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;