SW 개발/Spring

Spring Boot, JPA, @MappedSuperClass로 코드 중복 최소화하기

지단로보트 2019. 10. 17. 16:59

개요

  • 물리적으로 서로 다른 데이터베이스인데, 테이블 스키마는 완전히 동일하게 적용하여 운영 중인 2개의 레거시 애플리케이션이 있다고 가정하자. 이러한 2개의 서로 다른 데이터베이스를 한 애플리케이션에서 JPA를 이용하여 동시에 접근해야할 경우, 코드 중복을 최소화하는 방법을 고민하고 정리하였다.

사전지식

  • Spring Boot 환경에서 JPA를 적용하는 방법은 본 블로그의 이 글을 참고한다.

@MappedSuperClass 적용

  • @MappedSuperClass은 테이블 구조에 있어 많은 컬럼을 공통적으로 공유하면서 부분적으로 다른 컬럼이 존재하는 서로 다른 테이블을 엔티티로 구현하기 위해 고안된 JPA 어노테이션이다. 따라서 완전히 동일한 구조의 서로 다른 테이블에도 적용할 수 있다. 적용 예는 아래와 같다.
// 공통 클래스를 정의
// JPA의 패키지 탐색 대상에서 제외되어야 오류가 발생하지 않음
@MappedSuperClass
abstract class User {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0
    ...
}
  • 실제 물리 테이블에 맵핑될 엔티티의 작성 예는 아래와 같다. 이미 존재하는 공통 클래스를 통해 코드의 중복을 상당히 아낄 수 있다.
// foo 데이터베이스의 user 테이블에 대한 엔티티를 정의
// JPA의 패키지 탐색 대상에 포함되어 있어야 함
@Entity
@Table(name = "foo.user")
class FooUser : User()

// bar 데이터베이스의 user 테이블에 대한 엔티티를 정의
// JPA의 패키지 탐색 대상에 포함되어 있어야 함
@Entity
@Table(name = "bar.user")
class BarUser : User()
  • 위를 통해 엔티티에 대한 코드 중복은 최소화했다. 이 것으로 끝이 아니다. Spring Data JPA에서의 리파지터리에서도 코드 중복을 고려해야 한다. 작성 예는 아래와 같다.
// 공통 리파지터리를 정의
// JPA의 패키지 탐색 대상에서 제외되어야 오류가 발생하지 않음
interface UserRepository<T, ID> : JpaRepository<T, ID> {

    fun findByIdOrderByIdDesc(id: ID): T?
}

// foo 데이터베이스의 user 테이블에 대한 리파지터리를 정의
// JPA의 패키지 탐색 대상에 포함되어 있어야 함
interface FooUserAuthRepository : UserRepository<FooUser, Long>

// bar 데이터베이스의 user 테이블에 대한 리파지터리를 정의
// JPA의 패키지 탐색 대상에 포함되어 있어야 함
interface BarUserAuthRepository : UserRepository<BarUser, Long>

@MappedSuperClass 적용의 한계

  • @MappedSuperClass로 코드 중복은 최소화했지만, 엔티티와 리파지터리도 물리적으로 구분되면서 서비스 레이어에서 공통 로직을 처리하려면 구조가 상당히 복잡해진다. 이런 특수한 경우라면 차라리 애플리케이션을 분리하여 같은 소스지만 application.yml에 차이를 두고, 각각 지정된 데이터베이스의 테이블을 바라보는 것이 적절하다.