MappingException: Repeated column in mapping for entity 에러 해결
프로젝트를 진행하다가 Entity를 제대로 설계하지 않아서 MappingException: Repeated column in mapping for entity 에러가 발생했고, 이와 관련해서 간단히 정리해봤습니다.
문제 상황 (예시)
EntityA
@Entity
@NoArgsConstructor
public class EntityA {
@Id
@GeneratedValue
private Long id;
@Column(name = "member_id")
private Long memberId;
public EntityA(Long memberId) {
this.memberId = memberId;
}
}
EntityB
EntityB에는 Long 타입의 memberId 필드를 선언했고, OneToOne 관계의 EntityA를 선언했으며, @JoinColumn은 EntityA의 memberId로 설정했습니다.
@Entity
@NoArgsConstructor
public class EntityB {
@Id
@GeneratedValue
private Long id;
@Column(name = "member_id")
private Long memberId;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", referencedColumnName = "member_id")
private EntityA entityA;
public EntityB(Long memberId, EntityA entityA) {
this.memberId = memberId;
this.entityA = entityA;
}
}
테스트 코드
EntityA와 EntityB에 대한 간단한 테스트 코드를 실행 했는데, 아래와 같은 에러가 발생했습니다.
@SpringBootTest
@Transactional
public class EntityAandBRepositoryTest {
@Autowired
private EntityARepository entityARepository;
@Autowired
private EntityBRepository entityBRepository;
@Test
void saveTest() {
final Long memberId = 1L;
final EntityA entityA = new EntityA(memberId);
final EntityB entityB = new EntityB(memberId, entityA);
entityARepository.save(entityA);
entityBRepository.save(entityB);
}
}
위의 에러를 하나씩 해석해보면, 다음과 같습니다.
javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory;
코드로 구현한 테이블과 DB 테이블 간의 호환이 되지 않아서 발생하는 에러입니다. 예를 들면, 코드로 구현한 테이블의 설정이 DB 테이블 설정을 위반하거나, DB 테이블로 생성을 할 수 없을때 발생합니다. 추가로 객체 직렬화를 하지 않아서 발생하기도 합니다.
nested exception is org.hibernate.MappingException: Repeated column in mapping for entity: com.hyoseok.dynamicdatasource.domain.ss.EntityB column: member_id (should be mapped with insert="false" update="false")
Entity에 반복되는 컬럼이 있는데, 그 컬럼은 member_id이며, 해당 컬럼의 insert 옵션을 false로 설정하고, update 옵션을 false로 설정하라는 에러입니다. 아래의 EntityB의 코드를 다시 확인해보면, member_id의 필드가 중복되는 것을 확인할 수 있습니다.
@Entity
@NoArgsConstructor
public class EntityB {
@Id
@GeneratedValue
private Long id;
@Column(name = "member_id") // member_id 필드 중복!
private Long memberId;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", referencedColumnName = "member_id") // member_id 필드 중복!
private EntityA entityA;
public EntityB(Long memberId, EntityA entityA) {
this.memberId = memberId;
this.entityA = entityA;
}
}
@Column의 insertable, updatable 속성 값을 false로 변경하여, 에러 해결
insertable과 updatable를 간략하게 설명하자면, 다음과 같습니다.
insertable 속성
- 엔티티 저장시, 해당 필드도 같이 저장한다. (기본값은 true)
- false로 설정하면, 이 필드는 DB에 저장하지 않는다.
- false 옵션은 읽기 전용일 때, 사용한다.
updatable 속성
- 엔티티 수정시, 해당 필드도 같이 수정한다. (기본값은 true)
- false로 설정하면, DB에 필드를 수정하지 않는다.
- false 옵션은 읽기 전용일 때, 사용한다.
위의 특징을 기준으로 Long 타입의 memberId @Column 옵션에 insertable = false, updatable = false를 추가하거나
@Entity
@NoArgsConstructor
public class EntityB {
@Id
@GeneratedValue
private Long id;
// insertable = false, updatable = false 설정
@Column(name = "member_id", insertable = false, updatable = false)
private Long memberId;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", referencedColumnName = "member_id")
private EntityA entityA;
public EntityB(Long memberId, EntityA entityA) {
this.memberId = memberId;
this.entityA = entityA;
}
}
EntityA @Column 옵션에 insertable = false, updatable = false를 추가합니다. (둘 중에 한 곳에 설정하면 됩니다.)
@Entity
@NoArgsConstructor
public class EntityB {
@Id
@GeneratedValue
private Long id;
@Column(name = "member_id")
private Long memberId;
// insertable = false, updatable = false 설정
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", referencedColumnName = "member_id", insertable = false, updatable = false)
private EntityA entityA;
public EntityB(Long memberId, EntityA entityA) {
this.memberId = memberId;
this.entityA = entityA;
}
}
다시 테스트 코드를 실행하면, 성공한 내역을 확인할 수 있었습니다.
처음부터 잘 못 설계된 Entity
에러를 해결할 수 있었지만, 하나의 Entity에 같은 필드 2개가 있다는 것은 DB 테이블 설정에 위반되는 부분이라고 생각했고, 이를 아래와 같은 코드로 수정했습니다.
EntityB
@Entity
@NoArgsConstructor
public class EntityB {
@Id
@GeneratedValue
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", referencedColumnName = "member_id")
private EntityA entityA;
public EntityB(EntityA entityA) {
this.entityA = entityA;
}
}
테스트 코드
@SpringBootTest
@Transactional
public class EntityAandBRepositoryTest {
@Autowired
private EntityARepository entityARepository;
@Autowired
private EntityBRepository entityBRepository;
@Test
void saveTest() {
final Long memberId = 1L;
final EntityA entityA = new EntityA(memberId);
final EntityB entityB = new EntityB(entityA);
entityARepository.save(entityA);
entityBRepository.save(entityB);
}
}
위의 테스트 코드를 실행 했을때, 에러 없이 정상적으로 실행되는 것을 확인할 수 있었습니다.