-
Spring Boot - JPAFramework & Library/Spring Boot 2021. 12. 1. 14:37
JPA의 정의
JPA란?
JPA는 Java Persistence API의 약자로 Java에서 관계형 DB를 사용하기 위한 양식을 정의한 인터페이스이다. 말 그대로 인터페이스이기 때문에, 어떤 코드가 구현되어 있는 것은 아니다. 단지, Java라는 객체 지행 프로그래밍 언어에서 관계형 DB를 객체 지향적으로 설계하기 위한 모범 사례를 JPA라고 하는 것이다.
즉, Java 진영에서 ORM 기술 표준으로 애플리케이션과 JDBC 사이에서 동작하는 것이다. 여기서 ORM이란, Object Relationship Mapper의 약자이다. JDBC나 Spring JDBC, iBatis 같은 경우 DB 처리를 위해 로직을 별도로 작성하고, 이를 SQL 코드를 통해 질의하였는데, ORM은 SQL 코드를 작성하지 않고, 객체 지향 프로그래밍 방식으로 DB를 사용하는 것이다. 대표적인 프레임워크로 Hibernate가 있다.
Hibernate란?
Hibernate는 JPA의 구현체이다. 실제로 JPA의 핵심체인 EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서는 SessionFactory, Session, Transaction으로 상속받고 각각 Impl로 구현하고 있음을 확인할 수 있다.
위 사진은 애플리케이션의 객체가 ORM을 통해 DB에 접근하는 과정을 표현한 것이다.
JPA의 특징
JPA의 장점
개발자들이 JDBC를 사용했을 때 불편했던 것은 커넥션, 세션 등 수많은 인스턴스가 섞여있는 코드들을 DB에 접근할 때마다 사용하여 중복된 코드가 발생하고, 유지보수가 어려웠던 점이 있었다. 이 문제들을 iBatis를 통해서 어느 정도 해결이 되었지만 또 다른 문제점이 있었다.
바로, 관계형 데이터베이스를 매핑하는 작업이다. 애플리케이션을 개발하다 보면 각 엔터티들을 관계해야 할 때가 생기게 된다. 우리가 관계형 데이터베이스를 사용하는 이유는 개발하려는 애플리케이션의 도메인 모델들을 관계형으로 묶기 위함인데, 애플리케이션 개발 중에 이러한 작업을 도맡아 하는 것은 쉬운 일이 아니다.
따라서, 개발자가 수행해야 하는 번거로운 작업을 줄이고, 알아서 관계를 매핑해주는 점은 아주 큰 장점이다. 그 외에도 다른 좋은 점들이 있다.
1. 생산성 향상 : 반복적인 SQL 코드 구현, CRUD 작업을 알아서 해준다.
2. 유지보수 : 객체 수정에 따른 SQL 코드 수정 작업이 필요 없다.
3. 데이터 관계 매핑 : 객체와 관계형 데이터를 매핑하는 과정을 자동으로 처리해준다.
4. 성능 : 캐싱을 지원하여 수행한 결과를 빠르게 도출한다.
JPA의 단점
ORM이 기존의 코드 가독성을 높이고, 개발자가 SQL 코드를 가능한 건드리지 않는다는 것을 목표로 한 프레임워크지만 이것이 모두 좋지만은 않다. SQL 코드를 한 번쯤 작성해봤다면 이해하는 부분이지만 SQL 코드를 어떻게 작성하느냐에 따라 성능이 좋을 수도 있고, 그렇지 않을 수 있기 때문이다.
ORM은 편한만큼 실제 쿼리를 작성하는 것에 비해 성능은 좋지 않다. 그래서 가벼운 프로젝트나 간단한 쿼리를 사용하는 프로젝트에는 유리하지만, 서비스의 요구 사항이 커지거나 데이터의 양이 커지기 시작하면 한계치에 도달한다. 또한, 내부 로직의 정확한 동작을 알 수 없는 상태에서 사용하기 어려운 프로젝트라면 더더욱 힘들 것이다.
이러한 문제점을 해결하기 위해, JPA에서 JPQL을 지원하여 자체적으로 쿼리를 정의할 수 있는 방법이 있는가 하면, ORM과 MyBatis를 혼용해서 사용하는 방법이 있다.
그리고 JPA를 잘 사용하기 위해서는 잘 알고 사용해야 한다는 점도 한 몫한다. JPA는 데이터를 처리하기 위해 Entity의 생명주기, 캐시, 쓰기 지연, 상속 전략, 컬렉션 매퍼 등 DB를 사용할 수 있는 다양한 옵션들이 존재하는데, 이를 어떻게 쓰느냐에 따라서 성능이 많이 달라지고, 오류의 발생 영향 등이 갈리기 때문이다.
Spring Data JPA
Spring Data JPA란?
Spring Data JPA는 JPA를 Spring Framework에서 편하게 사용할 수 있도록 만들어 놓은 모듈이다. 여태까지 개발을 진행 진행하면서, "@Entity" 애너테이션, "@Column" 애너테이션 등 간단한 애너테이션만을 가지고, 엔터티를 정의하고 DDL을 수행하는 등의 작업을 하였다. 이것이 바로 Spring Data JPA이다.
애플리케이션에서 JpaRepository를 사용하여 JPA의 구현체인 Hibernate로 요청을 전달하고, Hibernate는 이에 맞게 JDB API를 사용하여 DB에 접근하는 방식이다.
Spring Data JPA의 사용
@Getter @Entity @NoArgsConstructor(access = AccessLevel.PUBLIC) @Table public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column private long id; @Column(unique = true, nullable = false) private String username; @Column private String password; @Builder public User(String username, String password) { this.username = username; this.password = password; } }
위 코드와 같이 User라는 테이블을 만들고 싶을 때, 간단한 애너테이션을 활용하여 굉장히 편리하게 만들 수 있다.
@Getter @NoArgsConstructor(access = AccessLevel.PUBLIC) @Entity @Table public class Role implements GrantedAuthority { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String name; @JsonIgnore @ManyToOne @JoinColumn(name = "userId", nullable = false) private User user; @JsonIgnore @OneToMany(mappedBy = "role", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private Collection<RolePermission> privileges; @JsonIgnore @Override public String getAuthority() { return name; } @Builder public Role(String name, User user, Collection<RolePermission> privileges) { this.name = name; this.user = user; this.privileges = privileges; } public static class ROLES { public static final String USER = "ROLE_USER"; public static final String ADMIN = "ROLE_ADMIN"; } }
@NoArgsConstructor(access = AccessLevel.PUBLIC) @Entity @Table(name = "ROLE_PERMISSION") public class RolePermission implements GrantedAuthority { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String permission; @ManyToOne @JoinColumn(name = "roleId", nullable = false) private Role role; @Override public String getAuthority() { return permission; } @Builder public Role(String name, Role role) { this.permission = name; this.role = role; } public static class PERMISSIONS { public static final String USER = "USER_PRIVILEGE"; public static final String ADMIN = "ADMIN_PRIVILEGE"; } }
위 코드와 같이 사용자의 권한을 처리하기 위한 엔터티를 구성하였다. Role 엔터티는 해당 역할이 어떤 역할인지를 정의한 테이블이고, RolePermission은 해당 역할이 어떤 권한을 가지고 있는지 권한을 저장하는 테이블이다.
각 역할은 여러 개의 권한을 가질 수 있다고 가정했을 때, Role과 RolePermission 두 엔터티의 관계는 1:N이어야 한다. 만약 iBatis, JDBC를 사용한다면 이를 SQL 코드를 통해 직접 정의해주어야 하지만, JPA에서는 위와 같이 애너테이션을 통해 정의를 해주고 있다.
코드르 보면 알 수 있듯이, 이 부분 역시 쉽지는 않다. SQL 코드를 건드리지 않을 뿐, Fetch나 Cascade 등 애플리케이션에 따라서 로딩 기법이나 엔터티 변경 전략을 설정해주어야 한다. 이런 점을 볼 때 JPA에서 제공하는 다양한 옵션들에 대한 학습이 필요하다.
마무리
정리
여기까지 간단하게 JPA, Hibernate, Spring Data JPA를 살펴보았다. 어떤 애플리케이션을 개발하던지 간에 DB를 사용하는 경우는 언제든지 생길 수 있고, 이러한 애플리케이션을 구현하기 위해 어떤 디펜던시를 사용해야 하는지에 대한 고민은 개발자의 몫이다. 따라서, 좋은 애플리케이션을 개발하기 위해서는 사용하는 디펜던시의 특징을 잘 알고 쓰는 것이 매우 중요하다.
JPA는 객체지향 프로그래밍 언어에서 관계형 데이터베이스를 쉽게 짜기 위한 구조일 뿐이다. 개발자가 직접 SQL 코드만 작성하지 않을 뿐이지, 실제 내부에서는 JDBC API가 동작하고 있다. 따라서, 성능에 대한 이슈, 코드의 가독성 등 장단점이 존재하며, 반드시 JPA를 사용하는 것이 정답은 아니다.
728x90'Framework & Library > Spring Boot' 카테고리의 다른 글
Spring Boot - 프로젝트 구조 알아보기 (0) 2022.01.29 Spring Boot - JPA의 영속성 컨텍스트 (0) 2021.12.02 Spring Boot - Transaction(트랜잭션) (0) 2021.11.29 Spring Boot - Filter (0) 2021.11.28 Spring Boot - AOP (0) 2021.09.23