기존 레거시의 DataBase는 Oracle를 사용하였으며 테이블의 PK가 시퀀스인 경우에는 Oracle Sequence를 사용하거나 Max채번을 하는 경우가 혼재되어있었다.
이번 구조 개선 프로젝트에서는 Oracle에서 MySql로 DataBase를 변경하며 아키텍처적으로 모든 테이블의 PK는 서버에서의 UUID
를 사용하기로 하였다.
여기서 문제가 발생하였다. 기존 레거시에서는 사용자들이 5~6자리 정도의 시퀀스 번호를 외워서 조회하거나 비지니스적으로 시퀀스 번호를 사용하는 경우가 많아 UUID
는 PK 기존 레거시의 시퀀스 PK는 UK로 설정하기로 하였다.JPA에서는 PK로 1차캐시를 관리하는데 비지니스로직에서는 UK로 조회를 하기 때문에 캐싱중인 데이터들을 사용하지 못하고 DB에 Select를 계속 날리는 문제가 있다ㅠㅠ JPA의 장점?을 안쓰는것이다!!
이러하여 Master 테이블
과 Detail 테이블
이 있는 경우 한 요청으로 두 개의 테이블의 정보를 모두 입력 하는경우 트랜잭션이 묶여 있는 경우 Master 테이블
저장 이후 Detail 테이블
을 저장 할 때 지연쓰기로 인해 아직 DB에서 부여되는 Auto Increment
항목의 값이 셋팅이 안되어 null인 상태였다.
문제해결을 위해 가장먼저 떠올린 방식은 Master 테이블
저장이후 Flush
를 수행하고 1차 캐시
를 clear
한 이후 다시 해당 PK UUID
로 해당 데이터를 다시 find
하는 방식으로 테스트를 하였다.
@Transactional
public void test(){
String id = UUID.randomUUID().toString();
testMariaRepository.saveAndFlush(new Test(id,null));
entityManager.clear();
Optional<Test> test = testMariaRepository.findById(id);
}
다행이도 생각과 같이 한번에 Insert 이후 1차 캐시
를 clear
하여 이후 find
에서 다시 DB select
가 발생하여 Auto Increment
항목의 값을 가져올 수 있었다. 아키텍처적으로 Java에서 UUID PK를 설정하는 방식이기 때문에 Entity의 PK 필드에 이미 값이 존재하여 Select이후 DB에 데이터가 없는 것을 확인 한 후 Insert가 실행된다. Persistable isNew설정을 통해 의미 없는 Select 발생을 제거 할 수 있긴하다
하지만 아키텍처적으로 EntityManager
를 직접 사용하지 않기로 하였고 나도 위의 방식이 깔끔하지 못하다고 생각하였다. 그래서 이러한 문제 또한 나만의 문제가 아니고 다른 선배님들도 이러한 고민을 하였을 것이라고 생각하고 구글링을 시작하였다.
구글링 결과 @Id
어노테이션에만 적용되는 @GeneratedValue
이외에 Hinbernate
어노테이션중 @Generated
라는 어노테이션을 사용하면 된다는 StackOverflow
의 글을 확인하였다.
@Generated
어노테이션은 해당 필드가 Insert
또는 Insert Or Update
될 경우 해당 Entity
의 PK로 find
하여 @Generated
가 설정된 필드의 값을 설정해 주는 어노테이션이였다.
/**
* The annotated property is generated by the database.
*
* @author Emmanuel Bernard
*/
@ValueGenerationType( generatedBy = GeneratedValueGeneration.class )
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Generated {
/**
* The enum value representing when the value is generated.
*/
GenerationTime value();
}
/**
* At what time(s) will the generation occur?
*
* @author Emmanuel Bernard
*/
public enum GenerationTime {
/**
* Indicates the value is never generated.
*/
NEVER( GenerationTiming.NEVER ),
/**
* Indicates the value is generated on insert.
*/
INSERT( GenerationTiming.INSERT ),
/**
* Indicates the value is generated on insert and on update.
*/
ALWAYS( GenerationTiming.ALWAYS );
...
}
해당 어노테이션를 적용한 이후 로직처리가 깔끔해졌다. 동작은 이전과 같이 Insert
이후 PK인 UUID
를 통해 find
가 발생하여 해당 결과로 값을 설정되었다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "test")
public class Test {
@Id
@Column(name = "id")
private String id;
@Generated(GenerationTime.INSERT)
@Column(name= "seq", insertable = false)
private Long seq;
}
@Transactional
public void test() {
String id = UUID.randomUUID().toString();
Test test = testMariaRepository.saveAndFlush(new Test(id, null));
System.out.println();
}