Interacting with an EntityManager
Now that you have learned how to deploy and obtain a reference to an entity manager, you are ready to learn the semantics of interacting with
it. The EntityManager API has methods to insert and remove entities from a database as well as merge updates from detached entity instances. There is also a rich query API that you can access by creating query objects from certain EntityManager methods:
package javax.persistence;
public interface EntityManager {
public void persist(Object entity);
public <T> T find(Class <T> entityClass, Object primaryKey);
public <T> T getReference(Class <T> entityClass, Object primaryKey);
public <T> T merge(T entity);
public void remove(Object entity);
public void lock(Object entity, LockModeType lockMode);
public void refresh(Object entity);
public boolean contains(Object entity);
public void clear( );
public void joinTransaction( );
public void flush( );
public FlushModeType getFlushMode( );
public void setFlushMode(FlushModeType type);
public Query createQuery(String queryString);
public Query createNamedQuery(String name);
public Query createNativeQuery(String sqlString);
public Query createNativeQuery(String sqlString, String resultSetMapping);
public Query createNativeQuery(String sqlString, Class resultClass);
public Object getDelegate( );
public void close( );
public boolean isOpen( );
}
Persisting Entities
Persisting an entity is the act of inserting it within a database. You persist entities that have not yet been created in the database. To create an entity, you first allocate an instance of it, set its properties, and wire up any relationships it might have with other objects. In other words, you initialize an entity bean just as you would any other Java object. Once you have done this, you then interact with the entity manager service by calling the EntityManager.persist( ) method:
Custom cust = new Customer( );
cust.setName("Bill");
entityManager.persist(cust);
When this method is called, the entity manager queues the Customer for insertion into the database, and the object instance becomes managed. When the actual insertion happens depends on a few variables. If persist( ) is called within a transaction, the insert may happen immediately, or it may be queued until the end of the transaction, depending on the flush mode (described later in this chapter). You can always force the insertion manually within a transaction by calling the flush( ) method. You may call persist( ) outside of a transaction if and only if the entity manager is an EXTENDED
persistence context. When you call persist( ) outside of a transaction with an EXTENDED persistence context, the insert is queued until the persistence context is associated with a transaction. An injected extended persistence context is automatically associated with a JTA transaction by the EJB container. For other extended contexts created manually with the EntityManagerFactor API, you must call Entity.Manager.joinTransaction( ) to perform the transaction association.
If the entity has any relationships with other entities, these entities may also be created within the database if you have the appropriate cascade policies set up. Cascading is discussed in detail in chapter-7. In chapter-6, we'll also see that Java Persistence can automatically generate a primary key when the persist( ) method is invoked. So, in the previous example, if you had auto key generation enabled, you could view the generated key after the persist( ) method completed.
The persist( ) method throws an IllegalArgumentException
if its parameter is not an entity type. TRansactionRequiredException
is thrown if this method is invoked on a transaction-scoped persistence context. However, if the entity manager is an extended persistence context, it is legal to call persist( ) outside of a transaction scope; the insert is queued until the persistence context interacts with a transaction.
Finding Entities
The entity manager provides two mechanisms for locating objects in your database. One way is with simple entity manager methods that locate an entity by its primary key. The other is by creating and executing queries.
find( ) and getReference( )
The EntityManager has two different methods that allow you to find an entity by its primary key:
public interface EntityManager {
<T> T find(Class<T> entityClass, Object primaryKey);
<T> T getReference(Class<T> entityClass, Object primaryKey);
}
Both methods take the entity's class as a parameter, as well as an instance of the entity's primary key. They use Java generics so that you don't have to do any casting. How do these methods differ? The find( ) method returns null if the entity is not found in the database. It also initializes the state based on the lazy-loading policies of each property (lazy loading is discussed in chapter-6):
Customer cust = entityManager.find(Customer.class, 2);
In this example, we are locating a Customer with a primary key ID of 2. How does this compile when the find( ) method expects an Object as its second parameter type? Well, Java 5 has a feature called autoboxing that converts primitive types directly into their Object type. So, the constant 2 is converted to a java.lang.Integer
:
Customer cust = null;
try {
cust = entityManager.getReference(Customer.class, 2);
} catch (EntityNotFoundException notFound) {
// recovery logic
}
getreference( ) differs from find( ) in that if the entity is not found in the database, this method throws a javax.persistence.EntityNotFoundException
and there is no guarantee that the entity's state will be initialized.
Both find( ) and getreference( ) throw an IllegalArgumentException if their parameters are not an entity type. You are allowed to invoke them outside the scope of a transaction. In this case, any object returned is detached if the EntityManager is transaction-scoped but remains managed if it is an extended persistence context.
Queries
Persistent objects can also be located by using EJB QL. Unlike EJB 2.1, there are no finder methods, and you must create a Query
object by calling the EntityManager's createQuery( ), createNamedQuery( ), or createNativeQuery( ) method:
public interface EntityManager {
Query createQuery(String queryString);
Query createNamedQuery(String name);
Query createNativeQuery(String sqlString);
Query createNativeQuery(String sqlString, Class resultClass);
Query createNativeQuery(String sqlString, String resultSetMapping);
}
Creating and executing an EJB QL query is very analogous to creating and executing a JDBC PreparedStatement:
Query query = entityManager.createQuery("from Customer c where id=2");
Customer cust = (Customer)query.getSingleResult( );
Queries and EJB QL are discussed in great detail in chapter-9.
All object instances returned by find( ), getResource( ), or a query remain managed as long as the persistence context in which you accessed them remains active. This means that further calls to find( ) (or whatever) will return the same entity object instance.
Updating Entities
Once you have located an entity bean by calling find( ), calling getreference( ), or creating and executing a query, the entity bean instance remains managed by the persistence context until the context is closed. During this period, you can change the state of the entity bean instance as you would any other object, and the updates will be synchronized automatically (depending on the flush mode) or if you call the flush( ) method directly:
@PersistenceContext EntityManager entityManager;
@TransactionAttribute(REQUIRED)
public void updateBedCount(int id, int newCount) {
Cabin cabin = entityManager.find(Cabin.class, id);
cabin.setBedCount(newCount);
}
In this code, the Cabin entity returned by the find( ) method is still managed by the EntityManager because an active persistence context is still associated with the transaction. This means that you can modify the object instance and the database will be updated automatically when the EntityManager decides to flush changes from memory to your database.
Merging Entities
The Java Persistence spec allows you to merge state changes made to a detached entity back into persistence storage using the entity manager's merge( ) method. Consider a remote Swing client. This client calls a method on our remote TravelAgent session bean to find a cabin in the database:
@PersistenceContext EntityManager entityManager;
@TransactionAttribute(REQUIRED)
public Cabin findCabin(int id) {
return entityManager.find(Cabin.class, id);
}
In this example, the persistence context ends when the findCabin( ) method finishes, as it is a single JTA transaction. When a Cabin instance is serialized, it is detached from the entity manager and is sent to the remote Swing client. This Cabin instance is a plain Java object and is no longer associated with any entity manager. You can call the getters and setters on this object just like you can on any other plain Java object. The Swing client makes a few changes to the Cabin instance and sends them back to the server.
Cabin cabin = travelAgent.findCabin(1);
cabin.setBedCount(4);
travelAgent.updateCabin(cabin);
The TRavelAgentBean.updateCabin( ) method takes its cabin parameter and merges it back into the current persistence context of the entity manager by calling the merge( ) operation:
@PersistenceContext EntityManager entityManager;
@TransactionAttribute(REQUIRED)
public void updateCabin(Cabin cabin) {
Cabin copy = entityManager.merge(cabin);
}
The changes made by the remote Swing client will now be reflected in persistence storage when the entity manager decides to flush to the database. The following rules apply when merging
in the cabin parameter of the updateCabin( ) method:
If the entity manager isn't already managing a Cabin instance with the same ID, a full copy of the cabin parameter is made and returned from the merge( ) method. This copy is managed by the entity manager, and any additional setter methods called on this copy will be synchronized with the database when the EntityManager decides to flush. The cabin parameter remains detached and unmanaged. If the entity manager is already managing a Cabin instance with the same primary key, then the contents of the cabin parameter are copied into this managed object instance. The merge( ) operation will return this managed instance. The cabin parameter remains detached and unmanaged.
The merge( ) method will throw an IllegalArgumentException
if its parameter is not an entity type. The transactionRequiredException
is thrown if this method is invoked on a transaction-scoped persistence context. However, if the entity manager is an extended persistence context, it is legal to invoke this method outside of a transaction scope and the update will be queued until the persistence context interacts with a transaction.
Removing Entities
An entity can be removed from the database by calling the EntityManager.remove( ) method. The remove( ) operation does not immediately delete the cabin from the database. When the entity manager decides to flush, based on the flush rules described later in this chapter, an SQL DELETE is executed:
@PersistenceContext EntityManager entityManager;
@TransactionAttribute(REQUIRED)
public void removeCabin(int id) {
Cabin cabin = entityManager.find(Cabin.class, id);
entityManager.remove(cabin);
}
After remove( ) is invoked, the cabin instance will no longer be managed and will become detached. Also, if the entity has any relationships to other entity objects, those may also be removed depending on the cascading rules discussed in chapter-7. The remove( ) operation can be undone only by re-creating the entity instance using the persist( ) method.
The remove( ) method throws an IllegalArgumentException if its parameter is not an entity type. The TRansactionRequiredException is thrown if this method is invoked on a transaction-scoped persistence context. However, if the EntityManager is an extended persistence context, it is legal to invoke this method outside of a transaction scope and the remove will be queued until the persistence context interacts with a transaction.
refresh( )
If you are concerned that a current managed entity is not up-to-date with the database, then you can use the EntityManager.refresh( ) method. The refresh( ) method refreshes the state of the entity from the database, overwriting any changes made to that entity:
@PersistenceContext EntityManager entityManager;
@TransactionAttribute(REQUIRED)
public void removeCabin(int id) {
Cabin cabin = entityManager.find(Cabin.class, id);
entityManager.refresh(cabin);
}
If the entity bean has any related entities, those entities may also be refreshed, depending on the cascade policy set up in the metadata of the entity mapping.
The refresh( ) method throws an IllegalArgumentException
if its parameter is not managed by the current entity manager instance. The transactionRequiredException
is thrown if this method is invoked on a transaction-scoped persistence context. However, if the entity manager is an extended persistence context, it is legal to invoke this method outside of a transaction scope. If the object is no longer in the database because another thread or process removed it, then this method will throw an EntityNotFoundException
.
contains( ) and clear( )
The contains( ) method takes an entity instance as a parameter. If this particular object instance is currently being managed by the persistence context, it returns true. It throws an IllegalArgumentException if the parameter is not an entity.
If you need to detach all managed entity instances from a persistence context, you can invoke the clear( ) method of the EntityManager. Be aware that when you call clear( ) any changes you have made to managed entities are lost. It is wise to call flush( ) before clear( ) is invoked so you don't lose your changes.
flush( ) and FlushModeType
When you call persist( ), merge( ), or remove( ), these changes are not synchronized with the database until the entity manager decides to flush. You can force synchronization anytime by calling flush( ). By default, flushing automatically happens before a correlated query is executed (inefficient implementations may even flush before any query) and at transaction commit time. The exception to this default rule is find( ). A flush does not need to happen when find( ) or getreference( ) is called because finding by a primary key is not something that would be affected by any updates.
You can control and change this default behavior by using the javax.persistence.FlushModeType
enumeration:
public enum FlushModeType {
AUTO,
COMMIT
}
AUTO is the default behavior described in the preceding code snippet. COMMIT means that changes are flushed only when the transaction commits, not before any query.
You can set the FlushModeType
by calling the setFlushMode( ) method on the EntityManager.
Why would you ever want to change the FlushModeType? The default flush behavior makes a lot of sense. If you are doing a query on your database, you want to make sure that any updates you've made within your transaction are flushed so that your query will pick up these changes. If the entity manager didn't flush, then these changes may not be reflected in the query. Obviously, you want to flush changes when a transaction commits.
FlushModeType.COMMIT
makes sense for performance reasons. The best way to tune a database application is to remove unnecessary calls to the database. Some vendor implementations will do all required updates with a batch JDBC call. If the updateBeds( ) method used the default, FlushModeType.AUTO
, then an SQL UPDATE
would happen when each query executed. Using COMMIT allows the entity manager to execute all updates in one huge batch. Also, an UPDATE usually ends up in the row being write-locked. Using COMMIT limits the amount of time the transaction holds on to this database lock by holding it only for the duration of the JTA
commit.
Locking
The EntityManager API supports both read and write locks. Because locking behavior is closely related to the concept of transactions, using the lock( ) method is discussed in detail in Chapter 16.
getDelegate( )
The getdelegate( ) method allows you to obtain a reference to the underlying persistence provider object that implements the EntityManager interface. Most vendors will have API extensions to the EntityManager interface that can be executed by obtaining and typecasting this delegate object to a provider's proprietary interface. In theory, you should be able to write vendor-independent code, but in practice, most vendors provide a lot of extensions to Java Persistence that you may want to take advantage of in your applications. The getdelegate( ) method provides one way to access vendor-specific APIs.
 |