HomeBlogGuestbookLab 

JDM's Blog

온갖 테스트 결과가 기록되는 이곳은 JDM's Blog입니다. :3

SpringBoot JPA 예제

SpringBoot에서는 JPA로 데이터를 접근하게끔 유도하고 있습니다. JPA가 무엇인지 알아보고 SpringBoot에서 어떻게 사용하는지도 알아봅시다!

JPA가 뭔가요?(What is JPA?)

Java Persistence API을 줄여서 JPA라고 합니다. 정확한것은 역시 위키피디아에서 찾아봅시다.

The Java Persistence API (JPA) is a Java programming language application programming interface specification that describes the management of relational data in applications using Java Platform, Standard Edition and Java Platform, Enterprise Edition.

Wikipedia - Java Persistence API

JPA는 자바에서 프로그래밍 인터페이스 명세입니다. 이 명세는 자바 플랫폼을 사용하는 어플리케이션 내부 관계형 데이터의 관리를 설명하기 위한 것입니다.

역시 텍스트만 봐서는 잘 모르겠습니다. 실제로 코드를 봐야겠어요.

데모 프로젝트 구성

실제로 코드를 짜봐야 이해가 가겠죠. 데모 프로젝트를 구성해 봅시다. 준비 할 것은 다음과 같습니다.

  • JDK 버전 1.6 이상
  • Spring Tool Suite 2.3.2 Relase 이상

이번 포스팅은 Spring Boot with STS(Spring Tool Suite)를 먼저 보고 오시면 편합니다. 해당 포스팅을 읽으셨다는 가정하에 밑의 내용을 진행 합니다.

프로젝트 생성

sts에서 New Spring Starter Project를 만듭시다. 구성내용은 다음처럼 했습니다.

Name : jpa
Type : Gradle Project
Packaging : Jar
Java Version : 1.7
Language : Java
Boot Version : 1.2.3
Group : kr.jdm
Artifact : jpa
Version : 0.0.1-SNAPSHOT
Description : Demo project for Spring Boot
Package : jpa
Dependencies : H2, JPA

위처럼 구성한 프로젝트의 build.gradle 파일은 다음과 같습니다.

build.gradle
buildscript {
   ext {
       springBootVersion = '1.2.3.RELEASE'
   }
   repositories {
       mavenCentral()
   }
   dependencies {
       classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 
       classpath("io.spring.gradle:dependency-management-plugin:0.4.1.RELEASE")
   }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea' 
apply plugin: 'spring-boot'
apply plugin: 'io.spring.dependency-management'

jar {
   baseName = 'jpa'
   version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.7
targetCompatibility = 1.7

repositories {
   mavenCentral()
}


dependencies {
   compile("org.springframework.boot:spring-boot-starter-data-jpa")
   runtime("com.h2database:h2")
   testCompile("org.springframework.boot:spring-boot-starter-test") 
}


eclipse {
   classpath {
        containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
        containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7'
   }
}

task wrapper(type: Wrapper) {
   gradleVersion = '2.3'
}

본 포스팅은 H2 데이터베이스를 기반으로 작성합니다. 실제 코드를 돌리기 위해 로컬에 RDBMS를 설치하기 보단 간편하게 프로젝트를 구성하기 위함입니다.

Entity

JPA에서 Entity라는 것은 데이터베이스에 저장하기 위해서 유저가 정의한 클래스입니다. 일반적으로 RDBMS에서 Table의 정의 같은 겁니다. Table의 이름이나 컬럼들에 대한 정보를 가지죠.

그렇다면 기본 패키지에 JPA Entity를 담당할 클래스를 만들어 봅시다.

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

@Entity
public class Member {
	
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private long id;
	
	@Column
	private String name;
	
	@Column
	private int age;
	
	public Member() {}
	
	public Member(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public long getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return "[" + id + "] name = " + name + ", age = " + age;
	}
}

Entity 어노테이션을 클래스의 선언 부분에 넣어주고 내부 멤버 변수는 각 컬럼에 대응하면 됩니다. 여기서 주의할건 기본 생성자는 꼭 넣어주세요.

Member 클래스에는 아이디와 이름, 나이를 정의했습니다.

@Id 어노테이션

일반적으로 키(primary key)를 가지는 변수에 선언합니다. @GeneratedValue 어노테이션은 해당 Id 값을 어떻게 자동으로 생성할지 전략을 선택할 수 있습니다. 여기서 선택한 전략은 "AUTO"입니다.

@Table 어노테이션

참고로 @Table을 이용하면 별도의 이름을 가진 데이터베이스 테이블과 매핑할 수 있습니다. 기본적으로 @Entity로 선언된 클래스의 이름은 실제 데이터베이스의 테이블 명과 일치하는 것을 매핑합니다. 따라서 @Entity의 클래스명과 데이터베이스의 테이블명이 다르면 @Table(name="XXX") 같은 형식으로 작성하면 이름을 다르게 해서 매핑 가능합니다.

@Column 어노테이션

@Column 선언은 꼭 필요한 것은 아닙니다. 하지만 그것과 별개로 @Column에서 지정한 멤버 변수와 데이터베이스의 컬럼명을 다르게 주고 싶다면 @Column(name="XXX") 같은 형식으로 작성해주면 됩니다. 그렇지 않으면 기본적으로 멤버 변수명과 일치하는 데이터베이스의 컬럼을 매핑합니다.

Repository

Entity 클래스를 구성했다면 이번엔 Repository 인터페이스를 만들어야 합니다. 스프링 프레임워크에서는 Entity의 기본적인 삽입, 조회, 수정, 삭제가 가능하도록 CrudRepository라는 인터페이스를 만들어놨습니다.

import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

public interface MemberRepository extends CrudRepository<Member, Long> {

	List<Member> findByNameAndAgeLessThan(String name, int age);
	
	@Query("select t from Member t where name=:name and age < :age")
	List<Member> findByNameAndAgeLessThanSQL(@Param("name") String name, @Param("age") int age);
	
	List<Member> findByNameAndAgeLessThanOrderByAgeDesc(String name, int age);
	
}

위의 코드는 실제로 Member Entity를 이용하기 위한 Repository 클래스입니다. 기본적인 메소드 외에도 추가적인 메소드를 지정할 수 있습니다. 메소드 이름을 기반(Query Method)으로 해서 만들어도 되고 @Query를 이용해 기존의 SQL처럼 만들어도 됩니다.

findByNameAndAgeLessThan 메소드와 findByNameAndAgeLessThanSQL 메소드는 같은 결과를 출력하지만 전자의 메소드는 메소드 이름을 기반으로 한것이고 후자의 메소드는 @Query 어노테이션을 기반으로 해서 만든것입니다.

메소드 이름 기반으로 해서 만들면 추후에 사용할 때 메소드 이름만으로도 어떤 쿼리인지 알 수 있다는 장점이 있습니다. 반대로 @Query 어노테이션으로 만든 메소드는 기존의 소스를 컨버팅하는 경우 유용하게 사용할 수 있겠죠.

@Query

다만 @Query 어노테이션으로 쿼리를 만들 때에 from 절에 들어가는 테이블은 Entity로 지정된 클래스명입니다. 해당 포스트에서는 Member입니다. 그리고 where절에서는 Member 클래스 내의 멤버 변수를 통해 조건을 걸 수 있습니다.

메소드 이름 기반 작성법

해당 부분은 최신 Spring 문서를 통해 확인합시다. 링크는 Query creation입니다. 2015.05.21

Application 작성

Entity 및 Repository가 준비 되었다면 실제로 사용을 해볼 때 입니다. @SpringBootApplication 어노테이션이 있는 클래스에서 실제로 사용해봅시다.

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication implements CommandLineRunner {

	@Autowired
	MemberRepository memberRepository;
	
   public static void main(String[] args) {
       SpringApplication.run(JpaApplication.class, args);
   }

	@Override
	public void run(String... args) throws Exception {

		memberRepository.save(new Member("a", 10));
		memberRepository.save(new Member("b", 15));
		memberRepository.save(new Member("c", 10));
		memberRepository.save(new Member("a", 5));
		
		Iterable<Member> list1 = memberRepository.findAll();
		
		System.out.println("findAll() Method.");
		for( Member m : list1){
			System.out.println(m.toString());
		}
		
		System.out.println("findByNameAndAgeLessThan() Method.");
		List<Member> list2 = memberRepository.findByNameAndAgeLessThan("a", 10);
		for( Member m : list2){
			System.out.println(m.toString());
		}
		
		System.out.println("findByNameAndAgeLessThanSQL() Method.");
		List<Member> list3 = memberRepository.findByNameAndAgeLessThanSQL("a", 10);
		for( Member m : list3){
			System.out.println(m.toString());
		}
		
		System.out.println("findByNameAndAgeLessThanSQL() Method.");
		List<Member> list4 = memberRepository.findByNameAndAgeLessThanOrderByAgeDesc("a", 15);
		for( Member m : list4){
			System.out.println(m.toString());
		}

		memberRepository.deleteAll();
	}
}

아래는 위의 코드를 실행시킨 콘솔 화면 중 일부입니다.

findAll() Method.
[1] name = a, age = 10
[2] name = b, age = 15
[3] name = c, age = 10
[4] name = a, age = 5
findByNameAndAgeLessThan() Method.
[4] name = a, age = 5
findByNameAndAgeLessThanSQL() Method.
[4] name = a, age = 5
findByNameAndAgeLessThanSQL() Method.
[1] name = a, age = 10
[4] name = a, age = 5

기존에 제공되는 메소드는 기본적인 기능들입니다. MemberRepository는 새로운 로우(Row) 삽입, 삭제, 수정, 조회를 제공합니다. 그리고 아까전 덧붙였던 메소드들도 사용할 수 있죠.

눈 여겨 볼것은 메소드 이름 기반(Query Method)의 메소드들입니다. 어떤 것을 하고자 하는지 명확합니다. 메소드 이름이 길어지긴 하지만 명확하다는것은 장점으로 보이네요. 그리고 메소드 이름 기반으로 작성할 때는 특정한 규칙에 맞춰서 작성해야 하기 때문에 다른 사람이 작성한 것도 파악하기 쉬울 것 같습니다.

의문점

MemberRepository 인터페이스에 별다른 설정이나 어노테이션이 없는데 JpaApplication 클래스에서 MemberRepository 인터페이스를 @Autowired 어노테이션을 통해 주입을 받고 있습니다. 해당 사항에 의문이 생겨서 찾아 봤더니 다음과 같은 내용을 찾았습니다. 2015.05.21

Spring Data repositories usually extend from the Repository or CrudRepository interfaces. If you are using auto-configuration, repositories will be searched from the package containing your main configuration class (the one annotated with @EnableAutoConfiguration or @SpringBootApplication) down.

Spring Data JPA Repositories 2015.05.21

@SpringBootApplication 어노테이션이 있는 패키지부터 검색을 한다고 되어 있습니다. 해당 어노테이션이 존재하는 패키지부터 하위 패키지에 있는 Repository들이 검색되어 주입 되는것으로 보이네요. 2015.05.21

만약, @SpringBootApplication 어노테이션과 다른 패키지에 Repository 및 Entity들이 있다면 아래와 같은 어노테이션을 통해 정의 해주면 됩니다. 2015.05.21

@EntityScan(basePackages="another.package.name")
@EnableJpaRepositories(basePackages="another.package.name")
@SpringBootApplication
public class JpaApplication implements CommandLineRunner {
...
}