PK가 아닌 JPA Auto Increment 값 가져오기(@Generated)

YouMakeMeSmile·2021년 8월 14일
1

기존 레거시의 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();
    }

profile
어느새 7년차 중니어 백엔드 개발자 입니다.

0개의 댓글