Unique index or primary key violation 해결 방법

SuYeong·2023년 2월 13일
3

Unique index or primary key violation 오류를 만났습니다.

오류 메세지

Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation:

위와 같은 오류가 발생했습니다.

발생 이유

DB에 데이터를 INSERT 할 때, PRIMARY KEYUNIQUE KEY 가 중복되어 발생한 오류입니다.

오류를 알게된 과정

  1. MySQL을 사용하도록 설정
  2. data.sql 을 사용해서 미리 데이터를 DB에 넣어둠.
  3. 저장이 잘 되는지 확인하는 테스트 코드를 만들어서 실행.
    (저장은 스프링 데이터 JPA에서 만들어준 save() 메서드를 사용)

save() 가 실행되면 위와 같은 문제가 발생했습니다.

엔티티 클래스 모양

기본키 생성 방법은 아래와 같이 설정했었습니다.

@Entity
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ...
}

문제 발생 이유

  • 애플리케이션 DB와 테스트 DB가 달라서 발생한 문제!!

스프링 부트에서는, 테스트 코드를 실행할 때 별도로 설정해둔 것이 없다면, 테스트용 H2 DB를 생성해서 사용합니다. H2 DB에는 AUTO_INCREMENT 기능이 없으므로, GenerationType.IDENTITY를 추가하더라도 MySQL처럼 AUTO_INCREMENT가 적용되지 않습니다.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.8)

...
2023-02-13 22:23:20.197  INFO 33132 --- [           main] o.s.j.d.e.EmbeddedDatabaseFactory        : Starting embedded database: url='jdbc:h2:mem:bf5b6e37-55ee-4cb0-892a-486078b8804c;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
...
2023-02-13 22:23:20.254  INFO 28176 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
...

위는 테스트 코드를 동작시켰을 때, 기록되는 로그의 일부입니다.
database: url='jdbc:h2:mem:... 이라고 출력되는 것을 확인할 수 있습니다.
또한 dialect: org.hibernate.dialect.H2Dialect 을 보면, H2 DB가 사용됨을 확인할 수 있습니다.

해결 방법

해결 방법에는 세가지가 있습니다.

1. data.sql의 id필드 제거하기

data.sql을 사용하고 계시다면, 해당 SQL에 id 필드를 제거해서 문제를 해결할 수 있습니다.
아래는 data.sql의 일부 내용입니다.

변경 전

insert into Article (id, title, content, hashtag, created_at, created_by, modified_at, modified_by) values (1, 'Cold Prey II (Fritt Vilt II)', 'Nam ultrices', 'ipsum primis in faucibus orci luctus et ultrices', '2019-08-28 07:39:08', 'mesel0', '2022-03-22 14:11:38', 'clampl0');

변경 후

insert into Article (title, content, hashtag, created_at, created_by, modified_at, modified_by) values ('Cold Prey II (Fritt Vilt II)', 'Nam ultrices', 'ipsum primis in faucibus orci luctus et ultrices', '2019-08-28 07:39:08', 'mesel0', '2022-03-22 14:11:38', 'clampl0');

위와 같이, INSERT 문에 ID 필드를 제거했습니다.

2. 테스트 DB를 앱DB 와 같도록 설정하기

@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 추가!!
@DataJpaTest
class JpaRepositoryTest {
	...
}

@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 을 설정하면 메모리 DB를 자동 생성하지 않고, application.yaml 혹은 application.properties 에 있는 설정이 적용됩니다.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.8)

...
2023-02-13 22:29:47.373  INFO 4456 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect1

실행해보면, 위와 같이 dialect: org.hibernate.dialect.MySQL8Dialect1. 즉, MySQL이 연결에 적용되는 것을 획안할 수 있습니다.

3. H2 DB에 Compatibility 적용하기

# application.yaml
spring:
  config.activate.on-profile: testdb # 프로파일 이름 등록
  datasource:
    url: jdbc:h2:mem:board;mode=mysql # 핵심!
    driver-class-name: org.h2.Driver
@ActiveProfiles("testdb") # testdb라는 이름을 가진 프로파일을 적용
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DataJpaTest
class JpaRepositoryTest {
	...
}

datasource.url:jdbc:h2:mem:board;mode=mysql 을 설정해줍니다.
board라고 적은 부분에는 본인이 원하는 글자를 적으면 됩니다.
H2 DB의 MySQL모드는 AUTO_INCREMENT를 지원합니다.
(참고: http://www.h2database.com/html/features.html#compatibility)

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.8)

...
2023-02-13 22:47:26.540  INFO 33508 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect

실행시켜보면, 위와 같이 H2 DB가 적용되지만 오류가 발생하지 않습니다.

"testdb" 프로파일을 모든 테스트에 적용하고 싶다면?

@ActiveProfiles("testdb")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

위 두개를 매번 적어주기가 귀찮고 번잡합니다.

spring:
  config.activate.on-profile: testdb
  datasource:
    url: jdbc:h2:mem:board;mode=mysql
    driver-class-name: org.h2.Driver
  test.database.replace: none # 추가!!

application.yaml에 위 추가 필드를 적습니다.
그러면, @AutoConfigureTestDatabase 어노테이션은 생략할 수 있습니다.

단, @AutoConfigureTestDatabase 혹은 test.database.replace: 둘중 하나는 설정해야 합니다. 그렇지 않으면 자동 생성 메모리 DB를 만들어버려서 앞서 해결한 오류가 계속 발생합니다.

profile
안녕하세요

3개의 댓글

comment-user-thumbnail
2023년 2월 13일

댓글에 답 좀 달아주세요🥺

답글 달기
comment-user-thumbnail
2023년 2월 14일

2.테스트 db 설명한 문단 마지막 줄에 오타 있어용

답글 달기
comment-user-thumbnail
2023년 7월 9일

덕분에 좋은 내용 잘 보고 갑니다.
정말 감사합니다.

답글 달기