Cmobilecom AF 5.19 Developer Guide

5.3 Persistence Entity Manager

PersistenceEntityManager is a wrapper over JPA EntityManager to create, delete, update and query entities in persistence supporting detached QueryCriteria and CriteriaElements.

Create PersistenceEntityManager

PersistenceEntityManager can access different DataAccessUnit(s) mapped to logical persistence units. One JPA persistence unit can be shared by multiple DataAccessUnit(s). In multitenant system, each InstanceType and Instance ("tenant") have their own DataAccessUnit(s).

Access current bound DataAccessUnit (bound by domain or login):


	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager();

Access a specified DataAccessUnit:
	
	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager(dataAccessUnit);
Access the system DataAccessUnit:

	BackingBeanContext context = BackingBeanContext.getInstance();
	DataAccessUnit systemDataAccessUnit = context.getSystemDataAccessUnit();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager(systemDataAccessUnit);
Access the DataAccessUnit associated with a Component such as ContainerBean, BackingBean, EntityProperty, MenuNode, etc.

	PersistenceEntityManager peManager = component.getPersistenceEntityManager();
For example, retrieve all full-time employees

	PersistenceEntityManager peManager = backingBean.getPersistenceEntityManager();
	QueryCriteria queryCriteria = new QueryCriteria(Employee.class, 
		new CriteriaElement[]{DetachedCriteria.eq("type", Employee.Type.FULL_TIME)});
	List<Employee> employees = peManager.getEntityList(queryCriteria, null, null);
To access JPA EntityManager:

	PersistenceEntityManager peManager = backingBean.getPersistenceEntityManager();
	EntityMangaer entityManager = peManager.getEntityManager();

Query Hints

QueryHints is used to pass JPA standard properties/hints and Cmobilecom JPA proprietary properties/hints to Cmobilecom JPA. For example,

Pass query hints to PersistenceEntityManager:


	QueryHints queryHints = new QueryHints();
	queryHints.instanceHint(null);
	PersistenceEntityManager peManager = backingBean.getPersistenceEntityManager(
			transactionRequired, queryHints);
By default, instanceType DataAccessUnit will search entities of all instances and entities not associated with any instances. Setting instanceHint to null will limit query scope to those entities not associated with any instances. Transaction is required for creating/updating/deleting entities in persistence.

Pass query hints to query.


	PersistenceEntityManager peManager = backingBean.getPersistenceEntityManager();

	QueryCriteria queryCriteria = new QueryCriteria(Employee.class,
		new CriteriaElement[]{DetachedCriteria.eq("type", Employee.Type.FULL_TIME)});
	queryCriteria.distinct(true);
	queryCriteria.setHint(Constants.DISTINCT_MODE, "db");
	List<Employee> employees = peManager.getEntityList(queryCriteria, 0, 10);

Transactions

For a request lifecycle, one UserTransaction will be started and committed for each of the following phases: So do not need to explicitly start and commit a transaction.

	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager(dataAccessUnit, true);
	Employee employee = new Employee(nid, name, type, hiredDate);
	peManager.persistEntity(employee);
	
	// change and save it
	employee.setType(Employee.Type.FULL_TIME);
	employee = peManager.mergeEntity(employee);
	
	// delete it
	peManager.removeEntity(employee);
Entities are detached from persistence context at the end of each transaction.

Transaction listeners can be registered with current active transaction, and they will be called when the transaction is committed or rolled back. For example,


	TransactionListener listener = new DefaultTransactionListener() {
		@Override
		public PageNavigation committed(PhaseId phaseId) throws SystemException {
			// do something
			return null;
		}
		
		@Override
		public PageNavigation rolledback(PhaseId phaseId) throws SystemException {
			// do something		
			return null;
		}
	};
	TransactionPhaseListener.addTransactionListener(listener);
To explicitly start a transaction for a DataAccessUnit, e.g., in a Servlet

	UserTransactionWrapper userTransactionWrapper = new UserTransactionWrapper(dataAccessUnit);
	userTransactionWrapper.begin(false);

	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager(dataAccessUnit, true);
	// create/update/delete entities

	userTransactionWrapper.commit();

Transaction Types

The transaction type for a persistence unit is specified in META-INF/persistence.xml. For a transaction type, a JPAEntityManagerAccessor needs to be registered.

For RESOURCE_LOCAL, a default JPAEntityManagerAccessor has been registered. For JTA, a JPAEntityManagerAccessor impl for JTA with PersistenceContext injections needs to be registered. For example,


public class JTAEntityManagerAccessor implements JPAEntityManagerAccessor {
	
	@PersistenceContext(unitName="system") 
	private EntityManager systemEntityManager;
	
	@PersistenceContext(unitName="persistenceUnit_1")
	private EntityManager puOneEntityManager;

	@Override
	public EntityManager getEntityManager(QueryDescriptor queryDescriptor) {
  		String persistenceUnit = queryDescriptor.getPersistenceUnit();
	  	if (persistenceUnit.equals("system"))
	  		return systemEntityManager;
	  	
	  	if (persistenceUnit.equals("persistenceUnit_1"))
	  		return puOneEntityManager;
	  	
	  	throw new IllegalArgumentException("EntityManager not found for persistence unit: " + persistenceUnit);
	}

	@Override
	public void clearEntityManagers(boolean allPersistenceUnits) {
		// EntityManager type is transaction, cleared at the end of each transaction.
		// so no need to clear(detach) entities.
	}
}
In a Module implementation class, register the JTAEntityManagerAccessor.

	@Override
	public void registerPersistenceEntityManagers() {
		JTAEntityManagerAccessor jtaEntityManagerAccessor = ; // get the instance
		PersistenceEntityManagerRegistry.register(true, jtaEntityManagerAccessor);
	}

Extend PersistenceEntityManager

PersistenceEntityManager can be extended, for example, to add application specific methods. First define an interface that extends PersistenceEntityManager. For example,

	public InventoryManager extends PersistenceEntityManager {
		public Integer getStockQuantity(Merchandise merchandise);
	}
The implementation class extends PersistenceEntityManagerBean. For example,
	
	public InventoryManagerBean extends PersistenceEntityManagerBean implements InventoryManager {
		@Override
		public Integer getStockQuantity(Merchandise merchandise) {
			...
		}
	}
Register the extended PersistenceEntityManager in module implementation:

	@Override
	public void registerPersistenceEntityManagers() {
		PersistenceEntityManagerRegistry.register(InventoryManager.class, InventoryManagerBean.class);
	}
An InventoryManager instance can be obtained in the following way:

	BackingBeanContext context = BackingBeanContext.getInstance();
	// access a DataAccessUnit
	InventoryManager inventoryManager = context.getPersistenceEntityManager(InventoryManager.class, dataAccessUnit);

	// access a DataAccessUnit with query hints, transaction required
	InventoryManager inventoryManager = context.getPersistenceEntityManager(InventoryManager.class, dataAccessUnit,
			transactionRequired, queryHints);

Data Source and Connection Pool

To use data sources that support connection pool, first configure them in application container. Then set non-jta-data-source or jta-data-source in META-INF/persistence.xml.
	<non-jta-data-source>jdbc/data_source_1</non-jta-data-source>
	or
	<jta-data-source>jdbc/data_source_1</jta-data-source>
For installer to configure data sources for Tomcat (e.g., install tomcat for test automation), create context.xml under ${root_project_dir}/install/installer/tomcat/.
<Context>

	<!-- data sources:
	The connection URL variable for an instance type is @JDBC_URL.instanceTypeId@, e.g.,
	variable @JDBC_URL.system@ for system instance,
	variable @JDBC_URL.xyz@ for instance type "xyz".
	
	All variables will be resolved by installer.
	-->

	<Resource name="jdbc/data_source_1"
			  auth="Container"
			  type="javax.sql.DataSource"
			  driverClassName="@JDBC_DRIVER@"
			  url="@JDBC_URL.system@"
			  username="@JDBC_USER@"
			  password="@JDBC_PASSWORD@"
			  maxActive="50"
			  maxIdle="10"/>

	<Resource name="jdbc/data_source_2"
			  auth="Container"
			  type="javax.sql.DataSource"
			  driverClassName="@JDBC_DRIVER@"
			  url="@JDBC_URL.xyz@"
			  username="@JDBC_USER@"
			  password="@JDBC_PASSWORD@"
			  maxActive="200"
			  maxIdle="20"/>
</Context>
Add the following at the beginning of the build.gradle of root project:

ext {
	// use jdbc connection pool
	set("jdbc.connectionPool", "true")
}
When running "gradlew install", the following property will be added to the generated answer.properties file.

	jdbc.connectionPool = true
and Tomcat will be installed with the context.xml (variables replaced).

Examples

Example 1: JPQL query

select nid and name of employees hired before a certain date and return the first 100 results.


	String query = "select nid, name from Employee where hiredDate<:hiredDate";
	Map<String, Object> parameterMap = new Hash<String, Object>();
	parameterMap.put("hiredDate", aDate);
	QueryCriteria queryCriteria = new QueryCriteria(Object[].class, 
		new DataDescriptor(Object[].class, true, false),
		query, QueryType.JPQL, parameterMap);
	List<Object[]> results = peManager.searchResults(queryCriteria, 0, 100);
	for (int i=0; i<results.size(); i++) {
		Object[] row = results[i];
		...
	}	
Or execute query without QueryCriteria:

	String query = "select nid,name from Employee where hiredDate<:hiredDate";
  	List results = peManager.executeQueryResultList(query, QueryType.JPQL, new Object[]{"hiredDate", aDate});
Example 2: JPQL update

	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager();

	String query = "update Employee set type=:type where hiredDate<:hiredDate";
	peManager.executeUpdate(query, QueryType.JPQL, new Object[]{"type", Employee.Type.FULL_TIME, "hiredDate", aDate});
Example 3: Pagination

	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager();

	CriteriaElement[] pqeList = new CriteriaElement[]{
		DetachedCriteria.eq("type", Employee.Type.FULL_TIME),
		DetachedCriteria.desc("hiredDate")};
	QueryCriteria<Employee, Employee> queryCriteria = 
		new QueryCriteria<Employee, Employee>(Employee.class, pqeList);
	
	// pagination
	List<Employee> employees = peManager.getEntityList(queryCriteria, 0, 20);
	List<Employee> employees = peManager.getEntityList(queryCriteria, 20, 20);

	// row count
	int numberOfEmployees = peManager.getRowCount(queryCriteria);
Example 4: Create/update/delete

	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager();
	
	// create
	Employee employee = new Employee(...);
	peManager.persistEntity(employee);
	
	// update
	employee.setName("NewName");
	employee = peManager.mergeEntity(employee);
	
	// delete
	peManager.removeEntity(employee);
Batch update: change all the part-time employees hired before a certain date to full-time employees.

	Data aDate = ;
	peManager.updateProperties(Employee.class, new CriteriaElement[]{
		DetachedCriteria.lt("hiredDate", aDate),
		DetachedCriteria.eq("type", Employee.Type.PART_TIME)},
		new Object[]{"type", Employee.Type.FULL_TIME});
Batch delete: delete all the expense claims that are older than a certain date.

	Data aDate = ;
	peManager.deleteEntities(ExpenseClaim.class, new CriteriaElement[]{
		DetachedCriteria.lt("date", aDate)});
Example 5: Lock entity

	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager();
	
	Employee employee = ;
	// reload with write lock
  	employee = peManager.lock(employee, true, LockModeType.WRITE);
  	
  	// retrieve entity with write lock
  	Employee employee = peManager.lock(Employee.class, 100, LockModeType.WRITE); 
 
Example 6: Detach entities/clear L2 cache

	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager();
	
	Collection<Employee> employees = ;
	// detach employees, and clear L2 cache
	peManager.detachEntities(employees, true);
Example 7: Check whether an entity/property is loaded

	BackingBeanContext context = BackingBeanContext.getInstance();
	PersistenceEntityManager peManager = context.getPersistenceEntityManager();
	
	// check entity
	ExpenseClaim expenseClaim = ;
	boolean loaded = peManager.isLoaded(expenseClaim);
	
	// check property
	boolean loaded = peManager.isLoaded(expenseClaim, "expenseClaimItems");
Query CriteriaContainer BeanFrames / No Frames