<< Back
blog 스프링프레임워크

스프링프레임워크 :: 5.스프링부트 JPA 프로그래밍

이미지출처: pexels.com, Brett Sayles



JPA 개요

JPA(Java Persistence API)는 자바 영속성 API 즉 영속성 프로그래밍을 위한 라이브러리로 이해할 수 있습니다. 영속성이라는 용어 자체는 프로그램이 종료되어도 사라지지 않는 데이터의 특성을 의미 합니다. 일반적으로 데이터베이스를 통해 영속성 구현이 가능합니다.

자바 데이터베이스 프로그래밍의 문제점

자바에서는 처음에 JDBC(Java Database Connectivity)를 만들어 표준 규격을 정하고 구현은 DB벤더에 맡기는 방식으로 영속성 관리를 지원 했습니다. 이는 SQL 기반의 개발이었으며 지금도 가장 기본적인 데이테이베이스 연동 프로그램을 구현하는 방법입니다.

그러나 JDBC 방식은 관계형 데이터베이스와의 연동을 위해 자바 객체를 SQL 형태로 풀어서 서술해야 하며 DB로 부터 가지고온 데이터의 경우 다시 자바 객체로 변환하는 등의 작업을 거쳐야 하는 문제가 있습니다. 특히 데이터베이스 연동 프로그램의 규모가 커지고 복잡해질수록 관련된 자바객체와 쿼리등을 관리하는 것 역시 점점 어려워집니다.

웹 프로그램이라고 가정하고 사용자 입력을 저장하는 경우 다음과 같은 과정을 거치게 됩니다.

ORM과 JPA

이러한 문제 해결을 위해 등장한 것이 ORM(Object Relational Mapping) 기술 입니다. 프로그램의 복잡도를 줄이고 자바 객체와 쿼리를 분리할 수 있으며 트랜잭션 처리나 기타 데이터베이스 관련 작업들을 좀 더 편리하게 처리할 수 있는 방법으로 Hibernate, Mybatis 등이 있습니다.

Java EE에 포함된 EJB의 Entity Bean 역시 같은 목적에서 나온것으로 여러 과정을 거쳐 지금은 자바의 모든 환경에서 사용할 수 있는 독립적인 API 규격인 JPA가 되었습니다.

Mybatis 는 SQL 을 프로그램에서 분리해서 처리할 수 있는 방법으로 SQL Mapper 라고도 합니다. 데이터베이스의 구조상 복잡한 쿼리가 많은 경우 선호하는 방법이며 지금도 많은 곳에서 사용하고 있습니다.

JPAORM에 대한 자바 API 규격이며 Hibernate 는 JPA를 구현한 구현체 입니다. Hibernate 이외에도 EcipseLink, DataNucleus, OpenJPA, TopLink 등이 있습니다. JPA는 Hibernate 로 부터 많은 부분을 수용하고 있습니다.

JPA 장점

JPA 단점

JPA 쿼리 구현 방법

JPA에서 데이터베이스 쿼리를 구현하는 방법은 다음의 4가지가 있습니다.

Named Query Method

가장 기본적인 방법으로 개발자가 별도의 쿼리를 작성하지 않고 메서드 이름을 조합해 원하는 쿼리를 실행하는 방식 입니다.

스프링에서는 리파지토리 인터페이스에 다음과 같이 메서드 선언부만 추가하고 사용하면 됩니다.

Named Query Method example

List<City> findByPopulationGreaterThan(int p);

JPQL

JPA 에서는 개발자가 직접 쿼리를 작성하지 않고 제공되는 이름조합 기반의 메서드를 통해 쿼리구현이 가능하지만 복잡한 쿼리는 처리하기 어렵습니다. 따라서 예외적인 쿼리들을 처리할 수 있는 방법이 나오게 되었는데 그것이 JPQL 입니다.

기본적으로 SQL과 거의 유사하며 JPA 안에서 사용할 수 있는 형태로 확장된 구조 입니다. 스프링에서는 @Query 애너테이션을 리파지토리 인터페이스의 메서드 선언 부분에 작성하면 됩니다.

JPQL example

@Query("SELECT c FROM City c WHERE c.population > :p")
public List<City> findCity(@Param("p") int p);

Criteria

그러나 JPQL 은 문자열로 작성되기 때문에 SQL과 마찬가지의 문제가 있고 구조적으로도 좋지 못하기 때문에 메서드 호출로 쿼리를 생성하는 Criteria 쿼리가 나오게 되었습니다.

타입 지원을 통해 개발도구의 문법체크와 코드 지원을 받을 수 있어 매우 편리합니다.

Criteria example

List<City> find City(int p) {
	CriteriaQuery<City> q = cb.createQuery(City.class);
	Root<City> c = q.from(City.class);
	q.select(c);
	q.where(cb.greaternThan(q.get("population"), p));

	List<City> cityList = q.getResultList();
	return cityList;
}

QueryDSL

Criteria 쿼리는 JPA에 포함된 표준 규격이지만 코드가 다소 복잡한 문제가 있어 이를 해결하기 위해 개발된 오픈소스 라이브러리 입니다.

Criteria 와 유사한 방식의 코드를 지원하지만 코드가 단순하고 가독성이 높다는 장점이 있습니다. 다만 일부 설정이 추가 되어야 하고 자동생성되는 코드들도 있어 관리가 필요 합니다.

QueryDSL example

List<City> find City(int p) {
	JPAQuery query = new JPAQuery(em); 
	QCity qcity = new QCity("city"); 
	List<City> cityList = query.from(qcity) 
				.where(qcity.population.goe(p)) 
				.list(qcity); 
				return cityList;
}

Spring Data JPA에서는 이들 방법을 모두 지원하고 있으며 기본적인 JPA 구현이외에 필요한 복잡한 쿼리들을 원하는 방식으로 처리할 수 있습니다. 물론 가장 원초적이라고 할 수 있는 JDBC 혹은 스프링 JDBC Template 등을 사용할수도 있습니다.


Spring Boot JPA 예제 셋업

Spring Data JPA

스프링프레임워크의 서브 프로젝트중 하나로 JPA를 스프링프레임워크에서 손쉽게 사용할 수 있도록 도와줍니다.

반복되는 코드 구조를 단순화 하거나 자동화 할 수 있으며 서비스나 핵심 기능에 집중할 수 있습니다. 내부적으로 기본 JPA 구현체는 Hibernate 를 사용하고 있습니다.

프로젝트 설정

기존 예제의 프로젝트를 계속 사용하기 때문에 별도의 프로젝트 생성은 필요 없습니다. 다만 pom.xml 에 다음과 같이 필요한 라이브러리들을 추가합니다.

pom.xml

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
</dependency>		
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<optional>true</optional>
</dependency>			

패키지 생성 및 코드 복사

com.example.jpa 패키지를 생성하고 ProductManager.java 를 제외한 나머지 소스를 복사해 옵니다.

JpaRestController.java
JpaWebController.java
Product.java
SpringStudyJpaApplication.java

데이터베이스

JPA 학습을 위해서는 당연히 데이터베이스가 필요합니다. MySQL, MariaDB, Oracle 등 모든 관계형 데이터베이스를 사용할 수 있습니다. 여기서는 별도의 설치가 필요없고 바로 사용이 가능한 내장형(Embedded) 데이터베이스인 H2 를 사용합니다.

H2 데이터베이스는 단독 실행을 통해 서버 모드로 실행할 수도 있고 메모리 DB나 파일로 부터 실행하는 방법등 다양한 운영이 가능합니다. 여기서는 임베디드 모드를 사용할 것이기 때문에 pom.xml 설정만으로 추가적으로 DB설치는 필요 없습니다.

만일 H2 데이터베이스를 서버 모드로 실행하려면 공통기초->개발환경구축하기 를 참고하기 바랍니다.

JPA 관련 환경 설정

application.propertis 에 데이터베이스접속과 JPA 관련 설정들을 추가 합니다.

# H2 DB console

웹 기반의 H2 데이터베이스 관리 도구 실행을 지정하는 부분 입니다. localhost:8080/h2-console 로 접속할 수 있도록 설정 합니다.

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# H2 DB Connection

H2 데이터베이스를 사용하기 위한 JDBC 설정 입니다.

spring.datasource.url=jdbc:h2:file:./data/demodb
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver

# JPA 설정은 JPA와 관련한 테이블 자동생성 및 데이터 스크립트 실행 설정 입니다. JPA와 관련해서는 많은 설정 항목이 있으며 추후 진행하면서 다루게 됩니다.

spring.datasource.initialization-mode=embedded
spring.jpa.hibernate.ddl-auto=update

spring.datasource.initialization-mode는 테이블 및 데이터 자동 초기화 설정 입니다. schema.sql, data.sql(data-h2.sql)등의 파일이 있으면 자동으로 해당 파일의 내용으로 데이터베이스 초기화 작업을 수행 합니다.

spring.jpa.hibernate.ddl-auto은 테이블 자동 생성 설정 입니다. 옵션에 따라 다음과 같이 동작 합니다.


Spring Boot JPA 예제 구현

도메인 클래스

Entity 클래스 라고도 합니다. JPA에서 테이블 구조를 대신하는 객체로 스프링에서는 @Entity 애너테이션 설정으로 생성할 수 있습니다. 일반적인 POJO 구조이고 이전 예제와 달리 초기 데이터 생성이 필요 없으므로 생성자는 없어도 됩니다.

Product.java

package com.example.jpa;

@Entity
public class Product {
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private int id;
	private String name;
	private int price;
	
	...getter/setter 생략	
}

@Entity

@Id

@GeneratedValue

@Column

JPA 리파지토리

DAO 클래스에 해당하는것으로 직접 클래스를 구현하는 것이 아니라 인터페이스를 생성하면 자동으로 구현된 객체가 참조 되는 형식 입니다. 만일 QueryDSL 등을 사용하거나 별도 쿼리 구현이 필요한 경우 해당 인터페이스를 직접 구현하는 클래스를 만들어 사용할수도 있습니다.

클래스와 애너테이션의 패키지는 org.springframework.data 에 있는 것으로 import 하기 바랍니다.

놀랍게도 데이터베이스 처리와 관련된 코드는 다음의 코드가 전부 입니다. 부분 코드가 아닙니다.

package com.example.jpa;

public interface ProductRepository extends JpaRepository<Product, Integer> {

}

JPA 리파지토리 인터페이스를 만들때 상속할 수 있는 인터페이스들이 있습니다. 대표적으로 다음의 세가지 인터페이스들을 주로 사용하게 됩니다.

만일 이들 인터페이스를 상속받지 않고 모든 인터페이스 메서드를 직접 처리하려면 @Repository 애너테이션을 추가한 다음 필요한 메서드를 선언해 사용해야 합니다.

CrudRepository

가장 기본이 되는 리파지토리로 이름과 같이 기본적인 CRUD 기능을 제공 합니다.

PagingAndSortingRepository

페이지 기능과 정렬 기능이 추가된 리파지토리 입니다. CrudRepository 를 상속받고 있습니다.

JpaRepository

PagingAndSortingRepository 를 상속받고 있습니다. JPQL 등을 사용할 수 있으며 가장 많이 사용되는 인터페이스 입니다.

컨트롤러 설정

이전 강좌에서 만든 상품정보 Rest 및 MVC 컨트롤러를 코드를 복사해서 JPA리파지토리와 연동되도록 수정 합니다. 여기서는 JpaRestController 클래스만 살펴봅니다. MVC 컨트롤러도 동일한 관점에서 수정하면 됩니다.

먼저 리파지토리를 참조하기 위해 오토와이어링을 사용합니다. productRepo 는 스프링에 의해 자동 생성된 ProductRepository 인터페이스 구현 클래스에 대한 참조를 가지게 됩니다.

@Autowired
ProductRepository productRepo;

다음은 상품 목록을 전달하는 url 매핑 처리 입니다. productRepo 의 findAll() 메서드를 호출하고 있습니다.

@GetMapping("/product")
public Iterable<Product> getAll() {		
	return productRepo.findAll();
}

아이디로 특정 상품을 검색하기 위해 findById() 를 사용할 수 있습니다. 이때 Optional 을 리턴하기 때문에 다음과 같이 예외처리를 해주어야 합니다.

@GetMapping("/product/{id}")
public Product getProduct(@PathVariable int id) {
	return productRepo.findById(id).orElseThrow(() -> new RuntimeException("product not found!!"));
}

상품 등록의 경우 save() 메서드를 사용하면 되고 삭제의 경우 deleteById()를 사용하면 됩니다.

@PostMapping("/product")
public String addProduct(@RequestBody Product p) {
	productRepo.save(p);
	return "product added!!";
}
@GetMapping("/product/delete/{id}")
public String delProduct(@PathVariable int id) {
	productRepo.deleteById(id);
	return "product deleted!!";
}

실행 및 테스트

SpringStudyJpaApplication 클래스를 실행하고 web 과 api 에 대해 각각 동작을 테스트 해보도록 합니다.

별도의 테이블 생성과 데이터베이스 코드 구현 없이 모든 기능이 동작하는 것을 확인할 수 있습니다.

데이터베이스에 저장된 값의 확인을 위해서는 H2 console 을 이용하도록 합니다. http://localhost:8080/h2-console 로 접속한 다음 설정 사항을 확인한다음 접속해서 원하는 쿼리를 작성해 조회해 봅니다.

<< Back