[spring-data-jpa] 기본 예제

소개

spring-data-jpa는 jpa 기반 저장소를 쉽게 구현하도록 만들어준다. 이중에서도 눈에 띄는 특징들은

  • type-safe한 쿼리가 가능하다.
  • 공통된 저장소 기능들(CRUD)을 제공해준다.
  • @Query 어노테이션을 제공한다.

그 외에도 querydsl의 predicate 제공이나 xml 기반의 entity 매핑등의 특징들도 보인다.

예제

먼저 Spring Project로 프로젝트를 생성하고 dependency를 추가하자.

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.7.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.3.2</version>
</dependency>


스프링 프로젝트가 기본 dependency를 등록해주므로 우리는 두개만 더 등록해주자.

다음 Article.java 라는 엔티티를 만들어보자.


Article.java

package com.tistory.jekalmin.domain;

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

@Entity
public class Article {

    @Id        // primary key
    @GeneratedValue        // auto increment
    private int articleNo;
    private int memberNo;
    private String title;
    private String content;
    /* 컬럼명은 필드명이랑 동일하게 된다. 따로 명시해주고 싶으면 @Column을 사용하자 */

    /**
     * 하이버네이트가 파라미터 없는 생성자를 호출하기 때문에, 파라미터 있는 생성자를 생성했을 경우
     * 꼭 파라미터 없는 생성자를 만들어 줘야 한다.
     */
    public Article(){}

    public Article(int memberNo, String title, String content) {
        this.memberNo = memberNo;
        this.title = title;
        this.content = content;
    }

    public int getArticleNo() {
        return articleNo;
    }
    @SuppressWarnings("unused")
    private void setArticleNo(int articleNo) {
        this.articleNo = articleNo;
    }
    public int getMemberNo() {
        return memberNo;
    }
    public void setMemberNo(int memberNo) {
        this.memberNo = memberNo;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Article [articleNo=" + articleNo + ", memberNo=" + memberNo
                + ", title=" + title + ", content=" + content + "]";
    }


}


그리고 Article 엔티티의 저장소를 만든다.


ArticleRepository.java

package com.tistory.jekalmin.repository;

import org.springframework.data.repository.CrudRepository;

import com.tistory.jekalmin.domain.Article;

public interface ArticleRepository extends CrudRepository<Article, Integer> {

}


이제 applicationContext 설정만 하면 된다.


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <context:component-scan base-package="com.tistory.jekalmin"/>
    <context:annotation-config/>

    <jdbc:embedded-database id="dataSource" type="HSQL"></jdbc:embedded-database>

    <!-- spring data jpa 설정 -->

    <!-- jpa repository가 위치한 패키지 경로 등록 -->
    <jpa:repositories base-package="com.tistory.jekalmin.repository" entity-manager-factory-ref="entityManagerFactory"/>

    <!-- 하이버네이트의 SessionFactory 에 상응하는 jpa의 EntityManagerFactory 등록 -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop> <!-- Hsql 형식에 맞게 변환해주는 클래스 -->
                <prop key="hibernate.connection.pool_size">1</prop>
                <prop key="hibernate.connection.shutdown">true</prop> <!-- hsql에 있는 마지막 연결이 끊어지면 데이터베이스 shutdown 하는 플래그 -->
                <prop key="hibernate.show_sql">true</prop> <!-- SQL 출력 -->
                <prop key="hibernate.hbm2ddl.auto">create</prop> <!-- 테이블 자동 생성 -->
            </props>
        </property>
        <!-- 엔티티 정의된 클래스들이 있는 패키지 등록 -->
        <property name="packagesToScan" value="com.tistory.jekalmin.domain"/>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"></property>
    </bean>
    <!-- spring data jpa 설정 끝 -->



</beans>


설정 부분이 쉽지 않은데, 약간의 주석들이 도움이 되길 바란다.

이제 테스트할 클래스만 만들고 테스트하면 된다.


ArticleRepositoryTest.java

package com.tistory.jekalmin.test;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.tistory.jekalmin.domain.Article;
import com.tistory.jekalmin.repository.ArticleRepository;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/application-config.xml")
public class ArticleRepositoryTest {

    @Resource
    ArticleRepository articleRepository;

    @Test
    public void write(){

        articleRepository.save(new Article(11, "제목", "내용"));

        System.out.println(articleRepository.findAll());

    }


}


spring-data-jpa의 가장 강력한 기능중 하나가 Repository의 기본 기능을 제공하는 것 같다. 다만 인터페이스만 선언한 만큼, 나만의 메소드를 추가하는 방법이 조금 까다롭다. 메소드를 추가하는 방법은 다음에 작성하겠다.

[Spring Boot] yaml 설정파일 불러오기(@ConfigurationProperties)

개요

properties를 사용하면 map처럼 key와 value 형식으로 가능하지만, list 형태로 쓰려면 prefix로 구분해야 하는 불편함이 있다. 계층형 구조를 나타내는 설정은 yaml이 매우 편리해 보인다. Spring Boot에서 yaml을 불러올 때 제약사항이 꽤 많아서 정리해본다.

예제1:Map

먼저 src/main/resource 아래에 yml 파일을 하나 생성했다.


servers.yml

servers:
    local:
        ip: 127.0.0.1
        port: 8080
    real:
        ip: xxx.xxx.xxx.xxx
        port: 80

그 다음, yml 파일을 불러올 클래스를 생성한다.


ServerHolder.java

package com.tistory.jekalmin;

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(locations="classpath:servers.yml")
public class ServerHolder {

    public Map<String, Map<String, String>> servers = new HashMap<String, Map<String, String>>();

    public Map<String, Map<String, String>> getServers() {
        return servers;
    }

}

이제 외부에서 데이터가 잘 들어갔는지 확인한다.


TestController.java

package com.tistory.jekalmin;

import javax.annotation.Resource;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/test")
public class TestController {

    @Resource
    ServerHolder serverHolder;

    @RequestMapping("/index")
    public void index(){
        System.out.println("servers : " + serverHolder.getServers());
    }
}


결과는 다음과 같다.

servers : {real={ip=xxx.xxx.xxx.xxx, port=80}, local={port=8080, ip=127.0.0.1}}



에제2:List

위와 같은 예제를 리스트 형식으로 바꿔보자.


server.yml

servers:
  - name: local
    ip: 127.0.0.1
    port: 8080
  - name: real
    ip: xxx.xxx.xxx.xxx
    port: 80


ServerHolder.java

package com.tistory.jekalmin;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(locations="classpath:servers.yml")
public class ServerHolder {

    public List<Map<String, String>> servers = new ArrayList<Map<String, String>>();

    public List<Map<String, String>> getServers() {
        return servers;
    }

}


TestController.java는 그대로 사용해서 출력했더니 결과는 아래와 같았다.

servers : [{ip=127.0.0.1, port=8080, name=local}, {name=real, ip=xxx.xxx.xxx.xxx, port=80}]

간단한 예제인데도 많은 삽질을 했는데, 그 내용들은 다음과 같았다.

주의사항:
  • yaml 언어는 공백 하나에도 민감하다. 일단 하위 계층으로 내려갈 때 tab이 아닌 스페이스바를 사용하고, 콜론(:)이나 하이픈(-) 이후에 공백 한칸도 필요하다. yaml이 민감한건지 아니면 자바에서 사용한 라이브러리인 snakeyaml이 민감한건지는 잘 모르겠다.
  • 다음은 위에 ServerHolder.java에 해당하는 @ConfigurationProperties를 사용한 클래스이다. 먼저 어노테이션 안에 locations="classpath:servers.yml" 이 되기 전에 locations="servers.yml" 로 테스트를 했었는데, 유닛테스트 할때는 잘 작동하다가 Application.java를 이용해서 접근하려 하면 파일을 찾을 수 없었다.
  • ServerHolder.java 안에 servers가 List이던 Map이던 new를 꼭 시켜줘야 한다.
  • ServerHolder.java 안에 getServer()와 같은 getter가 꼭 있어야 한다.

결론

주의사항에 나열한 내용들과 yml의 민감한 문법 때문에 오늘 하루종일 삽질한 것 같다. 이 글을 읽고 계신 분들은 조금이나마 수월하게 진행 하셨으면 하는 바람이다. 그리고 지금은 Map<Strig, String>으로 받았는데, 클래스로 받을 수도 있다.


참고 : http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-yaml

[Spring Boot] properties 등록하기 (@PropertySource)

개요

Spring Boot로 프로젝트를 실행하면 기본적으로 application.properties를 참조하여 스프링이 실행 되지만, 스프링에 관련 없는 properties는 따로 설정파일을 만들어 빼고 싶은데, xml 설정파일이 없으니 어디에 추가해야 할지 막연했다. 검색을 하다보니 어노테이션에 properties 파일을 등록하면 사용이 가능했다.

예제

먼저 config.properties라는 파일을 resource 폴더에 생성했다.


config.properties

name=jekalmin


그리고 Application.java에 어노테이션을 이용하여 등록해주었다.


Application.java

package com.tistory.jekalmin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ComponentScan
@EnableAutoConfiguration
@PropertySource("config.properties")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

마지막으로 컨트롤러를 생성하여 잘 가져오는지 테스트하였다.


TestController.java

package com.tistory.jekalmin;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/test")
public class TestController {

    @Value("${name}")
    String name;

    @RequestMapping("/index")
    public void index(){
        System.out.println("name : " + name);
    }
}


결과는 다음과 같았다.

name : jekalmin

config.properties가 무사히 스프링에 등록된 것을 확인할 수 있다.