ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • PK가 없는 테이블을 JPA 엔티티로 만들기
    JAVA/Spring 2021. 12. 7. 10:05
    반응형

    문제의 시작

    Spring batch의 메타테이블 중 batch_job_execution_params 의 데이터를 JPA로 조회할 일이 생겼다.
    테이블 스키마는 아래와 같았다.

    CREATE TABLE `batch_job_execution_params` (
      `JOB_EXECUTION_ID` bigint(20) NOT NULL,
      `TYPE_CD` varchar(6) NOT NULL,
      `KEY_NAME` varchar(100) NOT NULL,
      `STRING_VAL` varchar(250) DEFAULT NULL,
      `DATE_VAL` datetime DEFAULT NULL,
      `LONG_VAL` bigint(20) DEFAULT NULL,
      `DOUBLE_VAL` double DEFAULT NULL,
      `IDENTIFYING` char(1) NOT NULL,
      KEY `JOB_EXEC_PARAMS_FK` (`JOB_EXECUTION_ID`),
      CONSTRAINT `JOB_EXEC_PARAMS_FK` FOREIGN KEY (`JOB_EXECUTION_ID`) REFERENCES `batch_job_execution` (`JOB_EXECUTION_ID`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    JPA에 대한 지식이 별로 없어서
    PK가 없어도 job_execution_id@Id로 선언하면 되지 않을까...? 라고 생각했다...

    문제의 직면...

    BatchJobExecutionParams 엔티티를 아래와 같이 만들었다.

    @Getter
    @Entity
    @Table(name = "BATCH_JOB_EXECUTION_PARAMS")
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class BatchJobExecutionParams {
    
        @Id
        @Column(name = "JOB_EXECUTION_ID", nullable = false)
        private Long jobExecutionId;
    
        @Column(name = "TYPE_CD", columnDefinition = "VARCHAR(6)", nullable = false)
        private String typeCd;
    
        @Column(name = "KEY_NAME", columnDefinition = "VARCHAR(100)", nullable = false)
        private String keyName;
    
        @Column(name = "STRING_VAL", columnDefinition = "VARCHAR(250)")
        private String stringVal;
    
        @Column(name = "DATE_VAL")
        private LocalDateTime dateVal;
    
        @Column(name = "LONG_VAL")
        private Long longVal;
    
        @Column(name = "DOUBLE_VAL")
        private Double doubleVal;
    
        @Column(name = "IDENTIFYING", columnDefinition = "CHAR(1)", nullable = false)
        private String identifying;
    }

    오... QueryDSL을 붙여서 jobExecutionId로 조회하도록 코드를 짜서 돌려봤다.

        public List<BatchJobExecutionParams> findExecutionParamsById(Long id) {
            return queryFactory
                    .select(batchJobExecutionParams)
                    .from(batchJobExecutionParams)
                    .where(batchJobExecutionParams.jobExecutionId.eq(id))
                    .fetch();
        }

    DB에 조회해 봤을땐 총 7개의 row가 조회되었는데
    프로그램을 돌리니... 7개는 맞는데... 전부 똑같은 row가 조회되었다...

    해결 과정

    batch_job_execution 테이블도 엔티티로 만들고, QueryDSL을 붙여서 jobExecutionId로 조회를 해봤는데
    DB에서 직접 조회할때랑 동일하게 결과가 잘 나왔다.

    도대체 뭐가 문제일까...?

    그래서 batch_job_execution 테이블과 batch_job_execution_params 테이블을 비교해 보았더니
    후자에는 PK가 없었다.

    에이 설마...

    폭풍검색!

    그 결과 아래와 같은 내용을 찾을 수 있었다.

    Every JPA entity must have a primary key.
    You can specify a primary key as a single primitive, or JDK object type entity field (see "Configuring a JPA Entity Simple Primary Key Field").
    You can specify a composite primary key made up of one or more primitive, or JDK object types using a separate composite primary key class (see "Configuring a JPA Entity Composite Primary Key Class").
    You can either assign primary key values yourself, or you can associate a primary key field with a primary key value generator (see "Configuring JPA Entity Automatic Primary Key Generation").

    JPA Spec

    If your object does not have an id, but its table does, this is fine. Make the object an Embeddable object, embeddable objects do not have ids. You will need a Entity that contains this Embeddable to persist and query it.

    참고

    문제 해결!

    먼저 만들었던 BatchJobExecutionParams 엔티티에서 @Id 어노테이션을 제거한 뒤, jobExecutionId, typeCd, keyName 세 개의 컬럼을 복합키로 만들기 위해
    얘네를 따로 추출해서 BatchJobExecutionParamsId 란 클래스를 만들었다.

    @Getter @Setter
    @Embeddable
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class BatchJobExecutionParamsId implements Serializable {
    
        @Column(name = "JOB_EXECUTION_ID", nullable = false)
        private Long jobExecutionId;
    
        @Column(name = "TYPE_CD", columnDefinition = "VARCHAR(6)", nullable = false)
        private String typeCd;
    
        @Column(name = "KEY_NAME", columnDefinition = "VARCHAR(100)", nullable = false)
        private String keyName;
    }

    그리고 난 뒤에 BatchJobExecutionParams 엔티티에서 BatchJobExecutionParamsId 클래스를 속성으로 추가한 뒤, @EmbeddedId 어노테이션을 추가했다.

    @Getter
    @Entity
    @Table(name = "BATCH_JOB_EXECUTION_PARAMS")
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class BatchJobExecutionParams {
    
        @EmbeddedId
        private BatchJobExecutionParamsId id;
    
        @Column(name = "STRING_VAL", columnDefinition = "VARCHAR(250)")
        private String stringVal;
    
        @Column(name = "DATE_VAL")
        private LocalDateTime dateVal;
    
        @Column(name = "LONG_VAL")
        private Long longVal;
    
        @Column(name = "DOUBLE_VAL")
        private Double doubleVal;
    
        @Column(name = "IDENTIFYING", columnDefinition = "CHAR(1)", nullable = false)
        private String identifying;
    }

    짜잔!
    프로그램을 돌려보니 7개의 row가 모두 다르게 정상적으로 확인이 되었다.

    @Embeddable@EmbeddedId에 대한 자세한 내용은 여기를 참고하기 바란다.

    반응형

    'JAVA > Spring' 카테고리의 다른 글

    Factory Pattern within Spring  (0) 2020.07.18

    댓글

Designed by Tistory.