ぺーぺーSEのブログ

備忘録・メモ用サイト。

Spring Data JPA入門

下記記事の延長。

blog.pepese.com

PersonリソースをCRUDできるRestサービスを構築する。



■build.gradle

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'tomcat'

[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:1.2.4'
    }
}
repositories {
    mavenCentral()
}

dependencies {
    compile 'org.apache.derby:derby:10.11.1.1'
    compile 'org.eclipse.persistence:org.eclipse.persistence.jpa:2.6.0-M3'
    compile 'org.springframework:spring-webmvc:4.1.1.RELEASE'
    compile 'org.springframework:spring-orm:4.1.1.RELEASE'
    compile 'javax.servlet:javax.servlet-api:3.1.0'
    runtime 'com.fasterxml.jackson.core:jackson-databind:2.4.2'
    compile 'org.springframework.data:spring-data-jpa:1.7.0.RELEASE'
    runtime 'ch.qos.logback:logback-classic:1.1.2'

    def tomcatVersion = '8.0.12'
    tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
           "org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}"
    tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
        exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
    }
}

Derby+組み込みTomcatでやる。
JPA実装はEclipseLink。


■persistence.xml

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_1.xsd"
  version="2.1" xmlns="http://java.sun.com/xml/ns/persistence">

  <!-- PersistenceUnitの定義 -->
  <persistence-unit name="sampleUnit" transaction-type="RESOURCE_LOCAL">

    <!-- Entityの定義 -->
    <class>jp.sample.model.Person</class>

    <!-- PersistenceProviderをEclipseLinkに指定 -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

    <!-- DB接続設定 -->
    <properties>
      <!-- JDBCドライバ -->
      <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
      <!-- DB接続先 -->
      <property name="javax.persistence.jdbc.url" value="jdbc:derby:H:/Derby/JpaSample;create=true" />
      <!-- <property name="javax.persistence.jdbc.user" value="test" /> -->
      <!-- <property name="javax.persistence.jdbc.password" value="test" /> -->

      <!-- EclipseLinkのテーブル自動作成設定 -->
      <property name="eclipselink.ddl-generation" value="create-tables" />
      <property name="eclipselink.ddl-generation.output-mode" value="database" />

      <!-- Weavingオフ -->
      <property name="eclipselink.weaving" value="false" />

      <!-- ログ出力設定 -->
      <property name="eclipselink.logging.level.sql" value="FINE" />
      <property name="eclipselink.logging.parameters" value="true" />
    </properties>

  </persistence-unit>

</persistence>

persistence.xmlはクラスパス直下のMETA-INF配下に置いてね。
Mavenベースのディレクトリだと「src/main/resources/META-INF」配下。



■jp.sample.conf.AppConfig

package jp.sample.conf;

import javax.persistence.EntityManagerFactory;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = { "jp.sample" })
@EnableTransactionManagement
@EnableJpaRepositories("jp.sample")
public class AppConfig {

	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
		em.setPersistenceUnitName("sampleUnit");
		em.setPackagesToScan("jp.sample");

		return em;
	}

	@Bean
	public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
		JpaTransactionManager tm = new JpaTransactionManager();
		tm.setEntityManagerFactory(emf);
		return tm;
	}
}



■jp.sample.conf.WebMvcConf

package jp.sample.conf;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
@Profile("container")
public class WebMvcConfig extends WebMvcConfigurerAdapter {

	@Override
	public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		configurer.ignoreAcceptHeader(true).defaultContentType(MediaType.APPLICATION_JSON);
	}
}



■jp.sample.conf.WebAppInitializer

package jp.sample.conf;

import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;

import org.springframework.context.annotation.Profile;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

@Profile("container")
public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(AppConfig.class);

        // Manage the lifecycle of the root application context
        servletContext.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's SpringMVC application context
        AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
        mvcContext.register(WebMvcConfig.class);

        //mvcContext.setParent(rootContext);
        mvcContext.scan("jp.sample");

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcherServlet = servletContext.addServlet("dispatcherServlet", new DispatcherServlet(mvcContext));
        dispatcherServlet.setLoadOnStartup(1);
        Set<String> mappingConflicts = dispatcherServlet.addMapping("/");

        dispatcherServlet.setInitParameter("spring.profiles.active", "container");

        // Check the servlet mappings
        if (!mappingConflicts.isEmpty()) {
          for (String s : mappingConflicts) {
            System.out.println("[ERROR] Mapping conflict: " + s);
          }
          throw new IllegalStateException(
              "'webservice' cannot be mapped to '/'");
        }
    }
}



■jp.sample.model.Person

package jp.sample.model;

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

@Entity
public class Person {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String name;
	private Integer age;

	public Person() {}

	public Person(String name, Integer 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 Integer getAge() {
		return age;
	}

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



■jp.sample.repository.PersonRepository

package jp.sample.repository;

import jp.sample.model.Person;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}

今回の味噌はここ。
なんと実装無しのインターフェースだけでfindAll、findOne、saveといった一般的なCRUDオペレーションは既に実行されている!



■jp.sample.controller.PersonController

package jp.sample.controller;

import java.util.List;

import jp.sample.model.Person;
import jp.sample.repository.PersonRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
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.RestController;

@RestController
@Transactional
@RequestMapping(value="/persons")
public class PersonController {

	@Autowired
	private PersonRepository rep;

	@RequestMapping(method=RequestMethod.GET)
	public ResponseEntity<List<Person>> list(){
		List<Person> result = rep.findAll();
		HttpStatus status = HttpStatus.OK;
		if(result == null || result.size() < 1) {
			status = HttpStatus.NOT_FOUND;
		}
		return new ResponseEntity<List<Person>>(result, status);
	}

	@RequestMapping(value="/{id}", method=RequestMethod.GET)
	public ResponseEntity<Person> index(@PathVariable("id") Long id) {
		Person result = null;
		HttpStatus status = HttpStatus.OK;
		if(rep.exists(id)) {
			result = rep.getOne(id);
		} else {
			status = HttpStatus.NOT_FOUND;
		}
		return new ResponseEntity<Person>(result, status);
	}

	@RequestMapping(method=RequestMethod.POST)
	public ResponseEntity<Person> create(@RequestBody Person person) {
		if(person.getId() == null) {
			rep.save(person);
			return new ResponseEntity<Person>(person, HttpStatus.CREATED);
		} else {
			return new ResponseEntity<Person>((Person) null, HttpStatus.BAD_REQUEST);
		}
	}

	@RequestMapping(value="/{id}", method=RequestMethod.PUT)
	public ResponseEntity<Person> update(@PathVariable("id") Long id, @RequestBody Person person) {
		if(id != null && rep.exists(id)) {
			person.setId(id);
			rep.save(person);
			return new ResponseEntity<Person>(HttpStatus.OK);
		} else {
			return new ResponseEntity<Person>(HttpStatus.NOT_FOUND);
		}
	}

	@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
	public ResponseEntity<Person> destroy(@PathVariable("id") Long id) {
		if(id != null && rep.exists(id)) {
			rep.delete(id);
			return new ResponseEntity<Person>(HttpStatus.OK);
		} else {
			return new ResponseEntity<Person>(HttpStatus.NOT_FOUND);
		}
	}
}

なんか動いた。やばすwww

応用

応用というほどではないが、persistence.xmlを無くして全てJavaベースで設定してみる。
persistence.xmlを削除して、下記だけ修正する。


■jp.sample.conf.AppConfig

package jp.sample.conf;

import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
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.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = { "jp.sample" })
@EnableTransactionManagement
@EnableJpaRepositories("jp.sample")
public class AppConfig {

	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
		em.setPackagesToScan("jp.sample");

		EclipseLinkJpaVendorAdapter jva = new EclipseLinkJpaVendorAdapter();
		jva.setDatabase(Database.DERBY);
		jva.setGenerateDdl(true);
		jva.setShowSql(true);
		Properties prop = new Properties();
		// Weavingオフ
		prop.setProperty("eclipselink.weaving", "false");
		em.setJpaVendorAdapter(jva);
		em.setJpaProperties(prop);

		em.setDataSource(dataSource());

		return em;
	}

	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
		dataSource.setUrl("jdbc:derby:H:/Derby/JpaSample;create=true");
		return dataSource;
	}

	@Bean
	public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
		JpaTransactionManager tm = new JpaTransactionManager();
		tm.setEntityManagerFactory(emf);
		return tm;
	}
}