`Spring Data JPA`

Spring Data JPA는 Spring Data 패밀리의 일부입니다. Spring Data는 비관계형 데이터베이스, 맵-리듀스 프레임워크, 클라우드 서비스뿐만 아니라 고급 관계형 데이터베이스 지원과 같은 새로운 데이터 액세스 방식을 사용하는 Spring 기반 애플리케이션을 생성하는 데 도움을 줍니다. 이 문서에서는 Spring Data JPA에 대해 논의할 것입니다. 또한 Spring Data JPA 예제 애플리케이션을 살펴보겠습니다.

Spring Data JPA

Spring Data JPA가 제공하는 일부 훌륭한 기능은 다음과 같습니다:

  1. Spring과 JPA로 생성된 리포지토리의 생성 및 지원
  2. QueryDSL 및 JPA 쿼리 지원
  3. 도메인 클래스의 감사 기능
  4. 일괄 로딩, 정렬, 동적 쿼리 지원
  5. 엔티티에 대한 XML 매핑 지원
  6. CrudRepository를 사용하여 일반 CRUD 작업의 코드 크기를 줄임

Spring Data JPA를 언제 사용해야 하나요?

I would say that if you need to quickly create a JPA-based repository layer that is mainly for CRUD operations, and you do not want to create abstract DAO, implementing interfaces, Spring Data JPA is a good choice.

Spring Data JPA 예제

저희 Spring Data JPA 예제에서는 RESTful 웹 서비스를 만들어서 Postgresql 데이터베이스에 연결합니다. 기본적인 CRUD 작업을 구현하고 이미 만들어진 샘플 데이터에 작업을 수행할 것입니다.

Spring JPA 예제 샘플 데이터

아래 쿼리를 사용하여 Postgresql 데이터베이스에 테이블을 생성하고 몇 가지 테스트 데이터를 추가합니다.

create table people (
id serial not null primary key,
first_name varchar(20) not null,
last_name varchar(20) not null,
age integer not null
);

insert into people (id, first_name, last_name, age) values
(1, 'Vlad', 'Boyarskiy', 21),
(2,'Oksi', ' Bahatskaya', 30),
(3,'Vadim', ' Vadimich', 32);

Spring Data JPA 메이븐 프로젝트 구조

아래 이미지는 최종적인 Spring JPA 프로젝트 구조를 보여줍니다. 나중에 각 구성 요소를 자세히 살펴보겠습니다.

Spring Data JPA 메이븐 종속성

Spring Data JPA 예제 프로젝트에 다음 종속성을 추가해야 합니다.

  1. postgresql: Postgresql 자바 드라이버.
  2. spring-core, spring-context: Spring Framework 핵심 종속성.
  3. spring-webmvc, jackson-databind: Spring REST 애플리케이션을 위해.
  4. spring-data-jpa, hibernate-entitymanager: Spring Data JPA와 Hibernate 지원을 위해.

아래는 최종 pom.xml 빌드 파일의 내용입니다.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.journaldev</groupId>
	<artifactId>springData</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>Spring Data JPA Maven Webapp</name>
	<url>https://maven.apache.org</url>
	<properties>
		<spring.framework>4.3.0.RELEASE</spring.framework>
		<postgres.version>42.1.4</postgres.version>
		<serializer.version>2.8.1</serializer.version>
		<spring.data>1.3.4.RELEASE</spring.data>
		<hibernate.manager>4.2.5.Final</hibernate.manager>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.framework}</version>
		</dependency>
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<version>${postgres.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.framework}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.framework}</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jpa</artifactId>
			<version>${spring.data}</version>
		</dependency>

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.manager}</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${serializer.version}</version>
		</dependency>
	</dependencies>

	<build>
		<finalName>${project.artifactId}</finalName>
	</build>
</project>

Spring 구성 클래스

package com.journaldev.spring.config;

import java.util.Properties;

import javax.sql.DataSource;

import org.hibernate.ejb.HibernatePersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.journaldev.spring.repository")
@PropertySource("classpath:database.properties")
public class DataConfig {

	private final String PROPERTY_DRIVER = "driver";
	private final String PROPERTY_URL = "url";
	private final String PROPERTY_USERNAME = "user";
	private final String PROPERTY_PASSWORD = "password";
	private final String PROPERTY_SHOW_SQL = "hibernate.show_sql";
	private final String PROPERTY_DIALECT = "hibernate.dialect";

	@Autowired
	Environment environment;

	@Bean
	LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		LocalContainerEntityManagerFactoryBean lfb = new LocalContainerEntityManagerFactoryBean();
		lfb.setDataSource(dataSource());
		lfb.setPersistenceProviderClass(HibernatePersistence.class);
		lfb.setPackagesToScan("com.journaldev.spring.model");
		lfb.setJpaProperties(hibernateProps());
		return lfb;
	}

	@Bean
	DataSource dataSource() {
		DriverManagerDataSource ds = new DriverManagerDataSource();
		ds.setUrl(environment.getProperty(PROPERTY_URL));
		ds.setUsername(environment.getProperty(PROPERTY_USERNAME));
		ds.setPassword(environment.getProperty(PROPERTY_PASSWORD));
		ds.setDriverClassName(environment.getProperty(PROPERTY_DRIVER));
		return ds;
	}

	Properties hibernateProps() {
		Properties properties = new Properties();
		properties.setProperty(PROPERTY_DIALECT, environment.getProperty(PROPERTY_DIALECT));
		properties.setProperty(PROPERTY_SHOW_SQL, environment.getProperty(PROPERTY_SHOW_SQL));
		return properties;
	}

	@Bean
	JpaTransactionManager transactionManager() {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
		return transactionManager;
	}
}
  • @Configuration: 이 스프링 어노테이션은 구성 클래스임을 나타냅니다.

  • @EnableTransactionManagement: 이 어노테이션은 트랜잭션 관리를 사용할 수 있도록 합니다.

  • @EnableJpaRepositories("com.journaldev.spring.repository"): 리포지토리 클래스가 있는 위치를 지정합니다.

  • @PropertySource("classpath:database.properties"): 클래스 패스에 속성 파일이 있는 것을 나타냅니다. 이 파일의 값은 환경 변수에 주입됩니다. database.properties 파일의 내용은 아래에 표시되어 있습니다.

    driver=org.postgresql.Driver
    url=jdbc:postgresql://127.0.0.1:5432/postgres
    user=postgres
    password=postgres
    
    hibernate.dialect=org.hibernate.dialect.PostgreSQL82Dialect
    hibernate.show_sql=true
    
  • Spring Data를 사용하기 위해서는 먼저 DataSource 빈을 구성해야 합니다. 그런 다음 LocalContainerEntityManagerFactoryBean 빈을 구성해야 합니다. 이 빈은 엔티티를 제어하기 위해 필요합니다. 이 빈에서는 영속성 제공자인 HibernatePersistence를 지정해야 합니다.

  • 다음 단계는 트랜잭션 관리를 위한 빈을 구성하는 것입니다. 예제에서는 JpaTransactionManager를 사용합니다. 트랜잭션 관리자를 구성하지 않으면 @Transactional 주석을 사용할 수 없습니다..

AppInitializerWebConfig 클래스는 web.xml 파일을 사용하지 않고 애플리케이션을 웹 애플리케이션으로 구성하기 위해 사용됩니다.

모델 클래스

package com.journaldev.spring.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "people")
public class Person {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@Column(name = "age")
	private Integer age;
	@Column(name = "first_name")
	private String firstName;
	@Column(name = "last_name")
	private String lastName;

	public Person() {
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return "Person{" + "id=" + id + ", age=" + age + ", firstName='" + firstName + '\'' + ", lastName='" + lastName
				+ '\'' + '}';
	}
}

여기에는 몇 가지 새로운 주석이 있습니다. 이에 대해 자세히 이야기해보겠습니다.

  • @Entity: 이 주석은 엔티티 매니저가 이 클래스를 사용하고 컨텍스트에 넣도록 허용합니다.
  • @Table(name = “people”): 클래스를 데이터베이스의 테이블과 연결합니다.
  • @Id: 이 필드가 주키임을 나타냅니다.
  • @GeneratedValue(strategy = GenerationType.IDENTITY): 주 키를 생성하는 전략을 정의합니다.
  • @Column(name = "age"): 이 필드가 연결될 데이터베이스의 열을 나타냅니다.

Spring Data JPA Repository

다음 단계는 JPA 리포지토리를 만드는 것입니다.

package com.journaldev.spring.repository;

import org.springframework.data.repository.CrudRepository;

import com.journaldev.spring.model.Person;

import java.util.List;

public interface PersonRepository<P> extends CrudRepository<Person, Long> {
    List<Person> findByFirstName(String firstName);
}

CrudRepository를 상속함으로써 여러 메소드를 구현하지 않고도 호출할 수 있습니다. 일부 메소드는 다음과 같습니다:

  • save
  • findOne
  • exists
  • findAll
  • count
  • delete
  • deleteAll

또한 우리만의 메소드를 정의할 수도 있습니다. 이러한 메소드 이름은 “find”, “order”와 변수 이름을 사용해야 합니다. Spring Data JPA 개발자는 필요한 대부분의 옵션을 고려하려고 노력했습니다. 우리의 예제에서 findByFirstName(String firstName) 메소드는 first_name 필드가 firstName과 같은 테이블의 모든 항목을 반환합니다. 이것은 Spring Data JPA의 가장 중요한 기능 중 하나이며 많은 프로젝트에서 이미 사용 중인 이러한 Spring 메소드는 많은 보일러 플레이트 코드를 줄여주기 때문에 오류 가능성도 적습니다.

스프링 서비스 클래스

이제 스프링 데이터 JPA 코드가 준비되었으므로, 다음 단계는 서비스 클래스를 생성하고 데이터베이스 테이블과 작업할 메소드를 정의하는 것입니다.

package com.journaldev.spring.services;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.journaldev.spring.model.Person;
import com.journaldev.spring.repository.PersonRepository;

@Service
public class PersonService {

	@Autowired
	PersonRepository<Person> personRepository;

	@Transactional
	public List<Person> getAllPersons() {
		return (List<Person>) personRepository.findAll();
	}

	@Transactional
	public List<Person> findByName(String name) {
		return personRepository.findByFirstName(name);
	}

	@Transactional
	public Person getById(Long id) {
		return personRepository.findOne(id);
	}

	@Transactional
	public void deletePerson(Long personId) {
		personRepository.delete(personId);
	}

	@Transactional
	public boolean addPerson(Person person) {
		return personRepository.save(person) != null;
	}

	@Transactional
	public boolean updatePerson(Person person) {
		return personRepository.save(person) != null;
	}
}

@Transactional 어노테이션은 해당 메소드가 트랜잭션 내에서 실행될 것임을 나타냅니다. 스프링이 트랜잭션 관리를 처리할 것입니다.

스프링 컨트롤러 클래스

마지막 단계는 컨트롤러 클래스를 생성하여 API를 외부 세계에 노출시키는 것입니다.

package com.journaldev.spring.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.journaldev.spring.model.Person;
import com.journaldev.spring.services.PersonService;

@RestController
public class PersonController {

	@Autowired
	PersonService personService;

	@RequestMapping(value = "/person/{id}", method = RequestMethod.GET)
	public @ResponseBody Person getAllUsers(@PathVariable Long id) {
		return personService.getById(id);
	}

	@RequestMapping(value = "/personByName/{name}", method = RequestMethod.GET)
	public List<Person> getPersoneByName(@PathVariable String name) {
		return personService.findByName(name);
	}

	@RequestMapping(value = "/person", method = RequestMethod.GET)
	public List<Person> getAll() {
		return personService.getAllPersons();
	}

	@RequestMapping(value = "/person/{id}", method = RequestMethod.DELETE)
	public HttpStatus deletePersnone(@PathVariable Long id) {
		personService.deletePerson(id);
		return HttpStatus.NO_CONTENT;
	}

	@RequestMapping(value = "/person", method = RequestMethod.POST)
	public HttpStatus insertPersone(@RequestBody Person person) {
		return personService.addPerson(person) ? HttpStatus.CREATED : HttpStatus.BAD_REQUEST;
	}

	@RequestMapping(value = "/person", method = RequestMethod.PUT)
	public HttpStatus updatePerson(@RequestBody Person person) {
		return personService.updatePerson(person) ? HttpStatus.ACCEPTED : HttpStatus.BAD_REQUEST;
	}
}

스프링 데이터 JPA 테스트

프로젝트를 좋아하는 서블릿 컨테이너(Tomcat 등)에 빌드하고 배포하기만 하면 됩니다. 아래 이미지는 일부 API 호출에 대한 응답을 보여줍니다.

스프링 데이터 JPA 모두 읽기

스프링 데이터 JPA 이름으로 가져오기

스프링 데이터 JPA 생성

스프링 데이터 JPA 업데이트

스프링 데이터 JPA 삭제

Spring Data JPA 예제 튜토리얼은 여기까지입니다. 최종 프로젝트는 아래 링크에서 다운로드할 수 있습니다.

Spring Data JPA 예제 프로젝트 다운로드

참고: 공식 웹사이트

Source:
https://www.digitalocean.com/community/tutorials/spring-data-jpa