Enterprise Java Development@TOPIC@
Earlier versions of EJB Spec defined persistence as part of javax.ejb.EntityBean
JavaEE 5 moved persistence to its own specification
Java Persistence API (JPA) version 1.0
javax.persistence
Ease of use API above JDBC
Fueled by the addition of @Annotations to the JavaSE language
Provides
Object/Relational Mapping (ORM) Engine
Query Language (JPA-QL) - SQL-like - main carryover from EJB 2.x
JavaEE 6 advanced specification
Java Persistence API (JPA) version 2.0
More mapping capabilities
More entity manager capabilities
Standardization of properties
Criteria API
JavaEE 7 enhanced specification
Java Persistence API (JPA) version 2.1
Converters - to convert attributes to/from DB types
Criteria API Bulk Updates - no longer tied to just queries
Stored Procedure Query support
Partial fetching of objects
...
JavaEE 8
Java Persistence API (JPA) version 2.2
Streams - to fetch data from database in a stream versus batch
Date and Time API Support - eliminates need for converter
@Repeatable - no longer have to wrap repeated annotations within single wrapper
...
Replaced EJB 2.x Home Functionality
Handles O/R Mapping to the database
Provides APIs for
Inserting into database
Updating entities in database
Finding and querying for entities in database
Removing entities from database
Provides caching
Integrates with JTA transactions when on server-side
Tightly integrated with JavaEE and EJB, but not coupled to it
Plain Old Java Objects (POJOs)
Nothing special happens when calling new()
Author author = new Author();
From JPA perspective the above is a new/unmanaged entity
Entity minimum requirements:
@javax.persistence.Entity
public class Author {
@javax.persistence.Id
private long id;
private String firstName;
private String lastName;
private String subject;
private Date publishDate;
public Author() {
}
...
}
Annotated as an Entity or declared in an orm.xml
Unique identity (form primary key(s))
Public default constructor
Persistent when associated with an entity manager/persistence context
em.persist(author);
Author author = new Author();
author.setFirstName("dr");
author.setLastName("seuss");
author.setSubject("children");
author.setPublishDate(new Date());
logger.debug("creating author: {}", author);
assertEquals("unexpected initialized id", 0, author.getId());
logger.debug("em.contains(author)={}", em.contains(author));
assertFalse("author managed", em.contains(author));
em.persist(author);
logger.debug("created author: {}", author);
logger.debug("em.contains(author)={}", em.contains(author));
assertNotEquals("missing id", 0, author.getId());
assertTrue("author not managed", em.contains(author));
-creating author:ejava.examples.daoex.bo.Author@1d8e9e, id=0, fn=dr, ln=seuss, subject=children, pdate=Mon Sep 17 00:22:25 EDT 2012, version=0 -em.contains(author)=false -created author:ejava.examples.daoex.bo.Author@1d8e9e, id=50, fn=dr, ln=seuss, subject=children, pdate=Mon Sep 17 00:22:25 EDT 2012, version=0 -em.contains(author)=true
Associated with persistence context
Has identity
Changes to the entity will impact the database
Method em.contains(entity)
returns true
Has identity but not associated with persistence context
Changes to entity will not impact the database
Method em.contains(entity)
returns false
An entity becomes detached when:
Has not yet been persisted
After a transaction-scoped transaction is committed
After a transaction rollback <-- remember this in your error logic
Manually detaching entity from persistence context thru em.detach()
Manually clearing the persistence context thru em.clear()
Closing EntityManager
Serializing entity thru a remote interface
A set of managed instances managed by an EntityManager
All entities become detached once closed
Two types:
Extended
Author author = new Author();
...
em.persist(author);
em.getTransaction().begin();
em.getTransaction().commit();
author.setFirstName("foo");
em.getTransaction().begin();
em.getTransaction().commit();
em.getTransaction().begin();
author.setFirstName("bar");
em.getTransaction().commit();
Live beyond a single transaction
Allow long-lived algorithms to process without tying up a database transaction
Transaction-Scoped
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Author createAuthor(...) {
Author author = new Author();
...
em.persist(author);
return author;
}
Begin/end at transaction boundaries
Injected by containers
A set of classes that are mapped to the database
Defined in META-INF/persistence.xml
Entity classes may be named in persistence.xml or searched for
Entity mapping may be provided, augmented, or overridden with orm.xml
mapping file
|-- ejava | `-- examples | `-- daoex | |-- bo | | `-- Author.class | |-- dao | | |-- AuthorDAO.class | | `-- DAOException.class | `-- jpa | `-- JPAAuthorDAO.class `-- META-INF |-- orm.xml `-- persistence.xml
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="jpaDemo">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<mapping-file>META-INF/orm.xml</mapping-file>
<properties>
<!-- standard properties -->
<property name="javax.persistence.jdbc.url" value="jdbc:h2:./target/h2db/ejava"/>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<!-- hibernate-specific properties -->
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<!-- set to 0 to improve error messages when needed
<property name="hibernate.jdbc.batch_size" value="0"/>
-->
</properties>
</persistence-unit>
</persistence>
The above example:
defines properties for EntityManager to establish physical connections to database
this form of connection management only occurs for 2-tier thin clients (e.g., unit tests)
not a good place for potentially changing or sensitive property values
We can alternatively supply and override all the properties from the META-INF/persistence.xml at runtime if we control the creation of the EntityManagerFactory
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="ejbsessionbank">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<jar-file>lib/info.ejava.examples.ejb-ejbsessionBankImpl-5.0.0-SNAPSHOT.jar</jar-file>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.show_sql" value="false"/>
<!-- create is used here for demo project only -->
<property name="hibernate.hbm2ddl.auto" value="create"/>
<!--
<property name="hibernate.jdbc.batch_size" value="0"/>
-->
</properties>
</persistence-unit>
</persistence>
The above example:
uses a DataSource from JNDI tree to obtain connections to database
references entity classes in a separate archive through a relative path using jar-file
supplies references to resources that are fairly stable in value and non-sensitive
Can be used to specify connection properties outside of persistence.xml
Useful in separating production mapping information from runtime connection properties
#hibernate-specific alternate source of persistence.xml properties hibernate.connection.url=jdbc:h2:./target/h2db/ejava hibernate.connection.driver_class=org.h2.Driver hibernate.connection.password= hibernate.connection.username=sa hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.show_sql=true hibernate.format_sql=true #hibernate.jdbc.batch_size=0
Even though one can specify properties within the persistence.xml#persistence-unit or hibernate.properties file, they are not always the same. If you use the hibernate.properties file to specify the url, driver, and credentials -- always use the hibernate form of the name to avoid stumbling on the cases where hibernate fails to look for the javax.persitence form of the name for properties coming from the hibernate.properties file.
referenced by the persistence.xml
supplies mapping metadata for the entities that are either not annotated or overriding the annotations
each entity or group of entities within the same persistence unit can be defined in separate files
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
version="2.1">
<entity class="ejava.examples.daoex.bo.Author"
access="FIELD"
metadata-complete="false"
name="jpaAuthor">
<table name="DAO_AUTHOR"/>
<attributes>
<id name="id">
<generated-value strategy="SEQUENCE"
generator="AUTHOR_SEQUENCE"/>
</id>
</attributes>
</entity>
</entity-mappings>
name
Identity used to reference persistence unit
provider
Fully qualified name of javax.persistence.PersistenceProvider
Not needed if provider is in classpath
mapping-file
Path reference to an orm.xml
mapping file
jta-data-source
JNDI path of a JTA javax.sql.DataSource
non-jta-datasource
JNDI path of a RESOURCE_LOCAL javax.sql.DataSource
jarfile
Reference to an archive with entity classes
class
Fully qualified package name of entity class
One source of entity information
exclude-unlisted-classes
If set, provider will not scan to discover entity classes
properties
name/value property pairs to express additional configuration info
Database schema generation
Live database used by EntityManager has been prepared for persistence at runtime
Useful for demos and tutorials
Not an option for production
Script schema generation
Database schema commands written without changing the database
Useful in debugging "what does the provider think I am modeling?"
Good starting source script to author database migration scripts from
Vendor-specific option
Persistence provider is required to provide schema-generation capability
Likely has for years through a proprietary interface
javax.perisistance option
Standard interface added in JPA 2.1 for requesting schema generation
hibernate.hbm2ddl.auto
Controls hibernate schema generation
create, drop, create-drop, update, validate, and none
hibernate.hbm2ddl.import_files
Allows for self-authored files to be used as well
Expressed thru hibernate.properties using hibernate.hbm2ddl properties
hibernate.hbm2ddl.auto=create-drop
hibernate.hbm2ddl.import_files=/ddl/mydb-tuningdrop.ddl,/ddl/mydb-tuning.ddl
hibernate.connection.url=${jdbc.url}
hibernate.connection.driver_class=${jdbc.driver}
hibernate.connection.username=${jdbc.user}
hibernate.connection.password=${jdbc.password}
#hibernate.show_sql=true
#hibernate.format_sql=true
Can be expressed thru persistence.xml but use standard properties instead to avoid confusion
javax.persistence.schema-generation.database.action
Values: drop-and-create, drop, create, none
javax.persistence.schema-generation.create-source
Values: metadata (default), script, metadata-then-script, script-then-metadata
javax.persistence.schema-generation.create-script-source - provides reference to create source
javax.persistence.schema-generation.drop-script-source - provides reference to drop source
javax.persistence.sql-load-script-source
Provides reference to script to execute after schema initialized
Useful in Pre-populating sample database tables with content
Database schema generation example
<persistence-unit name="jpa-schemagen-test">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
<!-- a database connection definition is required for database actions -->
<property name="javax.persistence.jdbc.url" value="${jdbc.url}"/>
<property name="javax.persistence.jdbc.driver" value="${jdbc.driver}"/>
<property name="javax.persistence.jdbc.user" value="${jdbc.user}"/>
<property name="javax.persistence.jdbc.password" value="${jdbc.password}"/>
</properties>
</persistence-unit>
Script schema generation example
<persistence-unit name="jpa-schemagen-test">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<!-- a script file containing create and drop commands will be generated without interacting with database -->
<property name="javax.persistence.schema-generation.scripts.action" value="drop-and-create"/>
<property name="javax.persistence.schema-generation.scripts.create-target" value="${project.build.outputDirectory}/ddl/${project.artifactId}-create.ddl"/>
<property name="javax.persistence.schema-generation.scripts.drop-target" value="${project.build.outputDirectory}/ddl/${project.artifactId}-drop.ddl"/>
<!-- otherwise we would get 1 line per statement without standard delimiter character -->
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.hbm2ddl.delimiter" value=";"/>
<!-- required when no database connection specified -->
<property name="hibernate.dialect" value="${hibernate.dialect}"/>
</properties>
</persistence-unit>
Scripts written to target files
target/classes/ddl/ |-- jpa-schemagen-create.ddl `-- jpa-schemagen-drop.ddl
Create script contains commands to create schema
create sequence hibernate_sequence start with 1 increment by 1;
create table JPAUTIL_TABLET (
id integer not null,
maker varchar(255),
primary key (id)
);
Drop script contains commands to drop schema
drop table JPAUTIL_TABLET if exists;
drop sequence if exists hibernate_sequence;
Test Setup -- These actions occur outside of the individual @Test methods and normally not within the business and DAO logic. This is the kind of activity the server-side container will primarily take care of.
Create single EntityManagerFactory to share across multiple EntityManagers
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class MyJPATest {
private static EntityManagerFactory emf;
private EntityManager em;
@BeforeClass
public static void setUpClass() {
emf = Persistence.createEntityManagerFactory("jpaDemo");
}
Create EntityManager before each test
@Before
public void setUp() throws Exception {
em = emf.createEntityManager();
}
Runtime -- These actions occur either within the @Test or in the DAO under test. However, business and DAO logic rarely interact with the transaction calls. Transaction control is the job of the container and under the configuration of the server-side EJB. Think of a JUnit TestCase and @Test as being a simulated replacement for the container and EJB configuration so that we can test the business and DAO logic.
Start Transaction - this is the role of the container
em.getTransaction().begin();
Interact with EntityManager - this is the role of the DAO
em.persist(author);
Commit or Rollback Transaction - this is the role of the container
em.getTransaction().commit();
-or-
em.getTransaction().rollback();
Clean up
Close EntityManager after each test
@After
public void tearDown() throws Exception {
if (em != null) {
em.close();
em=null;
}
}
Close EntityManagerFactory after all tests
@AfterClass
public static void tearDownClass() {
if (emf != null) {
emf.close();
emf=null;
}
}
It is always good to try to keep @After or @AfterClass lifecycle methods from throwing sloppy exceptions (e.g., NullPointerException). These lifecycle methods get called whether the test is passing, failing, or even failed to fully initialize. If the @After or @AfterClass lifecycle methods throw an exception during cleanup after an exception -- their error will mask the real error and add confusion to the test results. Start each of these methods with a check of whether the sibling @BeforeClass or @Before lifecycle methods initialized what this method is trying to shutdown.
public interface javax.persistence.EntityManager{ ... }
void persist(Object entity);
<T> T find(Class<T> entityClass, Object primaryKey);
<T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties);
<T> T merge(T entity);
void remove(Object entity);
<T> T getReference(Class<T> entityClass, Object primaryKey);
void clear();
void detach(Object entity);
boolean contains(Object entity);
void flush();
void setFlushMode(javax.persistence.FlushModeType);
javax.persistence.FlushModeType getFlushMode();
void refresh(Object);
void refresh(Object, java.util.Map);
void lock(Object entity, javax.persistence.LockModeType);
void lock(Object entity, javax.persistence.LockModeType, Map<String, Object> properties);
<T> T find(Class<T> entityClass, Object primaryKey, javax.persistence.LockModeType);
<T> T find(Class<T> entityClass, Object primaryKey, javax.persistence.LockModeType, Map<String, Object> properties);
void refresh(Object, javax.persistence.LockModeType);
void refresh(Object, javax.persistence.LockModeType, Map<String, Object> properties);
javax.persistence.LockModeType getLockMode(Object entity);
javax.persistence.Query createQuery(String jpaql);
<T> javax.persistence.TypedQuery<T> createQuery(String jpaql, Class<T> resultClass);
javax.persistence.Query createNamedQuery(String name);
<T> javax.persistence.TypedQuery<T> createNamedQuery(String name, Class<T> resultClass);
javax.persistence.Query createNativeQuery(String sql);
javax.persistence.Query createNativeQuery(String sql, Class resultClass);
javax.persistence.Query createNativeQuery(String sql, String resultMapping);
<T> javax.persistence.TypedQuery<T> createQuery(javax.persistence.criteria.CriteriaQuery<T> criteria);
javax.persistence.criteria.CriteriaBuilder getCriteriaBuilder();
void close();
boolean isOpen();
javax.persistence.EntityTransaction getTransaction();
void joinTransaction();
void setProperty(String key, Object value);
java.util.Map getProperties();
<T> T unwrap(Class<T> clazz);
Object getDelegate();
javax.persistence.metamodel.Metamodel getMetamodel();
javax.persistence.EntityManagerFactory getEntityManagerFactory();