Table of contents


Java Persistence API(JPA)

Where to put the persistence.xml

  • for web application → WEB-INF/classes/META-INF/persistence.xml
  • for jar →
  • for maven main folder → src/main/resource/META-INF/persistence.xml
  • for maven test folder → src/test/resource/META-INF/persistence.xml

If uses maven structure in Eclipse IDE, main resource and test resource would complied to same directory(the build directory), we should avoid using the same persistence-unit name in test and main resources

presitence.xml

Hibernate

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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_1_0.xsd">
    <persistence-unit name="entity" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>com.kent.MyClass</class>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost/database_name" />
            <property name="hibernate.connection.username" value="root" />
            <property name="hibernate.connection.password" value="" />
                        <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

set property bernate.show_sql to true to output HSQL → SQL in console.

    <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
    <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
    <property name="hibernate.connection.url" value="jdbc:mysql://localhost/database_name" />

    <property name="hibernate.connection.url" value="jdbc:hsqldb:mem:database_name"/>
    <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
    <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>

    <property name="hibernate.connection.url" value="jdbc:derby:memory:database_name"/>
    <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.EmbeddedDriver"/>
    <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/>

    <property name="hibernate.connection.url" value="jdbc:postgresql://localhost:5432/databaseName"/>
    <property name="hibernate.connection.driver_class" value="org.postgresql.Driver"/>
    <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>

MySQL encoding issue. If you encountered encoding issue when result set contains CJK code, you can add this property to url connection string. ?useUnicode=true&amp;characterEncoding=UTF-8 ex:

  jdbc:mysql://localhost/database_name?useUnicode=true&amp;characterEncoding=UTF-8

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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_1_0.xsd">
    <persistence-unit name="entity" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <class>com.kent.MyClass</class>
        <properties>
            <property name="eclipselink.target-database" value="MYSQL"/>
            <property name="eclipselink.jdbc.url" value="jdbc:mysql://localhost/database_name"/>
            <property name="eclipselink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="eclipselink.jdbc.user" value="root"/>
            <property name="eclipselink.jdbc.password" value=""/>
            <property name="eclipselink.ddl-generation" value="update-tables"/>
            <property name="eclipselink.logging.level" value="SEVERE"/>
        </properties>
    </persistence-unit>
</persistence>

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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_1_0.xsd">
    <persistence-unit name="entity" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <class>com.kent.MyClass</class>
        <properties>
            <property name="eclipselink.target-database" value="HSQL"/>
            <property name="eclipselink.jdbc.url" value="jdbc:hsqldb:mem:database_name"/>
            <property name="eclipselink.jdbc.driver" value="org.hsqldb.jdbcDriver"/>
            <property name="eclipselink.jdbc.user" value="sa"/>
            <property name="eclipselink.jdbc.password" value=""/>
            <property name="eclipselink.ddl-generation" value="create-tables"/>
            <property name="eclipselink.logging.level" value="SEVERE"/>
        </properties>
    </persistence-unit>
</persistence>

Properties

EclipseLink does NOT support the property show sql like hibernate, but we can turn the logging system to FINE level (<property name=“eclipselink.logging.level” value=“FINE”/>), the interpret SQL would be dumped.

CRUD

Create, Read, Update, Delete operations in JPA

Read類型的methods像find(), getReference(), refresh(), detach() and read queries,可以在transaction外執行(沒有tx.begin()/tx.commit()的包覆).

Create

entityManager.getTransaction().begin();
Employee newEmployee = new Employee(5);
entityManager.persist(newEmployee);
entityManager.getTransaction().commit();

Read

entityManager.find(Employee.class, 1);
// lazy load (as same as hibernate load() method)
entityManager.getReference(Employee.class, 1);
entityManager.createQuery("select employee from Employee employee");

  • for single result uses query.getSingleResult();
  • for multiple results (result set) uses query.getResultList();

Update

entityManager.getTransaction().begin();
Employee existingEmployee = entityManager.find(Employee.class, 5);
existingEmployee.setLastName("NewLastName");
entityManager.getTransaction().commit();

or

Employee existingEmployee = entityManager.find(Employee.class, 5);
existingEmployee.setLastName("NewLastName");
 
entityManager.getTransaction().begin();
entityManager.getTransaction().commit();

Delete

entityManager.getTransaction().begin();
Employee existingEmployee = entityManager.find(Employee.class, 5);
entityManager.remove(existingEmployee);
entityManager.getTransaction().commit();

Bulk Update / Delete

Bulk Update

EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
String jpqlUpdate = "update Customer set name = :newName where name = :oldName"
int updatedEntities = entityManager.createQuery( jpqlUpdate )
                            .setParameter( "newName", newName )
                            .setParameter( "oldName", oldName )
                            .executeUpdate();
entityManager.getTransaction().commit();
entityManager.close();

Bulk Delete

EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = entityManager.createQuery( hqlDelete )
                            .setParameter( "oldName", oldName )
                            .executeUpdate();
entityManager.getTransaction().commit();
entityManager.close();

Tips

FK constraint violation

Sometime the forgine key constraint that export by DDL tools would be a problem when doing unit testing. But there is no way to disable hibernate FK constraints exports. In the situation we can rewrite the Dialect to prevent FK constraints exported.

In this case, we created our own H2Dialet (for H2 database) and let getAddForeignKeyConstraintString() method empty. Which will disable the original org.hibernate.dialect.H2Dialect constraint generating statement.

package com.kent.hibernate.dialect;

public class H2Dialect extends org.hibernate.dialect.H2Dialect {
    @Override
    public String getAddForeignKeyConstraintString(String constraintName, String[] foreignKey, String referencedTable, String[] primaryKey, boolean referencesPrimaryKey) {
        return "";
    }
}

Once we have the Dialect, we need apply it our persistence.xml, after done, when the schema be generated, the schema will not include any FK constraint.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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_1_0.xsd">
    <persistence-unit name="entity-test" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
                 ...
        <properties>
            ...
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
                        <property name="hibernate.dialect" value="com.kent.hibernate.dialect.H2Dialect"/>
            ...
        </properties>
    </persistence-unit>
</persistence>

JPA Query

JPA Query supports

  1. query
  2. update
  3. delete

function to retrieves, modify, remove data from database.

IS [NOT] EMPTY

JPA provides a empty to test a collection empty or not:

select book from Book book where book.chapters is not empty

Join Fetch

JPA tries to fetch any entity as LAZY as possible whatever uses JPA QL or get by API.

Fetch lazy or eager is by JPA vendor implementation, not all field type can fetch as LAZY

Local Manager Transaction

In a non-managed environment, an EntityManagerFactory is usually responsible for its own database connection pool. The application developer has to manually set transaction boundaries, in other words, begin, commit, or rollback database transactions itself

If using local manager transaction, better to apply this template in code.

// Non-managed environment idiom
EntityManager em = emf.createEntityManager();
EntityTransaction tx = null;
try {
    tx = em.getTransaction();
    tx.begin();
    // do some work
    ...
    tx.commit();
} catch (RuntimeException e) {
    if ( tx != null && tx.isActive() ) tx.rollback();
    throw e; // or display error message
} finally {
    em.close(); // make sure you always close and never outside of guaranteed finally block
}

Joins and Fetches

  1. inner join = join
  2. left out join = left join
  3. fetch = 用一個select把屬性或集合一次取回
  4. fetch all = 如果有lazy properties,可用fetch all去init all lazy

Expressions

  • is empty, is not empty
  • member of, not member of 僅適用於embedded object
  • elements 測試某個element是否在elements中
  • minimum, maximum, minindex, maxindex 可用於 array, list, map
  • KEY() and VALUE() 可用於map
  • any, some, all, exists, in 這幾個可以和elements,minimum, maximum, minindex, maxinde 搭配使用
  • current_date(), current_time(), current_timestamp()
  • second(…), minute(…), hour(…), day(…), month(…), year(…), (specific to HQL)
  • coalesce() and nullif() coalesce() 取回第一個非NULL的值
  • TYPE … in …
  • cast(… as …)
  • Java public static final constants eg.Color.TABBY
member of
'867-5309' MEMBER OF p.phoneNumbers

coalesce
SELECT COALESCE( NULL, 34, 13, 0 )
// return 34

any, some, all, exists, in

any, some, all, exists, in在SQL-92中的定義

select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)

select p from NameList list, Person p where p.name = some elements(list.names)

select cat from Cat cat where exists elements(cat.kittens)

select cat from Player p where 3 > all elements(p.scores)

select cat from Show show where 'fizard' in indices(show.acts)

index
select order from Order order where order.items[0].id = 1234

KEY(),VALUE()
select i.name, VALUE(p) FROM Item i JOIN i.photos p WHERE KEY(p) LIKE ‘%egret’

Data in collection

public List<Role> listRolesByPermissions(Set<String> permissions) {
  String ql = "select role from Role role left join role.permissions p where p in (:perms)";
  Query q = entityManager.createQuery(ql).setParameter("perms", permissions);
  return q.getResultList();
}

上面的例子採用naming parameter可以正常動作,但下面採用position parameter的方式卻不行?

public List<Role> listRolesByPermissions(Set<String> permissions) {
  String ql = "select role from Role role left join role.permissions p where p in (?)";
  Query q = entityManager.createQuery(ql).setParameter(1, permissions);
  return q.getResultList();
}

Implementation Restrictions

To avoid conflicts with the original database operation that fires the entity lifecycle event (which is still in progress) callback methods should not call EntityManager or Query methods and should not access other entity objects.

If a callback method throws an exception within an active transaction, the transaction is marked for rollback and no more callback methods are invoked for that operation.

Architecture

EntityManagerFactory

建立時會比較耗時耗資源,所以應隨server啟動而啟動,關閉而關閉.

EntityManager

The EntityManager API is used to access a database in a particular unit of work. 正常來說,EntityManager的life cycle應該是一個database transaction(也就是說,應隨著transaction committed而close), 但也可以跨多個database transactions(跟hibernate的session一樣,可以跟多個transaction). 如果發生任何例外的話,應該要立即呼叫EntityManager.close() ,並且棄用EntityManager instance.

Annotations

如果需要讓boolean有預設值,可透過@ColumncolumnDefinition屬性

@Column(columnDefinition = "bool default false")
private boolean   boolField;

如果要讓欄位接受null,要宣告成物件型別(Object type)而非原生型別(primitive type)

Resources

[java