JPA

★JPA vs JDO

http://db.apache.org/jdo/jdo_v_jpa.html(英語)

・JDO(元Sun社、RDBとNoSQL共に対応)

・JPA(元Sun社、EJB3.xから)

エンティティのライフサイクル

persistence.xml

Mavenプロジェクト:resources/META-INF/persistence.xml

JPA 1.0

★Toplink Essentials

<property name="toplink.jdbc.user" value="hoge"/>

<property name="toplink.jdbc.password" value="hoge"/>

<property name="toplink.jdbc.url"

value="jdbc:derby://localhost:1527/Employee"/>

<property name="toplink.jdbc.driver"

value="org.apache.derby.jdbc.ClientDriver" />


Hibernate EntityManager

<property name = "hibernate.dialect"

value="org.hibernate.dialect.H2Dialect">

※MySQLDialect

<property name="hibernate.connection.username" value="hoge"/>

<property name="hibernate.connection.password" value="hoge"/>

<property name="hibernate.connection.url"

value="jdbc:derby://localhost:1527/Employee"/>

<property name="hibernate.connection.driver_class"

value="org.apache.derby.jdbc.ClientDriver" />

※org.h2.Driver

<property name="hibernate.hbm2ddl.auto" value="create-drop"/> update

<property name="hibernate.show_sql" value="true"/>

<property name="hibernate.format_sql" value="true"/>

<property name="hibernate.cache.use_second_level_cache" value="true"/>

<property name="hibernate.cache.use_query_cache" value="true"/>

<property name="hibernate.cache.infinispan.statistics" value="true"/>

<property name="hibernate.default_batch_fetch_size" value="100"/>

<property name="hibernate.connection.useUnicode" value="true"/>

<property name="hibernate.connection.characterEncoding" value="UTF-8"/>


EclipseLink 1.x

<property name="eclipselink.jdbc.user" value="hoge"/>

<property name="eclipselink.jdbc.password" value="hoge"/>

<property name="eclipselink.jdbc.url"

value="jdbc:derby://localhost:1527/Employee"/>

<property name="eclipselink.jdbc.driver"

value="org.apache.derby.jdbc.ClientDriver" />

OpenJPA

<property name="openjpa.ConnectionUserName" value="hoge" />

<property name="openjpa.ConnectionPassword" value="hoge" />

<property name="openjpa.ConnectionURL"

value="jdbc:derby://localhost:1527/Employee"/>

<property name="openjpa.ConnectionDriverName"

value="org.apache.derby.jdbc.ClientDriver" />

JPA 2.0

<property name="javax.persistence.jdbc.user" value="hoge"/>

<property name="javax.persistence.jdbc.password" value="hoge"/>

<property name="javax.persistence.jdbc.url"

value="jdbc:derby://localhost:1527/Employee"/>

<property name="javax.persistence.jdbc.driver"

value="org.apache.derby.jdbc.ClientDriver"/>

JPA 2.1

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

<persistence xmlns="http://java.sun.com/xml/ns/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">

<persistence-unit name="samplePU" transaction-type="RESOURCE_LOCAL">

<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

<class>xxx.entity.Book</class>

<properties>

<!-- none, create, drop-and-create, drop -->

<property name="javax.persistence.schema-generation.database.action"

value="drop-and-create"/>

<!-- none, create, drop-and-create, drop -->

<property name="javax.persistence.schema-generation.scripts.action"

value="drop-and-create"/>

<property name="javax.persistence.schema-generation.scripts.create-target"

value="sampleCreate.ddl"/>

<property name="javax.persistence.schema-generation.scripts.drop-target"

value="sampleDrop.ddl"/>

<property name="javax.persistence.jdbc.driver"

value="org.apache.derby.jdbc.EmbeddedDriver"/>

<property name="javax.persistence.jdbc.url"

value="jdbc:derby:memory:sampleDB;create=true"/>

<property name="javax.persistence.sql-load-script-source" value="insert.sql"/>

</properties>

</persistence-unit>

</persistence>

public static void main(String[] args) {

Persistence.generateSchema("samplePU", null);

}

Java SE用

<persistence ...>

<persistence-unit name="testU" transaction-type="RESOURCE_LOCAL">

<!-- EclipseLink -->

<provider>

org.eclipse.persistence.jpa.PersistenceProvider

</provider>

<!-- Hibernate -->

<!-- org.hibernate.ejb.HibernatePersistence -->

<!-- TopLink Essentials -->

<!-- oracle.toplink.essentials.PersistenceProvider -->

<!-- Apache OpenJPA -->

<!-- org.apache.openjpa.persistence.PersistenceProviderImpl -->

<!-- DataNucleus -->

<!-- org.datanucleus.jpa.PersistenceProviderImpl -->

<class>com.example.Book</class>

<exclude-unlisted-classes>false</exclude-unlisted-classes>

<properties>

<property name="javax.persistence.jdbc.driver"

value="com.mysql.jdbc.Driver" />

<property name="javax.persistence.jdbc.url"

value="jdbc:mysql://localhost:3306/test" />

<property name="javax.persistence.jdbc.user" value="root" />

<property name="javax.persistence.jdbc.password" value="" />

<property name="eclipselink.ddl-generation"

value="drop-and-create-tables"/>

<property name="eclipselink.logging.level" value="INFO"/>

</properties>

</persistence-unit>

</persistence>

Java EE用

<persistence ...>

<persistence-unit name="glassfishU" transaction-type="JTA">

<provider>

org.eclipse.persistence.jpa.PersistenceProvider

</provider>

<jta-data-source>jdbc/MySQLDataSource</jta-data-source>

<class>com.example.Book</class>

<properties>

<property name="eclipselink.ddl-generation"

value="drop-and-create-tables"/>

<property name="eclipselink.logging.level" value="INFO"/>

</properties>

</persistence-unit>

<persistence-unit name="jbossU" transaction-type="JTA">

<jta-data-source>java:jboss/datasources/test</jta-data-source>

<mapping-file>META-INF/xml/entity.xml</mapping-file>

<mapping-file>META-INF/xml/jpql.xml</mapping-file>

<mapping-file>META-INF/xml/sql.xml</mapping-file>

<properties>

<!-- hibernate -->

</properties>

</persistence-unit>

</persistence>

★Listener設定(XML版)

persistence.xml

<entity-listeners>

<entity-listener class="xxx.LogListener">

<pre-persist method-name="showLog"/>

</entity-listener>

</entity-listeners>

★L1 cache と L2 cache(JPA 2.0)

※他サイトを参照

L1 cache

廃棄

em.clear

em.refresh

L2 cache

Target entities

read often

modified infrequently

Not critical if stale

有効にする設定 (persistence.xml)

<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>

<properties>

<property name="hibernate.cache.use_second_level_cache"

value="true" />

<property name="hibernate.cache.use_query_cache" value="true" />

<property name="hibernate.cache.infinispan.cachemanager"

value="java:jboss/infinispan/hibernate"/>

<property name="hibernate.cache.region.factory_class"

value="org.hibernate.cache.infinispan.JndiInfinispanRegionFactory"/>

</properties>

shared-cache-mode:

ALL(default) すべてのエンティティ

DISABLE_SELECTIVE @Cacheable(true)を付けたエンティティを除く

ENABLE_SELECTIVE @Cacheable(true)を付けたエンティティ

NONE キャッシュは無効

サンプル

Bean

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

@Entity

//@Table(name="book", catalog="test")

@Getter @Setter

public class Book implements Serializable {

private static final long serialVersionUID = 1L;

@Id

@GeneratedValue

private long id;

@Column(nullable = false)

//@Column(name="Title", unique=true, nullable=false, length=10)

private String title;

@Column(length = 1000)

private String description;

public String toString() {

return String.format("%s[%s, %s, $s]",

getClass().getSimpleName(),

getId(),

getTitle(),

getDescription());

}

自動生成されたテーブル

mysql> desc book;

+----------------------+-------------------+---------+------+-----------+-------+

| Field | Type | Null | Key | Default | Extra |

+----------------------+-------------------+---------+------+-----------+-------+

| ID | bigint(20) | NO | PRI | NULL | |

| TITLE | varchar(255) | NO | | NULL | |

| DESCRIPTION | varchar(1000) | YES | | NULL | |

+----------------------+-------------------+---------+------+-----------+-------+

Dao

public interface BookServiceDao {

List<Book> findAll();

Book find(long id);

Book create(Book book);

Book update(Book book);

Book delete(Book book);

}

import javax.ejb.Local;

import javax.ejb.Stateless;

import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;

import javax.persistence.TypedQuery;

@Stateless

@Local(BookService.class)

public class BookServiceDaoImpl implements BookServiceDao {

@PersistenceContext(unitName = "testU")

private EntityManager em;

@Override

public List<Book> findAll() {

TypedQuery<Book> query

= em.createQuery("SELECT b from Book b", Book.class);

return query.getResultList();

}

@Override

public Book find(long id) {

//Book book = em.find(Book.class, id);

Book book = em.getReference(Book.class, id); // lazy version

return book;

}

@Override

public Book create(Book book) {

em.persist(book);

return book;

}

@Override

public Book update(Book book) {

em.merge(book);

return book;

}

あるいは

public Book update(long id) {

Book book = new Book();

book.setId(id);

...

em.merge(book); // DB上にある場合はUPDATE、ない場合はINSERT

return book;

}

@Override

public Book delete(Book book) {

em.remove(book);

return book;

}

あるいは

public Book delete(long id) {

Book book = this.find(id);

em.remove(book);

return book;

}

}

Servlet

import javax.ejb.EJB;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns={"/book/xxx"})

public class BookFindServlet extends HttpServlet {

@EJB

private BookServiceDao bookServiceDao;

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws IOException {

// 検索

List<Book> books = bookServiceDao.findAll();

for (Book b : books) {

...

}

...

// 追加

Book book = new Book();

book.setTitle("xxx");

...

book = bookServiceDao.create(book);

...

}

}

補足(Util)

PersistenceUtil util = Persistence.getPersistenceUtil();

boolean isObjectLoaded = util.isLoaded(employee);

boolean isFieldLoaded = util.isLoaded(employee, "address");

★複合主キー

NewsId id = new NewsId("xxx", "yyy");

News news = em.find(News.class, id);

方法1

@Embeddable

@EqualsAndHashCode

public class NewsId implements Serializable {

private String title;

private String language;

...

}

@Entity

public class News {

@EmbeddedId

private NewsId id;

...

@Embedded //主キーではない場合

private NewsId item;

}

JPQL:SELECT n.newsId.title FROM News n

方法2

public class NewsId {

private String title;

private String language;

...

}

@Entity

@IdClass(NewsId.class)

public class News {

@Id

private String title;

@Id

private String language;

...

}

JPQL:SELECT n.title FROM News n

★XML設定

sql.xml

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

<entity-mappings version="2.0" ...>

<named-native-query name="Order.delete" result-set-mapping="dummy">

<query>

DELETE FROM order ...

</query>

</named-native-query>

<named-native-query name="Order.count" result-set-mapping="count">

<query>

SELECT COUNT(*) AS cnt ...

</query>

</named-native-query>

...

<sql-result-set-mapping name="dummy">

<column-result name="none"/>

</sql-result-set-mapping>

<sql-result-set-mapping name="count">

<column-result name="cnt" />

</sql-result-set-mapping>

...

</entity-mappings>

★EntityManagerの取得方法

コンテナ管理から

・コンテナによるDI

・JNDIルックアップ

@Stateless

public class MyBean {

@PersistenceContext(unitName="xxx")

private EntityManager em;

...

}

アプリケーション管理から

・コンテナによるDI

・JNDIルックアップ

・createEntityManagerFactoryメソッド

public class MyServlet extends HttpServlet {

@PersistenceUnit(unitName="xxx")

private EntityManagerFactory emf;


public void doPost(...) {

EntityManager em = emf.createEntityManager();

...

}

}

JavaSE環境

Book book = new Book();

book.setId(bookid);

...

EntityManagerFactory emf = Persistence.createEntityManagerFactory("testU");

EntityManager em = emf.createEntityManager();

EntityTransaction tx = em.getTransaction();

tx.begin();

em.persist(book);

tx.commit();

book = em.find(Book.class, 1001L);

//EJBコンテナの場合、close処理が不要

em.close();

emf.close();

★エラー対応

java.lang.VerifyError: Expecting a stackmap frame at branch target ...

方法1:JavaコンパイラーをJDK6に設定

方法2:JDK7の場合、JVMの引数に"-XX:-UseSplitVerifier"を追加

★JPA 2.1 new Features and Enhancements

・Stored Procedure Query

@NamedStoredProcedureQuery

或は

EntityManager#createStoredProcedureQuery(String procedureName, Class… resultClasses)

・Attribute Converter

@Converter

・Constructor Result Mapping

@ConstructorResult (addition to @SqlResultSetMapping)

・Programmatic Named Queries

EntityManager#addNamedQuery(String name, Query query)

・Entity Graph

@NamedEntityGraph @NamedAttributeNode @NamedSubGraph

或は

EntityManager#createEntityGraph(Class rootType)

・JPQL Enhancements

ON FUNCTION TREAT

・Criteria API Bulk Operations

CriteriaUpdate CriteriaDelete

・Unsynchronized Persistence Context @PersistenceContext(synchronization=SynchronizationType.UNSYNCHRONIZED)

EntityManager#joinTransaction()

・Generating DB Schema

javax.persistence.schema-generation.database.action

javax.persistence.schema-generation.scripts.action

javax.persistence.schema-generation.create-source

javax.persistence.schema-generation.drop-source

javax.persistence.schema-generation.create-database-schemas

javax.persistence.schema-generation.scripts.create-target

javax.persistence.schema-generation.scripts.drop-target

javax.persistence.database-product-name

javax.persistence.database-major-version

javax.persistence.database-minor-version

javax.persistence.schema-generation.create-script-source

javax.persistence.schema-generation.drop-script-source

javax.persistence.schema-generation.connection

javax.persistence.sql-load-script-source

・CDI-Support in Entity Listener

import javax.persistence.Cacheable;

@Entity

@Cacheable

@NamedQueries(

{

@NamedQuery(

name = "listUsers",

query = "FROM User c WHERE c.name = :name",

hints = { @QueryHint(

name = "org.hibernate.cacheable", value = "true") }

)

})

public class User { ... }

RetrieveMode

CacheRetrieveMode.USE (default)

CacheRetrieveMode.BYPASS

em.setProperty(

"javax.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS);

query.setHint(

"javax.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS);

Long pk = ...;

em.find(MyEntity.class, pk,

Collections.<String, Object>singletonMap(

"javax.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS));

StoreMode

CacheStoreMode.BYPASS not updated with new data

CacheStoreMode.USE (default) new data in stored (only for not in the cache)

CacheStoreMode.REFRESH new data is stored (refreshing already cached)

em.setProperty(

"javax.persistence.cache.storeMode", CacheStoreMode.BYPASS);

Long pk = ...;

em.find(MyEntity.class, pk,

Collections.<String, Object>singletonMap(

"javax.persistence.cache.storeMode", CacheStoreMode.BYPASS));

あるいは

Map<String, Object> props = new HashMap<String, Object>();

props.put("javax.persistence.cache.storeMode", "BYPASS");

em.find(MyEntity.class, pk, props);

Cache Interface

Cache cache = emf.getCache();

Cache cache = em.getEntityManagerFactory().getCache();

Long pk = ...;

boolean isCached = cache.contains(MyEntity.class, pk);

// Remove

cache.evict(MyEntity.class, pk);

cache.evict(MyEntity.class);

cache.evictAll();

★@AttributeOverride & @AssociationOverride

@MappedSuperclass

public abstract class License {

@Id

protected int licenseId;

@ManyToOne(cascade = ALL)

@JoinColumn(name = "employeeId")

private Employee employee;

...

}

@Entity

public class DriverLicense extends License {...}

@Entity

@AttributeOverride(name="licenseId", column=@Column(name="computerLicenseId"))

@AssociationOverride(name="employee", joinColumns={@JoinColumn(name="computerEmployeeId")})

public class ComputerLicense extends License {...}

★@GeneratedValue & @SequenceGenerator & @TableGenerator

@Id

@GeneratedValue(strategy=GenerationType.IDENTITY)

private int licenseId;

@Id

@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="LICENSE_SEQ")

@SequenceGenerator(name="LICENSE_SEQ",

sequenceName="LICENSE_SEQ",

allocationSize=1)

private int licenseId;

@Id

@GeneratedValue(strategy=GenerationType.TABLE, generator="LICENSE_TABLE_SEQ")

@TableGenerator(name="LICENSE_TABLE_SEQ",

table="sequences",

pkColumnName="SEQ_NAME",

valueColumnName="SEQ_NUMBER",

pkColumnValue="LICENSE_ID",

allocationSize=1)

private int licenseId;