본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
JPA Dirty Checking
JPA Dirty Checking
- 코드에서 엔티티의 값만 변경했을 뿐인데, 데이터베이스 업데이트 쿼리가 발생한다?
- 이유는 Dirty Checking 덕분이며, Dirty란 상태의 변화가 생긴 정도를 의미한다
- 즉, Dirty Checking이란 entity 상태 변경 검사
- JPA에서 트랜잭션이 끝나는 시점에 변화가 있는 모든 entity 객체를 데이터베이스에 자동으로 반영해준다
Dirty Checking 내부 구조
- JPA는 commit 하는 순간 내부적으로 flush가 호출되고, 이때 엔티티와 스냅샷을 비교
- 1차 캐시에는 처음 들어온 상태인 엔티티 스냅샷을 넣어두고 commit 하는 순간 변경된 값이 있는지 비교하여 변경된 값이 있으면 update 쿼리를 쓰기 지연 SQL에 넣어둔다
JPA Dirty Checking 주의사항
- 당연히 Dirty Checking은 영속성 컨텍스트가 관리하는 entity에만 적용된다
- 영속성 컨텍스트에 처음 저장된 순간 스냅샷을 저장해놓고, 트랜잭션이 끝나는 시점에 비교하여 변경된 부분을 쿼리로 생성하여 데이터베이스로 반영한다
- 즉, 영속 상태가 아닐 경우 값을 변경해도 데이터베이스에 반영되지 않는다
- 트랜잭션이 없이 데이터 반영이 일어나지 않는다
JPA Auditing으로 생성시간-수정시간 자동화 구현
JPA Auditing 사용 이유
- 엔티티 마다 공통적으로 필요한 값(등록일, 수정일) 등은 유지보수에 있어서 반드시 필요한 값이며, 이를 코드로 매번 구현한다면?
JPA Auditing 이란?
- @MappedSuperclass : 엔티티의 공통 매핑 정보
- @EntityListeners : 해당 클래스에 auditing 기능을 포함
- @CreatedDate : Entity가 생성되어 저장될 때 시간이 자동 저장
- @LastModifiedDate : Entity 값을 변경할 때 시간이 Update
Spring Transactional 사용 시 주의사항
Spring Transactional 이란?
- 스프링에서 트랜잭션 처리를 @Transactional 어노테이션을 이용하여 처리
- @Transactional 은 스프링 AOP 기반이며, 스프링 AOP는 Proxy 기반으로 동작
- @Transactionl이 포함된 메소드가 호출될 경우, 프록시 객체를 생성함으로써 트랜잭션 생성 및 커밋 또는 롤백 후 트랜잭션 닫는 부수적인 작업을 프록시 객체에게 위임
- 프록시의 핵심적인 기능은 지정된 메소드가 호출(Invocation) 될 때 이 메소드를 가로채어 부가 기능들을 프록시 객체에게 위임
- 즉, 개발자가 메소드에 @Transactional만 선언하고 비즈니스 로직에 집중 가능
Spring Transactional 주의사항
- 스프링 AOP 기반으로 하는 기능들(@Transactional, @Cacheable, @Async) 사용시 Self Invocation 문제로 인하여 장애가 발생할 수 있음
- 메소드가 호출되는 시점에 프록시 객체를 생성하고, 프록시 객체는 부가 기능(트랜잭션)을 주입해 준다.
- 외부에서 bar() 메소드를 실행할 때 정상적으로 프록시가 동작
- 하지만, @Transactional을 foo()에만 선언하고 외부에서 bar()를 호출하고, bar() → foo() 호출했다면?
Spring Transactional 주의사항(읽기 전용)
- @Transactional(readOnly=true) 스프링에서 트랜잭션을 읽기 전용으로 설정 가능
- 읽기 전용으로 설정하게 되면, JPA에서 스냅샷 저장 및 Dirty Checking 작업을 수행하지 않기 때문에 성능적으로 이점
- 따라서, Dirty Checking 불가
Spring Transactional 주의사항(우선순위)
- @Transactiona
- 클래스에 @Transactional(readOnly=true) (읽기 전용)으로 적용해 놓고, update가 발생하는 메소드에만 readOnly=false 우선 적용 (SimpleJpaRepository)
약국 데이터 셋업
공공기관 데이터 약국
- https://www.data.go.kr/data/15065023/fileData.do
- 직접 sql 생성하여 저장하는 방법
- 도커 컨테이너 생성 시 초기 데이터 만들기
- 디렉토리 /docker-entrypoint-initdb.d/ 에 .sql 또는 .sh 파일을 넣어두면 컨테이너 실행 시 실행된다
- jpa.hibernate.ddl-auto:validate
pharmacy-recommendation-database:
container_name: pharmacy-recommendation-database
build:
dockerfile: Dockerfile
context: ./database
image: yoonms5319/pharmacy-recommendation-databse
environment:
- MARIADB_DATABASE=pharmacy-recommendation
- MARIADB_ROOT_PASSWORD=${SPRING_DATASOURCE_PASSWORD}
volumes:
- ./database/config:/etc/mysql/conf.d
- ./database/init:/docker-entrypoint-initdb.d
ports:
- "3306:3306"
거리 계산 알고리즘 구현
거리 계산 알고리즘 구현
- https://en.wikipedia.org/wiki/Haversine_formula
- 두 위도, 경도 사이의 거리를 계산하기 위한 알고리즘
- Haversine formula 알고리즘은 지구를 완전한 구 라고 가정하고 계산하기 때문에 0.5% 정도 오차가 발생 가능
- 해당 프로젝트에서 약간의 오차는 이슈 없기 때문에 아래와 같이 구현
// Haversine formula
private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
lat1 = Math.toRadians(lat1);
lon1 = Math.toRadians(lon1);
lat2 = Math.toRadians(lat2);
lon2 = Math.toRadians(lon2);
double earthRadius = 6371; //Kilometers
return earthRadius * Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon1 - lon2));
}
Spring retry 소개
[Spring] Spring Retry - SW Developer
Spring retry 구현 및 검증
Spring retry 구현
- retryTemplate을 사용한 방법과 어노테이션을 이용한 방법
@EnableRetry
@Configuration
public class RetryConfig{
}
@Retryable(
value={Exception.class},
maxAttempts=2,
backoff=@Backoff(delay=2000)
)
public KakaoApiResponseDto requestAddressSearch(String address){
@Recover
public KakaoApiResponseDto recover(Exception e, String address){
log.error("All the retries failed. address: {}, error: {}",address, e.getMessage());
return null;
}
implementaion 'org.springframework.retry:spring-retry'
Spring retry 테스트 코드 작성
- MockWebServer를 통해 retry 테스트
def "requestAddressSearch retry fail "() {
given:
def uri = mockWebServer.url("/").uri()
when:
mockWebServer.enqueue(new MockResponse().setResponseCode(429))
mockWebServer.enqueue(new MockResponse().setResponseCode(429))
def result = kakaoAddressSearchService.requestAddressSearch(inputAddress)
then:
2 * kakaoUriBuilderService.buildUriByAddressSearch(inputAddress) >> uri
result == null
}
'패스트캠퍼스 강의' 카테고리의 다른 글
[32일차] 50일 포트폴리오 챌린지 (0) | 2023.09.08 |
---|---|
[31일] 50일 포트폴리오 챌린지 (0) | 2023.09.07 |
[29일차] 50일 포트폴리오 챌린지 (0) | 2023.09.05 |
[28일차] 50일 포트폴리오 챌린지 (0) | 2023.09.04 |
[27일차] 50일 포트폴리오 챌린지 (0) | 2023.09.03 |