먼저 중요 패키지 버전을 보면 아래와 같다.

  • springboot 2.1.5.RELEASE
    • spring-data 2.1.5.RELEASE
  • spring-core 5.1.7.RELEASE
  • hibernate-core 5.3.10.Final
  • mysql 5.7.21-log

entity는 아래와 같습니다.

@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "T_NOTIFICATION")
public class Notification {

    @EmbeddedId
    private NotificationId id;

    @CreatedDate
    @Column(name = "CREATE_DT", nullable = false, updatable = false, columnDefinition = "datetime(3)")
    private Instant createdDate;

    @LastModifiedDate
    @Column(name = "RECEIVER_RESPONSE_DT", nullable = true, updatable = false, columnDefinition = "datetime(3)")
    private Instant receiverResponseDate;

    @Column(name = "NOTI_REASON", nullable = false, length = 64)
    private String notiReason;

    @ColumnDefault("1")
    @Column(name = "SEND_COUNT", nullable = false, columnDefinition = "tinyint")
    private int sendCount;

    @Column(name = "REQUEST_USER", nullable = false, length = 36)
    private String requestUser;

    @Column(name = "SEND_MESSAGE", nullable = false, columnDefinition = "text")
    private String message;

    @Builder.Default
    @OneToMany(mappedBy = "notification", fetch = FetchType.EAGER, cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Collection<NotificationMessage> notificationMessages = new ArrayList<NotificationMessage>();
}
@Embeddable
@AllArgsConstructor
@NoArgsConstructor
@Data
public class NotificationId implements Serializable {

    private static final long serialVersionUID = 1L;

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "NOTI_SEQ", unique = true, updatable = false, nullable = false)
    private BigInteger NotiSeq;

    @Column(name = "PROJECT_ID", nullable = false, length = 36)
    private String projectId;

    @Column(name = "RECEIVER_ID", nullable = false, length = 128)
    private String receiverId;

    @Column(name = "NOTI_GID", nullable = false, length = 128)
    private String notiId;

}
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "T_NOTIFICATION_MESSAGE")
public class NotificationMessage {

    @Id
    @Column(name = "MESSAGE_ID", nullable = false, length = 100)
    private String messageId;

    @ColumnDefault("SEND")
    @Column(name = "STATE", nullable = false, length = 10)
    private String state;

    @CreatedDate
    @Column(name = "SEND_DT", nullable = false, columnDefinition = "datetime(3)")
    private Instant sendDate;

    @Column(name = "END_DT", nullable = true, columnDefinition = "datetime(3)")
    private Instant endDate;

    @Column(name = "SEND_RESULT_CODE", nullable = true, columnDefinition = "smallint(5)")
    private Integer resultCode;

    @Column(name = "SEND_RESULT_REASON", nullable = true, length = 1024)
    private String resultReason;

    @Column(name = "SEND_RESULT_MESSAGE", nullable = true, columnDefinition = "text")
    private String resultMessage;

    @ManyToOne
    @JoinColumns(value = {
            @JoinColumn(name = "NOTI_SEQ", referencedColumnName = "NOTI_SEQ"),
            @JoinColumn(name = "PROJECT_ID", referencedColumnName = "PROJECT_ID"),
            @JoinColumn(name = "RECEIVER_ID", referencedColumnName = "RECEIVER_ID"),
            @JoinColumn(name = "NOTI_GID", referencedColumnName = "NOTI_GID"),
    }, foreignKey = @ForeignKey(name = "FK_T_NOTIFICATION_TO_T_NOTIFICATION_MESSAGE"))
    private Notification notification;
}

Notification class에 EmbeddedId를 설정하여 복합키를 사용하고 있고 NotificationId class에 정의된 복합키중에 notiSeq는 @GeneratedValue로 설정하여 auto increment를 사용하려고 하였습니다. 실제 코드에서는 

@Override
public Notification insertOrUpdatePushMessage(Message message) {
    Notification notification = convertMessageToNotification(message);
    return this.repository.saveAndFlush(notification);
}

Message를 받아서 Notification으로 변경해서 save()를 하죠. 그런데 문제는 save()하고 return된 Notification 객체에는 notiSeq가 Null이 들어오는 것이었습니다.

사실 hibernate의 batch를 사용하고 있어서 auto increment를 가져오는 타이밍이 persistant와 안 맞나 보다 하고 batch도 사용하지 않아 봤는데 실패!!! 그럼 flush를 안 해서 그런가??? 하고 위 코드처럼 saveAndFlush()로 바꿔도 실패!!! 멘붕 중에 아래 블로그를 찾았다.

https://kihoonkim.github.io/2017/01/27/JPA(Java%20ORM)/3.%20JPA-%EC%97%94%ED%8B%B0%ED%8B%B0%20%EB%A7%A4%ED%95%91/

 

(JPA - 3) 엔티티 매핑

앞에서 영속성 컨텍스트에 객체(엔티티)를 저장하는 것을 보았다.객체와 관계형 데이터베이스의 다른 개념을 어떻게 매핑하고 데이터가 반영되는지 알아보자. 객체(엔티티)를 영속성 컨테스트에 저장(persist) 후 트랜젝션이 커밋되는 시점에각 데이터베이스에 맞는 SQL을 생성하여 데이터베이스로 보내진다.객체를 어떻게 엔티티로 만들고 테이블과 매핑하는지 알아보

kihoonkim.github.io

두 방식 중 무엇을 사용하든 복합키에는 @GeneratedValue를 통해 기본키를 자동생성할 수 없다.
반드시 직접 할당 전략을 사용해야 된다.

오!!! 복합키는 안 되는 거구나.... 허무함과 안도감이 교차했다. 나중에 왜 그런지 hibernate문서를 좀 더 찾아봐야겠지만 일단 믿고 가기로 했다. 

결론은 복합키에 unique한 auto increment를 사용하는 게 모순이라고 판단해서 auto increment만 PK로 변경해서 진행 중이다.

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 1  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04
JPA 기본 키 전략  (0) 2019.06.27

1. 직접 할당

@id 애노테이션으로 필드와 매핑한다. 자바 기본형, 래퍼형, String, Date, BigDecimal, BigInteger가 타입이 가능하다

 

2. IDENTITY

기본 키 생성을 DB에 위임한다. DB의 auto_increment와 같은 기능을 사용할 때 쓴다. 키 필드에 @GeneratedValue(strategy = GenerationType.IDENTITY)를 사용한다.

이 전략을 사용하면 JPA는 기본 키 값을 얻어오기 위해 DB를 추가로 조회한다. 따라서 이 전략을 사용하는 엔티티를 새로 생성하여 식별자 값을 할당하려면 1차 캐시를 넘어서 DB에서 Insert한 후에 기본 키 값을 조회한다. 즉, persist()를 호출하는 즉시 Insert SQL이 DB에 전달되므로 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.

 

3. SEQUENCE

데이타베이스 시퀀스 : 유일한 값을 순서대로 생성하는 데이타베이스 오브젝트

시퀀스를 사용해서 기본 키를 생성하는 전략이다. 오라클, PostgreSQL, DB2, H2 등이 시퀀스를 지원한다.

@Entity
@SequenceGenerator(name = "MY_SEQ_GENERATOR",
		sequnceName = "MY_SEQ",
		initialValue = 1,
		allocationSize = 1)
public class Board {
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MY_SEQ_GENERATOR")
	private Long id;
}

@SequenceGenerator를 의 속성에서 name에 식별자 생성기 이름을 정해준다. 그리고 sequenceName에 DB에 등록되어 있는 시퀀스 이름을 지정해주고, 초기값(initialValue)과 한번 호출에 증가하는 수(allocationSize)를 입력하여 시퀀스 생성기를 등록한다.

 

이 전략에서 persist()를 호출할 때 DB의 시퀀스를 사용하여 식별자를 조회한다. 그리고 그 식별자를 엔티티에 할당한 후 엔티티를 영속성 컨텍스트에 저장한다.

 

4. TABLE

키 생성 전용 테이블을 하나 만들고, 여기에 이름과 값으로 사용할 컬럼을 만들어 DB의 시퀀스처럼 동작하게 하는 전략.

@Entity
@TableGenerator(name = "MY_SEQ_GENERATOR",
		table = "MY_SEQUNCES",
		pkColumnValue = "MY_SEQ",
		allocationSize = 1)
public class Board {
	@Id
	@GeneratedValue(strategy = GenerationType.TABLE, generator = "MY_SEQ_GENERATOR")
	private Long id;
}

 

MY_SEQUNCES 테이블은 아래와 같을 것이다

sequnceName next_val

MY_SEQ 2

@TableGenerator.pkColumnValue에서 지정한 "MY_SEQ"가 컬럼명으로 추가되었고, 키 생성기를 사용하여 기본 키를 할당할 때마다 next_val 컬럼 값이 증가한다.

출처: https://feco.tistory.com/96 [wmJun]

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 1  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04
jpa 복합키에서 auto increment  (0) 2019.06.27

간만에 네비 업데이트를 하게 되었다. 그런데 업데이트를 하는 도중에 특정 파일을 압축해제하는데 오류가 발생하는 상황이 발생 하였다.

결국 파일 문제라고 생각하고 내폴더에 있는 finedrive라는 폴더를 지우려고 하는데 지워지지 않는 zip 파일이 있었다. NetCoreDownloader.exe인가? 하는 프로세스가 이 파일을 사용중이라고 해서 지워지지 않았다.

ctl+alt+del을 누르면 작업관리자(task manager)로 들어 갈 수 있고 실행되면 프로세스 들이 쭉 보일텐데 NetCore로 시작하는 프로세스는 안보인다... 쩝... 안보이는데 어떻게 프로세스를 stop시키나...

결국 모든 프로세스를 stop시키는 방법!!! 리부팅이다. ㅋ 리부팅 한 후에 내폴더/finedrive가 깨끗하게 지워 졌고 그후에 ez다운로더의 압축해제오류도 무사히 처리 되었다.

결론은 압축해제 오류는 어떤 프로세스가 업데이트 파일을 사용하다 오류가 나서 ez다운로더가 파일을 사용할 수 없어서 발생한 오류로 추측된다.

+ Recent posts