JPA (Hibernate)

MappingException: Repeated column in mapping for entity 에러 해결

DevHyo 2021. 4. 22. 00:10

프로젝트를 진행하다가 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로 변경하여, 에러 해결

insertableupdatable를 간략하게 설명하자면, 다음과 같습니다.

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);
    }
}

위의 테스트 코드를 실행 했을때, 에러 없이 정상적으로 실행되는 것을 확인할 수 있었습니다.