JPA2.1入門
JavaEE 7のJPA2.1をさわってみる。
事始めなので2.1新機能とかには全然ふれない。
用語とか
- Persistence Unit
- EntityManagerのインスタンスを生成するファクトリクラスを定義
- EntityクラスのRDBにマッピングを設定する
- データベースへの接続用データソース設定
- など
- persistence.xml
- 1つ以上のPersistence Unitを定義可能
- @Entityアノテーションが付加されたクラスを検索
- EntityManager
- Entityのインスタンスのライフサイクルを管理するAPIを提供
- 永続化エンジンとプログラミングによる対話が可能
- 提供機能例
- ライフサイクル操作:persist(),remove(),refresh(),merge()
- 検索処理:find(),getReference()
- クエリの発行:createQuery(),createNamedQuery(),createNativeQuery()
- Persistence Contextの管理系:flush(),clear(),close(),getTransaction()
- Persistence Context
- 管理するEntityの集合
- 1つのPersistence Unitに属する
EntityManagerには2種類ある。
- コンテナ管理のEntityManager
- Java EE環境用
- @PersistenceContext(unitname="unit")アノテーション
- アプリケーション管理のEntityManager
- Java SE/EE両方使える
- 次の例のようにFactoryから生成
今回は後者のEntityManagerでサンプルを作ってみる。
- JPQL(Java Persistence Query Language)
- SQLに似たJPA用のクエリ
- クエリは4種類
- 動的クエリ:アプリ実行時にクエリ生成
- 名前付きクエリ:静的にクエリ定義
- ネイティブクエリ:DB固有機能を使用するためのクエリ
- Criteria API:JPQLに相当するクエリをAPIベースで記述できる(JPA2.1〜)
■JPQL例
SELECT c FROM Customer c WHERE c.lastName = 'Yamada' ORDER BY c.id
JPA2.1新機能
- ストアドプロシージャ対応
- バルク更新・削除対応
- JPQLのON句サポート
- エンティティからインデックスを自動生成
- コンバータ
- 「DB上は文字列で保持し、Entity上はIntegerで保持する」といったもの
- AttributeConverter等を継承して実装し、@Convertや@Converterで適用する
サンプルアプリ 〜アプリケーション管理のEntityManager版〜
■build.gradle
apply plugin: 'java' apply plugin: 'eclipse' repositories { mavenCentral() } dependencies { compile 'org.apache.derby:derby:10.11.1.1' compile 'org.eclipse.persistence:org.eclipse.persistence.jpa:2.6.0-M3' }
JPA実装はEclipseLinkを使う。
■persistence.xml(クラスパスの通った「META-INF」配下)
<?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> <!-- 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" /> </properties> </persistence-unit> </persistence>
persistent.xmlはクラスパス直下の「META-INF」内に作る。
今回は「src/main/resources/META-INF」内に作った。
■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.Main
package jp.sample; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import jp.sample.model.Person; public class Main { public static void main(String[] args) { // PersistenceUnitからEntityManagerFactoryを生成 EntityManagerFactory emf = Persistence.createEntityManagerFactory("sampleUnit"); // EntityManagerFactoryからEntityManagerを作成 EntityManager em = emf.createEntityManager(); // EntityManagerからEntityTransactionを取得 EntityTransaction tx = em.getTransaction(); // Entityの作成 Person person = new Person(); person.setName("Hoge"); person.setAge(20); tx.begin(); // トランザクション開始 em.persist(person); // 管理状態のEntity tx.commit(); // トランザクション終了 em.close(); emf.close(); } }
DBを覗くとテーブルも自動でできてるしいい感じ。
> java org.apache.derby.tools.ij ij version 10.11 ij> connect 'jdbc:derby:H:/Derby/JpaSample'; ij> select * from PERSON; ID |AGE |NAME ------------------------------------------ 1 |20 |Hoge 1 rows selected
なにこれかんたんw
Derbyの接続方法下記参照。
http://d.hatena.ne.jp/tanakakns/20140926/1411722693
サンプルアプリ 〜コンテナ管理のEntityManager版〜
SpringMVCのRestでやってみる。
くっそハマった。
ハマったときの参考:
http://revelfire.com/spring-webmvc-unit-test-fails-caused-by-java-lang-illegalargumentexception-a-servletcontext-is-required-to-configure-default-servlet-handling/
http://stackoverflow.com/questions/10769051/eclipselinkjpavendoradapter-instead-of-hibernatejpavendoradapter-issue
http://www.baeldung.com/2011/12/13/the-persistence-layer-with-spring-3-1-and-jpa/
■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' 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' } }
■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>
WeavingはEntityをバイナリにして高速化する仕組みだとかなんとか。
よく知らん。
※「@EnableLoadTimeWeaving」とかいうアノテーションを見かけた。。。
persistence.xmlの書きっぷりは以下あたりを参考。
http://itdoc.hitachi.co.jp/manuals/link/cosmi_v0870/APR2/EU150094.HTM
■jp.sample.config.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.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 public class AppConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setPersistenceUnitName("sampleUnit"); return em; } @Bean public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { JpaTransactionManager tm = new JpaTransactionManager(); tm.setEntityManagerFactory(emf); return tm; } }
SpringでJPA使うときは「LocalContainerEntityManagerFactoryBean」ってのを使うくさい。
また、トランザクションもSpringのJpaTransactionManagerを使う。
■jp.sample.config.WebMvcConfig
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.config.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) { // ルートコンテキストの作成 AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); rootContext.register(AppConfig.class); // ルートコンテキストをリスナーに追加 servletContext.addListener(new ContextLoaderListener(rootContext)); // MVC用のコンテキストを作成 AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext(); mvcContext.register(WebMvcConfig.class); // スキャンパッケージを指定 mvcContext.scan("jp.sample"); // Spring用のディスパッチャーサーブレットを作成 ServletRegistration.Dynamic dispatcherServlet = servletContext.addServlet("dispatcherServlet", new DispatcherServlet(mvcContext)); dispatcherServlet.setLoadOnStartup(1); Set<String> mappingConflicts = dispatcherServlet.addMapping("/"); // 「container」プロファイルをアクティブに dispatcherServlet.setInitParameter("spring.profiles.active", "container"); // マッピングのコンフリクトチェック if (!mappingConflicts.isEmpty()) { for (String s : mappingConflicts) { System.out.println("[ERROR] Mapping conflict: " + s); } throw new IllegalStateException( "'webservice' cannot be mapped to '/'"); } } }
■jp.sample.controller.PersonController
package jp.sample.controller; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import jp.sample.model.Person; import org.springframework.transaction.annotation.Transactional; 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 public class PersonController { @PersistenceContext private EntityManager em; @Transactional @RequestMapping(value="/persons", method=RequestMethod.POST) public void create(@RequestBody Person person) { em.persist(person); } }
gradle tomcatRunWarでトム猫起動。
「http://localhost:8080/[Context]/persons.json」へ「Content-Type: application/json」をつけてPOSTでJSONを送信。
■BodyのJSON例
{ "name":"Yamada", "age":30 }
初回アクセスのレスポンス超遅す。。。
出力されたSQLログ。
[EL Info]: 2014-10-14 11:40:44.039--ServerSession(2870262)--EclipseLink, version: Eclipse Persistence Services - 2.6.0.v20140809-296a69f [EL Info]: connection: 2014-10-14 11:41:26.06--ServerSession(2870262)--file:/H:/tools/Luna/pleiades/workspace/JPA/build/libs/JPA.war_sampleUnit login successful [EL Fine]: sql: 2014-10-14 11:41:30.103--ServerSession(2870262)--Connection(30001505)--SELECT ID FROM PERSON WHERE ID <> ID [EL Fine]: sql: 2014-10-14 11:41:56.28--ClientSession(9818516)--Connection(30001505)--INSERT INTO PERSON (AGE, NAME) VALUES (?, ?) bind => [11, KUMAKICHI] [EL Fine]: sql: 2014-10-14 11:42:00.341--ClientSession(9818516)--Connection(30001505)--values IDENTITY_VAL_LOCAL()