About JPA (1)

Why use JPA?


영속성 프레임워크

영속성(Persistence)은 데이터가 프로세스 내에서 생성됐지만 프로세스의 생명주기와 별개로 유지되는 특성을 말하며, 파일이나 데이터베이스 같은 저장소에 데이터를 보존하는 것으로 실현한다.

프로그램 언어 측면에서 데이터를 영속시키는 방법은 다양한데 자바 진영에서는 SQL Mapper와 ORM 같은 기술을 이용한 영속성 프레임워크를 주로 사용한다.

SQL Mapper는 sql에 데이터를 매핑하는 sql 중심 기술로 iBatis와 MyBatis가 있고, ORM은 객체와 테이블 간의 관계를 매핑하는 객체 중심 기술로 JPA를 구현한 하이버네이트와 eclipseLink 등이 있다.

MyBatis와 JPA는 장단이 명확한데, 우리는 객체 지향 언어인 자바를 사용하는 개발자니까 “Work in Object centric” 관점에서 두 기술의 차이점을 알아보자

MyBatis와 JPA 비교


* 비지니스 요구사항

두 기술을 비교하기 위해 간단한 예제를 만들어보자

1. 회원을 관리할 수 있는 시스템을 구축해라
2. 회원은 최초 이름과 나이 정보를 갖는다.

* 회원 Class

JPA의 @Entity나 @Id 등의 어노테이션은 생략

@Getter
@Setter
public class User {
    private Long id;
    private String name;
    private int age;
}

* 구조

Spring framework를 사용할 경우 볼수있는 일반적인 구조 architecture

1. 삽입 기능 개발

비지니스 요청사항에 맞게 삽입 기능을 개발해보자.

// UserController.class는 공통
@Controller
@RepuestMapping("/user")
public UserController() {
    @RequestMapping(method=RequestMethod.POST) 
    public void save(User user){
        userService.save(user);
    }
}
// UserService.class도 공통
@Service
public UserService() {
    public void save(User user) {
        userRepository.save(user);
    }
}
  • MyBatis

    UserService로부터 UserRepository의 save 메소드가 호출되면 MyBatis 설정으로 인해 UserRepository와 매핑되는 UserMapper.xml의 save를 찾아서 실행하는데, 데이터베이스에 user 정보를 삽입할 수 있도록 xml을 작성하자

@Repository
public interface UserRepository() {
    void save(User user);
}
<xml>
    <namespace="user"/>
    <insert id="save" parameterType="com.test.dto.User">
        insert into USER(name, age)
        values(#{name}, #{age});
    </insert>
</xml>
  • JPA

    UserService가 UserRepository의 save 메소드만 호출하면 끝. MyBatis 처럼 xml을 작성할 필요가 없다. 삽입 sql은 JPA가 알아서 만들어준다.

@Repository
public interface UserRepository() extends SimpleJpaRepository<User.class, Long> {
    void save(User user);
}

2. 조회 기능 개발

데이터를 삽입하는 기능을 개발했으니, 삽입한 데이터를 조회하는 기능을 추가하자.

// UserController.class는 공통
@Controller
@RepuestMapping("/user")
public UserController() {
    @RequestMapping("/{id}", method=RequestMethod.GET) 
    public User find(@PathVariable Long userId){
        return userService.findById(userId);
    }
}
// UserService.class도 공통
@Service
public UserService() {
    public User findById(Long userId) {
        return userRepository.findById(userId);
    }
}
  • MyBatis

    UserService로부터 UserRepository의 findById 메소드가 호출되면 UserRepository와 매핑되는 UserMapper.xml의 findById를 찾아서 실행하는데, 데이터베이스에서 user 정보를 조회할 수 있도록 xml을 작성하자

@Repository
public interface UserRepository() {
    void findById(Long userId);
}
<xml>
    <namespace="user"/>
    <select id="findById" parameterType="Long">
        select name, age
        from USER
        where id = #{userId};
    </select>
</xml>
  • JPA

    UserService가 UserRepository의 findById 메소드만 호출하면 끝. MyBatis 처럼 xml을 작성할 필요가 없다. 조회 sql은 JPA가 알아서해준다.

@Repository
public interface UserRepository() extends SimpleJpaRepository<User.class, Long> {
    void findById(Long userId);
}

3. 수정 기능 개발

삽입과 조회가 가능해졌다. 이제 수정 기능을 만들어보자 (참고로 고객은 회원 정보를 삭제하는 기능을 원하지 않기 때문에 이게 마지막이다.)

// UserController.class는 공통
@Controller
@RepuestMapping("/user")
public UserController() {
    @RequestMapping(method=RequestMethod.PUT) 
    public User find@RequestBody User user){
        return userService.update(user);
    }
}
  • MyBatis

    UserService로부터 UserRepository의 update 메소드가 호출되면 UserRepository와 매핑되는 UserMapper.xml의 update를 찾아서 실행하는데, 데이터베이스에서 user 정보를 수정할 수 있도록 xml을 작성하자

@Service
public UserService() {
    public User update(User user) {
        return userRepository.update(user);
    }
}
@Repository
public interface UserRepository() {
    void update(User user);
}
<xml>
    <namespace="user"/>
    <update id="update" parameterType="com.test.dto.User">
        update USER
            name = #{name}
            age = #{age} 
        where id = #{user.userId};
    </update>
</xml>
  • JPA

    UserService가 UserRepository의 save 메소드만 호출하면 끝. MyBatis 처럼 xml을 작성할 필요가 없다. 수정 sql은 JPA가 알아서해준다.

    참고로 EntityManager는 save할 객체에 id(식별자)가 이미 있는 상태라면 데이터베이스에서 id로 조회한 후 update를 수행한다.

@Service
public UserService() {
    public User update(User user) {
        return userRepository.save(user);
    }
}
@Repository
public interface UserRepository() extends SimpleJpaRepository<User.class, Long> {
    void save(User user);
}

4. 요구사항의 변경

개발을 하다보면 요구사항이 변경되어 다시 개발해야하거나 잘사용하던 기능을 개선 혹은 사용하지 않는 기능의 삭제 등으로 유지보수를 해야하는 경우가 많다.

만약 고객으로부터 회원의 연락처를 추가해달라는 요구사항이 온다면 MyBatis와 JPA는 어떤 차이가 있을까?

  • MyBatis

    User class에 전화번호 field를 추가하고 User 테이블과 관련된 모든 class와 xml에 전화번호를 추가한다.

  • JPA

    User class에 전화번호 field를 추가 후 테이블의 전화번호 column과 정확하게 매핑한다.

5. 정리

MyBatis와 같이 SQL 중심의 개발은 CRUD 쿼리를 작성하는데 많은 시간을 소요하게되고 요구사항 변화에 치명적이다. 단순히 테이블이 100개라면 CRUD 코드를 100번 만들어야 하는데 기껏 만들어 놓아도 테이블이 변경되면 연관된 모든 class와 xml을 확인하고 수정해야하니 상당히 비효율적인 것이다.

이렇게 비효율적인 작업을 할 수 밖에 없는 이유는 데이터를 영속하기 위해서 데이터베이스에 저장해야하는데, 데이터베이스에는 객체지향 언어에서 제공하는 추상화나 상속 같은 개념이 없고, 객체의 구조와 데이터베이스에 저장되는 데이터의 구조가 다르기 때문이다.

이러한 문제를 패러다임의 불일치라고 하는데, MyBatis 같은 SQL Mapper를 이용하면 개발자가 패러다임의 불일치 문제를 해결해야하는 반면 JPA는 객체와 테이블의 관계를 잘 매핑 해준다면 패러다임의 불일치 문제를 알아서 해결해준다.

그러므로 JPA를 씁시다..

Written on January 11, 2018