본문 바로가기

개발/쉽게 이해하고 사용하는 JPA

JPA 개요와 Spring Boot 개발 환경구성

사용기술

  1. Spring boot 2.0.5.RELEASE
  2. maven
  3. JPA
  4. MyBatis
  5. H2 DBMS
  6. Hikari CP
  7. java 1.8

 

개요

JPA 는 java persistence API 의 약자 이며 java 에서 제공하는 JDBC 와 같은 API spec 을 의미 합니다. JDBC 와 JPA 는 둘다 데이타베이스 와 데이타를 주고 받기 위한 방법을 정의 하였으나 JDBC 는 보다 하위 레벨에서 데이타베이스와 직접 SQL 쿼리를 질의 하는 API 스펙 이며 JPA 는 ORM API 스펙 입니다.

JPA 이전에는 hibernate 와 MyBatis 가 보편적으로 사용 되고 있었지만 JPA 스펙이 발표가 된 이후 보다 쉽게 데이타를 사용 할 수 있기 때문에 JPA 가 주로 사용 되고 있습니다. 하지만 JPA 자체로는 스펙 이며 내부 구현체로서 hibernate 가 사용 되고 있습니다. (물론 다른 JPA 구현체도 있습니다.)

ORM 과 JPA

ORM 이란 무엇 일까요 ? JPA 를 사용하기에 앞서 먼저 ORM 에 대해 알아 보겠습니다. java와 같은 현대언어는 객체지향 설계를 사용하고 있습니다. 어떤 데이타 모델을 의미를 가지는 단위로 추상화를 시켜 재해석한 객체라는 단위로 프로그래밍을 한다는 이야기 입니다.

내가 그의 이름을 불러주기 전에는 
그는 다만 
하나의 몸짓에 지나지 않았다. 

내가 그의 이름을 불러주었을 때 
그는 나에게로 와서 
꽃이 되었다. 

김춘수 -꽃-

개인적으로 생각할때는 객체 지향을 설명하기에 이보다 더 적합한 내용은 없을 것 같습니다. 핵심은 어떤 대상을 내가 의미를 가지는 단위로 재해석 하여 모델링을 한다는 것 입니다.

하지만 데이타를 다루기 위해 사용하는 데이타베이스는 관계형 입니다. 데이타 모델을 정규화 과정을 거쳐 중복된 데이타를 제거하고 의미를 가지는 단위까지 정의한 테이블을 기본 단위로 데이타를 관리 합니다.

대부분 데이타를 바라보는 관점에서 객체지향 설계나 관계형 설계가 서로 비슷한 입장이긴 하나 분명 데이타를 바라보는 관점이 다르며 이로 인해 생기는 불일치를 객체지향으로 변환해 보자는 것이 ORM 의 기본 컨셉입니다.

또한 데이타베이스에서 데이타를 가져오기 위해서 JDBC API 를 이용해 데이타베이스 마다 거의 비슷하지만 다른 특징을 가지고 있는 SQL 구문 즉 쿼리 질의어를 이용해 직접 데이타를 조작해야 하는 부분을 ORM 에서는 사용하지 않게끔 해줍니다. 이 부분이 중요한 이유는 각 데이타베이스 벤더 마다 가지고 있는 특성을 잘 이해하고 개발을 해야 한다는 어려움이 생기기 때문입니다.  중간에 사용중인 데이타베이스 종류가 변경이 된다거나 동시에 여러 데이타베이스를 사용해야 한다면 개발적인 부분 외에 신경써야 되는 부분이 더 많아지게 되는 것입니다. 

ORM 은 모든 데이타와 로직을 객체로 바라보고 각 데이타베이스의 특징을 고려하지 않아도 데이타를 사용할 수 있는 방법을 제공해 줍니다. 데이타를 오롯이 데이타로만 바라볼 수 있게 된 것입니다.

멘토 개발자분이랑 ORM 즉 JPA 에 대해서 이야기 하면서 왜 한국에서는 ORM 이 인기가 없을까에 대해서 이야기해본적이 있었습니다. 그때 대답이 참 인상 깊었는데 한국 특유의 보수적인 개발문화가 있는 환경적인 부분도 분명히 영향이 있겠으나 한국 개발자의 특유의 잡학스런 기술 스택으로 인해 SQL 에 대한 학습 비용이 별달리 필요 없기 때문이지 않을까 하는 이야기를 들은 적이 있었습니다.

MyBatis vs JPA

MyBatis 와 같은 SQL template 라이브러리를 이용하여 프로젝트를 진행할때 만약 SQL을 사용하는게 익숙하다면 무엇과도 비교할수 없을 정도의 빠른 개발 속도와 단순화되는 비지니스 로직 (대부분을 쿼리에서 해결하게 되기 때문에) 등이 장점이라고 할 수 있습니다. 필요하다면 데이타베이스에 종속적인 PL/SQL 과 같은 언어를 이용해 비지니스 로직 전체를 데이타베이스로 개발할 수도 있습니다.

하지만 SQL 구문을 사용해 데이타를 관리함으로서 데이타베이스에 종속적인 SQL 구문 때문에 프로그램이 유연하지 않게 되는 부분이 가장 큰 문제라고 볼 수 있습니다. 비지니스 로직의 분산 (데이타베이스에서 생성한 함수, 패키지 혹은 PL-SQL 문법) 그리고 변화에 취약한 구조 (컬럼명이 변경되거나 추가될때 변경하기 위해선 일일이 참조하고 있는 쿼리를 찾아 변경해야 되는 구조), 데이타를 격리시킬수 없어 단위 테스트를 쉽게 할 수 없는 문제 등으로 인해 초기 개발 속도는 빠를 수 있지만 점차 시스템의 유지보수 비용이 높아지게 됩니다.

물론 JPA가 이 모든 문제를 해결해 줄 수 없습니다. 하지만 데이타와 로직을 명확하게 분리하여 관리할 수 있게 해주며 저수준 API 를 직접 호출하여 데이타를 사용하지 않고 비지니스 로직에 좀더 집중할 수 있게 해주며 무엇보다 익숙해지면 보다 빠른 개발 속도를 가질 수 있게 됩니다. 하지만 그러기 위해선 JPA 의 특성을 잘 이해해야 합니다. 기본 구조는 매우 단순해 보이나 내부로 들어가게 되면 데이타베이스의 특성을 이해하지 않고선 적정한 속도를 보장할 수 없기 때문입니다.

먼저 JPA 가 할 수 있는 것과 할 수 없는 것을 살펴보겠습니다.

  • JPA 가 잘 할 수 있는 것은 JDBC 와 같은 저수준의 API 로 쿼리를 직접 질의하지 않고 데이타를 객체 단위로 관리 할 수 있다는 점 입니다. 이로 인해 비지니스 로직을 데이타베이스상에 분산되어 개발하지 않아도 되며 데이타와 비지니스 로직을 서로 적절하게 분리 시킬 수 있게 됩니다. 이로 인해 단위 테스트가 용이해진다는 점 역시 JPA 를 사용함으로서 얻는 이점이라고 할 수 있습니다.
  • JPA 가 잘 할 수 없는 것통계 자료를 추출한다거나 배치성 다량의 데이타를 처리한다거나 동적쿼리(JPA 상에서 동적쿼리를 작성하기 위해선 creteria, query dsl 과 같은 추가 기능을 사용해 동적 쿼리를 작성 할 수도 있습니다. ) 를 유연하게 작성 할 수 없다는점 입니다.

이렇게 서로의 역활이 명확하게 다르기 때문에 두가지의 기술이 서로 대체하는 역활이라고 볼 수 없으며 성격에 따라 적절히 분리하여 데이타의 CRUD (Create, Read, Update, Delete) 처리중에 RMyBatis 가 그리고 단순한 R 과 CUDJPA 에서 처리하게끔 같이 구성을 하는 것이 가장 좋습니다. 즉 두가지 기술을 같이 사용해야 한다는 것입니다.

maven 설정

그럼 JPA 를 사용하기 위해 Spring boot 설정을 진행해 보겠습니다. Start Spring https://start.spring.io 에서 설정된 기본 라이브러리 구성에서 필요한 내용을 추가 하겠습니다.

Spring boot 은 2.0.5.RELEASE, java 는 1.8 을 사용하겠습니다.

데이타 테스트를 위해 h2 dbms 를 추가 하였고 개발을 위해 lombok lib 를 추가 하였습니다. 추가로 JPA 와 MyBatis 의 통합 환경을 설정하기 위해 mybatis-spring-boot-starter 를 추가 했습니다. MyBatis 는 Spring boot 에 기본적으로 포함되지 않기 때문에 별도로 추가 구성해야 합니다.

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.5.RELEASE</version>
	<relativePath/>
</parent>
<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	<java.version>1.8</java.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>1.3.2</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
		<scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>com.h2database</groupId>
		<artifactId>h2</artifactId>
		<scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

 

데이타소스 설정

h2 데이타베이스에 대한 데이타소스 설정을 진행하겠습니다. 데이타소스 종류는 hikari cp 를 사용하며 Spring boot 2.0 부터는 apache common cp 와 같이 기본으로 사용되는 데이타소스 입니다.

spring:
  datasource:
    hikari:
      driver-class-name: org.h2.Driver
      jdbc-url: jdbc:h2:file:./db;AUTO_SERVER=TRUE;Mode=MYSQL
      username: sa
      password:
      pool-name: hikari-cp
      minimum-idle: 10
      maximum-pool-size: 30
      connection-test-query: SELECT 1

 

먼저 데이타베이스 접속 정보를 추가합니다. h2 데이타베이스를 영속적으로 사용하기 위해 db 라는 이름으로 데이타베이스 file 을 생성하고 AUTO_SERVER=TRUE 설정을 추가하여 여러 프로세스에서 동시에 접근이 가능하도록 설정해 둡니다. 그리고 사용하는 데이타베이스 특성을 MYSQL 과 같은 형태로 사용 되게끔 설정을 추가합니다.

jdbc:h2:file:./db;AUTO_SERVER=TRUE;Mode=MYSQL

그리고 데이타소스의 최소 커낵션 갯수와 최대 커낵션 갯수를 지정합니다.

minimum-idle: 10
maximum-pool-size: 30

데이타소스가 가지고 있는 커낵션이 유효한지를 확인하기 위해 확인 쿼리를 설정합니다. 만약 커낵션이 시간이 지나 끊어져 있는 상태라면 해당 커낵션을 버리고 새로운 커낵션을 맺어 최소 커낵션 갯수를 유지 합니다.

connection-test-query: SELECT 1

보다 데이타소스 설정에 정확한 정보를 확인하고 싶으면 아래 주소를 참고해 주세요.

https://github.com/brettwooldridge/HikariCP

 

JPA 설정

이어서 JPA 관련된 설정을 진행 하겠습니다. 설정은 테스트용으로 진행할 것이기 때문에 기본적인 설정만 진행 하겠습니다.

spring:
  ...
  jpa:
    hibernate.ddl-auto: update
    show-sql: true
  • hibernate.hbm2ddl.auto 는 Spring 이 시작할때 JPA 에 설정된 내용을 기준으로 db table 을 어떤 방식으로 초기화 시킬 것인지를 설정하는 값입니다. 각각 create, create-drop, update, validate, none 이 있습니다.

    JPA 의 가장 강력하면서도 위험한 기능이며 가능하면 개발 시점에서만 사용하고 운영시점에는 none 혹은 validate 만 켜 놓고 운영하는게 안전합니다.

    TIP. 가능하다면 운영시 사용하는 데이타베이스 계정에는 dml 을 통해 데이타 조작만 가능하게 설정하고 스키마를 변경하는 ddl 조작은 별도의 계정으로 분리하여 운영하는 것이 가장 좋습니다. 왜냐하면 배포가 잘못 진행되었을때 테이블 자체가 삭제 혹은 변경 될 수도 있기 때문입니다.

설정설명
create시작될때 모든 table 을 생성 합니다.
create-drop시작될때 모든 table 을 생성 하나 종료될때 생성된 table 을 지웁니다.
update시작될때 JPA Entity 와 실제 table 을 비교하여 변경된 내용을 반영 합니다. 만약 table 이 없다면 새롭게 생성 합니다.
validate시작될때 JPA Entity 와 실제 table 을 비교하여 결과가 상이하면 시작되지 않고 종료 합니다.
none아무런검사를 하지 않습니다.
  • hibernate.show_sql JPA 가 동작될때 sql 구문을 로그 파일에 노출 시킬지 여부를 결정 합니다.

 

MyBatis 설정

앞서 이야기한 것 처럼 MyBatis 는 Spring Boot 기본 설정에 포함되지 않기 때문에 별도의 라이브러리 mybatis-spring-boot-starter 를 추가해 줘야합니다. 또한 MyBatis 설정 역시 spring.xxx 와 같이 spring 하위 설정으로 추가 되는 것이 아니라 별도의 mysql 라는 prefix 를 가지게 됩니다.

mybatis:
  configuration:
    map-underscore-to-camel-case: true
    jdbc-type-for-null: varchar

설정을 하나씩 확인해 보겠습니다.

  • mybatis.configuration.map-underscore-to-camel-case : true 쿼리를 통해 결과 값을 가지고 올때 컬럼명이 underscore 형태로 되어 있는 것을 camel case 로 자동으로 변경해 줍니다. 이는 java 도메인 객체에서 지정한 camel case 속성명으로 자동으로 매칭을 시켜 주는 역할을 하게 됩니다.

    (예: 데이타베이스 컬럼명이 code_name 일때 자동으로 컬럼명을 codeName 으로 변경해 resultType 으로 지정된 java 객체에 반영 됨)

    만약 map-underscore-to-camel-case 가 지정되어 있지 않다면 수동으로 컬럼마다 alias 를 지정해 주던지 mapper xml 에 result map 을 설정해 줘야 합니다.

  • mybatis.configuration.jdbc-type-for-null : varchar 데이타베이스 상에 null 을 만나면 varchar 형으로 캐스팅을 해줍니다. MyBatis 는 null 을 적절치 처리해주지 못하기 때문에 다양한 이유로 에러가 발생되는데 파라미터에 값에 null 이 포함되거나 혹은 반환되는 값에 null 이 포함될때 형식을 찾을 수 없다는 에러가 발생되게 됩니다. 이때 null 을 varchar 형으로 캐스팅하여 에러 없이 null 값 자체를 처리할 수 있게 해줍니다.

 

다음글에서는 실질적으로 JPA의 특성과 사용방법을 알아보겠습니다.