Home

0

Spring Web Socket - STOMP (Simple Text Oriented Message Protocol)

목차 Spring Web Socket - Chat 프로그램 만들기 1 Spring Web Socket - STOMP (Simple Text Oriented Message Protocol) 네트워크 - Web Socket 참고 https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html https://www.baeldung.com/websockets-spring STOMP (Simple/Stream Text Oriented Message Protocol) WebSocket 기반 프로토콜이며 Client 와 Server 가 negotiate 하기 위한 sub-protocol 이 정의 돼 있다. STOMP 은 메세징 전송을 효율적으로 하기 위해 탄생한 Text 지향 프로토콜 이며 Message Payload에는 Text or Binary 데이터를 포함 해 전송할 수 있다. 또한 sub-protocol 이 정의 돼 있어 Client 에서 서버로 전송할 메시지 유형, 형식, 내용등이 정의 돼 있다. pub/sub 구조로 되어있어 메세지를 전송하고 메세지를 받아 처리하는 부분이 확실히 정해져 있기 때문에 개발자 입장에서 명확하게 인지하고 개발할 수 있는 이점이 있다. 또한 STOMP를 이용하면 메세지의 헤더에 값을 줄 수 있어 헤더 값을 기반으로 통신 시 인증 처리(ChannelInterceptor) 를 구현하는 것도 가능하며 STOMP 스펙에 정의한 규칙만 잘 지키면 여러 언어 및 플랫폼 간 메세지를 상호 운영할 수 있다. 만약 Spring에서 지원하는 STOMP를 사용하면 Spring WebSocket 어플리케이션은 STOMP Broker로 동작하게 된다. STOMP (Simple Text Oriented Message Protocol) 구성 SimpAnnotationMethod Client 로 부터 전달 받은 Message 를 처리한다. clientInboundChannel WebSocket client 로부터 전달 받은 메시지를 전송해준다. clientOutboundChannel Server 메시지를 WebSocket Client 에 전송해준다. brokerChannel Server 내부에서 사용하는 Channel, Message Broker 에 메시지를 전송해준다.

0

Spring boot - StreamingResponseBody

목차 Spring boot - StreamingResponseBody Spring boot - ResourceRegion Spring boot - 파일 다운로드 서비스 구현하기 Spring boot - 파일 업로드 서비스 구현하기 Spring boot - Resource 추상화 Spring boot - MultipartFile 에서 발생하는 예외 처리 Spring boot - MultipartFile 를 이용한 파일 업로드 Spring boot - Part 객체를 이용한 파일 업로드 참고 https://www.baeldung.com/spring-mvc-sse-streams StreamingResponseBody Spring 에서 제공하는 비동기 요청 처리를 위한 객체, 응답값을 Byte 로 변환해 Streaming 형태로 줄 때 사용하는 객체 @GetMapping("/video3")public ResponseEntity<StreamingResponseBody> handleRbe() throws FileNotFoundException { File file = new File("/Users/dongwoo-yang/spring-file/mysong.mp4"); InputStream inputStream = new FileInputStream(file); StreamingResponseBody stream = out -> { byte[] data = new byte[1024]; int length = 0; while((length = inputStream.read(data)) >= 0){ out.write(data, 0, length); } inputStream.close(); out.flush(); }; HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "video/mp4"); headers.add("Content-Length", Long.toString(file.length())); return ResponseEntity.ok().headers(headers).body(stream);}

0

Spring boot - Actuator + Prometheus + Grafana

목차 Spring boot - Actuator + Prometheus + Grafana Spring boot - 메시지 국제화 MessageSource Spring Boot - WebMvcConfigurer Spring Boot - 자동 설정 이해 참고 https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator https://www.baeldung.com/spring-boot-actuators https://docs.spring.io/spring-boot/docs/2.1.8.RELEASE/reference/html/production-ready.html https://covenant.tistory.com/244 https://devbksheen.tistory.com/182 Spring boot - Actuator Actuator 는 별도의 구현 없이 Metric 정보, 트래픽 정보, 데이터 베이스 등 운영환경에서 Application 상태 정보에 접근 할 수 있는 기능을 제공하고, HTTP 와 JMX 를 이용해 접근할 수 있다. https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.endpoints 공식 문서에서 Actuator 가 지원하는 End Point 를 확인할 수 있다. Actuator 의존성 추가implementation 'org.springframework.boot:spring-boot-starter-actuator' 의존성을 추가한 뒤 Spring Application 을 실행 시킨 후 http://localhost:8080/actuator 로 접속하면 현재 Application 에서 End Point 들을 확인할 수 있다.

0

Spring boot - ResourceRegion

목차 Spring boot - StreamingResponseBody Spring boot - ResourceRegion Spring boot - 파일 다운로드 서비스 구현하기 Spring boot - 파일 업로드 서비스 구현하기 Spring boot - Resource 추상화 Spring boot - MultipartFile 에서 발생하는 예외 처리 Spring boot - MultipartFile 를 이용한 파일 업로드 Spring boot - Part 객체를 이용한 파일 업로드 Http Range Request 서버에서 클라이언트로 HTTP 메시지 중 일부만 전송할 수 있도록 허용하는 기술 대용량의 미디어 파일, 파일 전송 중 일시 정지 및 다시 시작이 가능하다 Client 가 Range Header 를 통해 특정 리소스의 일부를 요청하면 서버가 그 부분만 전송하는 방식으로 동작한다. Server 가 Range Request 를 지원하면 Response Http Header 에 Content-Range 가 존재한다. HTTP Range 요청에 대한 정상 응답 코드로는 PARTIAL_CONTENT(206) 을 반환한다. Range 요청에 대한 응답 값이 Body 에 담겨져 있다. Spring boot - ResourceRegion HttpRange 는 Range Header 정보를 담든 객체다. Request Header 로부터 정보를 얻어올 수 있다. ResourceRegion 는 전달 받은 Resource 객체 로부터 Range 범위 만큼 나눠 가져오는 객체다. @RestController@Slf4jpublic class VideoController { @GetMapping(value = "/video") public ResponseEntity<ResourceRegion> streamVideo(@RequestHeader HttpHeaders headers) throws IOException { UrlResource video = new UrlResource("file:/Users/dongwoo-yang/spring-file/mysong.mp4"); ResourceRegion resourceRegion; final long size = 1000000L; long contentLength = video.contentLength(); Optional<HttpRange> optional = headers.getRange().stream().findFirst(); HttpRange httpRange; if (optional.isPresent()) { httpRange = optional.get(); long start = httpRange.getRangeStart(contentLength); long end = httpRange.getRangeEnd(contentLength); long rangeLength = Long.min(size, end - start + 1); resourceRegion = new ResourceRegion(video, start, rangeLength); } else { long rangeLength = Long.min(size, contentLength); resourceRegion = new ResourceRegion(video, 0, rangeLength); } return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT) .contentType(MediaTypeFactory.getMediaType(video).orElse(MediaType.APPLICATION_OCTET_STREAM)) .body(resourceRegion); }} 서버에서 전송해주는 Resource Size 가 1 MB(1000000) 로 잡혀 있어 1 MB 씩 부분적으로 응답받는 것을 확인할 수 있다.

0

Redis

RedisRedis 특징 Key-Value Store Collection 지원 Pub/Sub 지원 디스크 저장 Replication (복제) Redis 는 싱글 스레드다.

0

데이터 베이스 - Index

Index 란?인덱스는 데이터베이스에서 테이블의 검색 속도를 높이기 위한 자료 구조 입니다. 인덱스는 테이블의 컬럼을 기반으로 만들어지며, 이를 사용하여 특정 데이터를 빠르게 찾을 수 있습니다. 일반적으로 인덱스는 B-트리나 해시 테이블과 같은 자료 구조를 사용하여 구현됩니다. B-트리 인덱스는 매우 일반적인 인덱스 유형으로, 테이블의 키 값이 정렬된 트리 구조를 사용하여 저장됩니다. 이 구조는 검색, 삽입 및 삭제 작업에 대해 매우 효율적입니다. 해시 인덱스는 테이블의 키 값을 해시 함수를 사용하여 저장하며, 빠른 검색 속도를 제공하지만 범위 검색이나 정렬 기능이 없습니다. 인덱스를 사용하면 특정 행을 검색하는 데 걸리는 시간이 줄어듭니다. 특히 대량의 데이터가 있는 경우 검색 성능을 향상시키는 데 매우 유용합니다. 그러나 인덱스는 테이블의 크기와 관련하여 저장 공간을 차지하므로 인덱스를 너무 많이 사용하면 데이터베이스 성능이 떨어질 수 있습니다. 또한 인덱스를 사용하면 데이터를 삽입, 수정 또는 삭제할 때 추가 작업이 필요하므로 데이터베이스 성능에 영향을 미칠 수 있습니다. 따라서 인덱스는 데이터베이스의 검색 성능을 향상시키는 데 매우 유용하지만, 사용에 주의해야 합니다. 적절한 인덱스를 사용하면 데이터베이스의 성능을 최적화할 수 있습니다. Index Scan 종류 Table Full Scan : 테이블 전체 탐색 Index Unique Scan : 인덱스 수직 탐색 Index Full Sacn : 인덱스 전체 탐색 Index Range Scan : 인덱스 범위 탐색 Index Skip Scan : 인덱스 스킵 탐색 Table Full Scan - 테이블 전체 스캔 전체 테이블의 모든 레코드를 검색하는 방식

0

Spring Data JPA - 벌크성 수정 쿼리

목차 Spring Data JPA - 벌크성 수정 쿼리 Spring Data JPA - Convertor Spring Data JPA - Auditing Spring Data JPA - Paging Request Paramater Spring Data JPA - 페이징과 정렬 Post not found: spring/spring-data-jpa/07-spring-data-jpa Spring Data JPA - 반환 타입 Spring Data JPA - Query 파라미터 바인딩 Spring Data JPA - Query 를 이용한 조회 결과를 특정 값으로 반환하기 Spring Data JPA - JPQL (Java Persistence Query Lange) 사용하기 Spring Data JPA - 메소드 이름으로 쿼리 생성하기 Spring Data JPA - 시작하기 벌크성 수정 쿼리public int bulkAgePlus(int age) { int resultCount = em.createQuery("update Member m set m.age = m.age + 1 where m.age >= :age") .setParameter("age", age) .executeUpdate(); return resultCount;} @Testpublic void bulkUpdate(){ memberJpaRepository.save(new Member("member1", 10)); memberJpaRepository.save(new Member("member2", 19)); memberJpaRepository.save(new Member("member3", 20)); memberJpaRepository.save(new Member("member4", 21)); memberJpaRepository.save(new Member("member5", 40)); int resultCount = memberJpaRepository.bulkAgePlus(20); assertThat(resultCount).isEqualTo(3);} Spring JPA 사용하기@Modifying@Query("update Member m set m.age = :age+1 where m.age >= :age")int bulkAgePlus(@Param("age") int age); 벌크성 쿼리는 영속성 Context를 무시하고 바로 DB에 업데이트를 진행 시키기 때문에 벌크성 쿼리를 수행한 후에는 영속성 Context를 초기화 해줄 필요가 있다. 안그러면 기존에 남아있는데이터가 꼬여서 문제가 발생할 수 있다. @Testpublic void bulkUpdate(){ memberRepository.save(new Member("member1", 10)); memberRepository.save(new Member("member2", 19)); memberRepository.save(new Member("member3", 20)); memberRepository.save(new Member("member4", 21)); memberRepository.save(new Member("member5", 40)); int resultCount = memberRepository.bulkAgePlus(20); // 벌크성 쿼리를 실행한 후 영속성 컨텍스트를 날려준다. entityManager.flush(); entityManager.clear(); assertThat(resultCount).isEqualTo(3);}

0

QueryDSL - QueryDsl 페이징 사용하기

목차 QueryDSL - QueryDsl 페이징 사용하기 QueryDSL - 사용자 정의 Repository QueryDSL - 동적 쿼리와 성능 최적화 조회 QueryDSL - 순수 JPA 리포지토리와 Querydsl QueryDSL - SQL Function 호출하기 QueryDSL - 수정, 삭제 벌크 연산 QueryDSL - 동적 쿼리 QueryDSL - 프로젝션과 결과 반환 QueryDSL - 조인 QueryDSL - 집계 QueryDSL - 결과 조회 QueryDSL - 검색 조건 쿼리 QueryDSL - Q-Type 활용 QueryDSL - JPA JPQL vs JPA QueryDSL QueryDSL 시작하기 Querydsl 페이징public interface MemberRepositoryCustom { List<MemberTeamDto> search(MemberSearchCondition condition); Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable); Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable);} QueryDSL 에서 제공하는 fetchResults 를 사용하게 되면 데이터를 가져오는 쿼리 와 가져온 데이터 수를 확인하는 Count 쿼리 총 2개의 쿼리가 발생하게 된다. @Overridepublic Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) { QueryResults<MemberTeamDto> results = queryFactory .select(new QMemberTeamDto( member.id.as("memberId"), member.username, member.age, team.id.as("teamId"), team.name.as("teamName") )) .from(member) .leftJoin(member.team, team) .where( usernameEq(condition.getUsername()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe()) ) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetchResults(); // fetchResults 를 사용하게 되면 count 쿼리가 같이 나가게 된다. List<MemberTeamDto> content = results.getResults(); long total = results.getTotal(); return new PageImpl<>(content, pageable, total);} Querydsl 페이징 쿼리와 Count 쿼리 분리@Overridepublic Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) { // 전체 내용을 가져오는 쿼리 List<MemberTeamDto> content = queryFactory .select(new QMemberTeamDto( member.id.as("memberId"), member.username, member.age, team.id.as("teamId"), team.name.as("teamName") )) .from(member) .leftJoin(member.team, team) .where( usernameEq(condition.getUsername()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe()) ) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); // 전체 Count 를 가져오는 쿼리 long total = queryFactory .select(ExpressionUtils.count(member)) .from(member) .leftJoin(member.team, team) .where( usernameEq(condition.getUsername()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe()) ) .fetchCount(); return new PageImpl<>(content, pageable, total);} 스프링 데이터 페이징 활용2 - CountQuery 최적화

0

QueryDSL - 사용자 정의 Repository

목차 QueryDSL - QueryDsl 페이징 사용하기 QueryDSL - 사용자 정의 Repository QueryDSL - 동적 쿼리와 성능 최적화 조회 QueryDSL - 순수 JPA 리포지토리와 Querydsl QueryDSL - SQL Function 호출하기 QueryDSL - 수정, 삭제 벌크 연산 QueryDSL - 동적 쿼리 QueryDSL - 프로젝션과 결과 반환 QueryDSL - 조인 QueryDSL - 집계 QueryDSL - 결과 조회 QueryDSL - 검색 조건 쿼리 QueryDSL - Q-Type 활용 QueryDSL - JPA JPQL vs JPA QueryDSL QueryDSL 시작하기 참고 https://docs.spring.io/spring-data/jpa/docs/2.1.3.RELEASE/reference/html/#repositories.custom-implementations 사용자 정의 Repository 사용법 사용자 정의 인터페이스 작성 사용자 정의 인터페이스 구현 스프링 데이터 Repository 에 사용자 정의 인터페이스 상속 1. 사용자 정의 인터페이스 작성public interface MemberRepositoryCustom { List<MemberTeamDto> search(MemberSearchCondition condition);} 2. 사용자 정의 인터페이스 구현

0

QueryDSL - 순수 JPA 리포지토리와 Querydsl

목차 QueryDSL - QueryDsl 페이징 사용하기 QueryDSL - 사용자 정의 Repository QueryDSL - 동적 쿼리와 성능 최적화 조회 QueryDSL - 순수 JPA 리포지토리와 Querydsl QueryDSL - SQL Function 호출하기 Post not found: jpa/querydsl/querydsl-10 QueryDSL - 동적 쿼리 QueryDSL - 프로젝션과 결과 반환 QueryDSL - 조인 QueryDSL - 집계 QueryDSL - 결과 조회 QueryDSL - 검색 조건 쿼리 QueryDSL - Q-Type 활용 QueryDSL - JPA JPQL vs JPA QueryDSL QueryDSL 시작하기 순수 JPA 리포지토리와 Querydsl@Repositorypublic class MemberJpaRepository { private final EntityManager em; private final JPAQueryFactory queryFactory; public MemberJpaRepository(EntityManager em){ this.em = em; this.queryFactory = new JPAQueryFactory(em); } public void save(Member member){ em.persist(member); } public Optional<Member> findById(Long id){ Member findMember = em.find(Member.class, id); return Optional.ofNullable(findMember); } public List<Member> findAll(){ return em.createQuery("select m from Member m", Member.class) .getResultList(); } public List<Member> findByUsername(String username){ return em.createQuery("select m from Member m where m.username = :username", Member.class) .setParameter("username", username) .getResultList(); }} SpringBootTest@Transactionalclass MemberJpaRepositoryTest { @Autowired EntityManager em; @Autowired MemberJpaRepository memberJpaRepository; @Test public void basicTest(){ Member member = new Member("member1", 10); memberJpaRepository.save(member); Member findMember = memberJpaRepository.findById(member.getId()).get(); assertThat(findMember).isEqualTo(member); List<Member> result = memberJpaRepository.findAll(); assertThat(result).containsExactly(member); List<Member> result2 = memberJpaRepository.findByUsername("member1"); assertThat(result2).containsExactly(member); }} JPQL QueryDsl 로 변경public List<Member> findAll(){ return em.createQuery("select m from Member m", Member.class) .getResultList();}public List<Member> findAll_Querydsl(){ return queryFactory .selectFrom(member) .fetch();}public List<Member> findByUsername(String username){ return em.createQuery("select m from Member m where m.username = :username", Member.class) .setParameter("username", username) .getResultList();}public List<Member> findByUsername_Querydsl(String username){ return queryFactory .selectFrom(member) .where(member.username.eq(username)) .fetch();} @Test public void basicQueryDsl(){ Member member = new Member("member1", 10); memberJpaRepository.save(member); Member findMember = memberJpaRepository.findById(member.getId()).get(); assertThat(findMember).isEqualTo(member); List<Member> result = memberJpaRepository.findAll_Querydsl(); assertThat(result).containsExactly(member); List<Member> result2 = memberJpaRepository.findByUsername_Querydsl("member1"); assertThat(result2).containsExactly(member); }