Spring Data JPA入門
下記記事の延長。
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; } }