EJB 3 In Action - Using EJB with JPA and CDI - Managing entities

87 阅读9分钟

image.png

Introducing EntityManager

It manages the lifecycle of entities.

EntityManager interface

In a sense, the EntityManager is the bridge between the OO (object-oriented) and relational worlds.

EntityManager is used to perform CRUD operations. Here are the most commonly used methods of the EntityManager interface.
Method signatureDescription
public void persist(Object entity);Saves (persists) an entity into the database and makes the entity managed.
public <T> T merge(T entity);Merges an entity to the EntityManager’s persistence context and returns the merged entity.
public void remove(Object entity);Removes an entity from the database.
public <T> T find(Class<T> entityClass, Object primaryKey);Finds an entity instance by its primary key. This method is overloaded and comes in different forms.
public void flush();Synchronizes the state of entities in the EntityManager’s persistence context with the database.
public void setFlushMode(FlushModeType flushMode);Changes the flush mode of the EntityManager’s persistence context. The flush mode may be AUTO or COMMIT. The default flush mode is AUTO, meaning that the EntityManager tries to automatically sync the entities with the database.
public FlushModeType getFlushMode();Retrieves the current flush mode.
public void refresh(Object entity);Refreshes (resets) the entities from the database. This method is overloaded and comes in different forms.
public Query createQuery(String jpqlString);Creates a dynamic query using a JPQL statement. This method is overloaded and comes in different forms.
public Query createNamedQuery(String name);Creates a query instance based on a named query on the entity instance. This method is overloaded and comes in different forms.
public Query createNativeQuery(String sqlString);Creates a dynamic query using a native SQL statement. This method is overloaded and comes in different forms.
public StoredProcedureQuery createStoredProcedureQuery(String procedureName);Creates a StoredProcedureQuery for executing a stored procedure. This method is overloaded and comes in different forms.
public void close();Closes an application-managed EntityManager.
public void clear();Detached all managed entities from the persistence context. All changes made to entities not committed will be lost.
public boolean isOpen();Checks whether an EntityManager is open.
public EntityTransaction getTransaction();Retrieves a transaction object that can be used to manually start or end a transaction.
public void joinTransaction();Asks an EntityManager to join an existing JTA transaction.

Lifecycle of an entity

Managed entities

image.png

Bid bid = new Bid();
manager.persist(bid);

Detached entities

manager.remove(bid);

Persistence context, scopes, and the EntityManager

The EntityManager delegates the task of managing the entity state to the currently available persistence context.

In a very simple sense, a persistence context is a self-contained collection of entities managed by an EntityManager during a given persistence scope. The persistence scope is the duration of time a given set of entities remains managed.

There are two different types of persistence scopes: transaction and extended.

Transaction-scoped EntityManager

An EntityManager associated with a transaction-scoped persistence context is known as a transaction-scoped EntityManager . If a persistence context is under transaction scope, entities attached during a transaction are automatically detached when the transaction ends.

image.png

Extended-scope EntityManager

The lifespan of the extended EntityManager lasts across multiple transactions. An extended-scoped EntityManager can only be used with stateful session beans and lasts as long as the bean instance is alive.

Once an entity is attached in any given transaction, it’s managed for all transactions in the lifetime of the persistence context image.png

Using EntityManager

@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface PersistenceContext {
  public String name() default "";
  public String unitName() default "";
  public PersistenceContextType type() default PersistenceContextType.TRANSACTION;
  public PersistenceProperty[] properties() default {};
}
  • name: specifies the JNDI name of the persistence context. This element is used in the unlikely case that you must explicitly mention the JNDI name for a given container implementation to be able to look up an EntityManager . In most situations, leaving this element empty is fine, except when you use @PersistenceContext at the class level to establish a reference to the persistence context.
  • unitName: element specifies the name of the persistence unit. A persistence unit is a grouping of entities used in an application. The idea is useful when you have a large Java EE application and would like to separate it into several logical areas (think Java packages).

Persistence units can’t be set up using code; you must configure them through the persistence.xml deployment descriptor.

@PersistenceContext(unitName="admin")
EntityManager entityManager

You aren’t allowed to use extended persistence scope for stateless session beans or MDBs. The reason is pretty obvious, because the purpose of the extended scope is to extend across method invocations on a bean, even if each method invocation is a separate transaction. Because neither stateless session beans nor MDBs are supposed to implement such functionality, it makes no sense to support extended scope for these bean types. On the other hand, extended persistence scope is ideal for stateful session beans. An underlying EntityManager with extended scope could be used to cache and maintain the application domain across an arbitrary number of method invocations from the client. More importantly, this can be done without giving up on method-level transaction granularity (most likely using CMT).

EntityManager and Thread-Safety

EntityManagers aren’t thread-safe and shouldn’t be used in situations where more than one thread may access them. This means that it’s dangerous to use EntityManagers in servlets or JSP pages. A servlet is instantiated once and handles multiple requests concurrently.

**It’s best to use EntityManagers from within EJBs. **

If you must access the EntityManager directly, you can use the following code snippet or access the EntityManagerFactory:

@PersistenceContext(name="pu/actionBazaar" unitName="ActionBazaar")
public class ItemServlet extends HttpServlet {
  @Resource
  private UserTransaction ut;
  public void service(HttpSerlvetRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    Context ctx = new InitialContext();
    EntityManager em = (EntityManager)ctx.lookup("java:comp/env/pu/actionBazaar");
    ...
    ut.begin();
    em.persist(item);
    ut.commit();
    ...
  }
}

The other alternative is to use an application-managed EntityManager with a JTA transaction. It’s worth noting that EntityManagerFactory is thread-safe.

Injecting the EntityManagerFactory

Using the EntityManagerFactory , you can get EntityManager instances that you can fully control.

image.png

The EntityManagerFactory annotation is defined as follows:

@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface PersistenceUnit {
  String name() default "";
  String unitName() default "";
}

image.png

The name and unitName elements serve exactly the same purpose as they do for the @PersistenceContext annotation. Whereas the name element can be used to point to the JNDI name of the EntityManagerFactory , the unitName element is used to specify the name of the underlying persistence unit.

Persistence operations

Persisting entities

image.png

image.png

Retrieving entities by key

  • By primary key
Seller seller = entityManager.find(Seller.class, sellerId);
  • Find by primary key using composite keys
SellerPK sellerKey = new SellerPK();
sellerKey.setFirstName(firstName);
sellerKey.setLastName(lastName);

Seller seller = entityManager.find(Seller.class, sellerKey);

Entity fetch mode

JPA has more than one mechanism to support lazy fetching. Specifying column fetch-mode using the @Basic annotation is the easiest one to understand. For example, you can set the fetch mode for the picture property on the Item entity to be lazy as follows:

@Column(name="PICTURE")
@Lob
@Basic(fetch=FetchType.LAZY)
public byte[] getPicture() {
    return picture;
}

A SELECT statement generated by the find method to retrieve Item entities won’t load data from the ITEMS.PICTURE column into the picture field. Instead, the picture data will be automatically loaded from the database when the property is first accessed through the getPicture method.

Loading related entities

EntityManager’s find method must retrieve all entities related to the one returned by the method.

image.png

When the find method returns an instance of an Item, it also automatically retrieves the Seller , Bid, and Category entities associated with the instance and populates them into their respective Item entity properties.

image.png

By default, some of the relationship types are retrieved lazily and some are loaded eagerly.

The Seller associated with an Item is retrieved eagerly, because the fetch mode for the @ManyToOne annotation is defaulted to EAGER.

image.png

As the listing shows, an eager fetch means that the most natural way of retrieving the Item entity would be through a single SELECT statement using a JOIN between the ITEMS and SELLERS tables. It’s important to note the fact that the JOIN will result in a single row, containing columns from both the SELLERS and ITEMS tables.

Pretty much the same thing applies to the @OneToOne annotation.

lazy versus eager loading of related entities

In contrast, the @OneToMany and @ManyToMany annotations are defaulted to lazy loading.

Behavior of loading an associated entity is different for each kind of association by default. You can change the loading behavior by specifying the fetch element with the relationship.
Relationship typeDefault fetch behaviorNumber of entities retrieved
One-to-oneEAGERSingle entity retrieved
One-to-manyLAZYCollection of entities retrieved
Many-to-oneEAGERSingle entity retrieved
Many-to-manyLAZYCollection of entities retrieved

Here’s an example of explicitly specifying the fetch mode for a relationship

@ManyToOne(fetch=FetchType.LAZY)
public Seller getSeller() {
    return seller;
}

Updating entities

Recall that the EntityManager makes sure that changes made to attached entities are always saved into the database behind the scenes. This means that for the most part, the application doesn’t need to worry about manually calling any methods to update the entity.

image.png

After each method is invoked, the EntityManager ensures that the changes are persisted to the database. Typically, all changes are persisted when the transaction ends and the EntityManager attempts to commit all the changes. But you can force persistence at any time by using the EntityManager flush method. When flush is called, it applies to all entities managed in the persistence context. Flushing is controlled by the FlushModeType.

  • FlushTypeMode.AUTO means to persist to the database at query execution time.
  • FlushTypeMode.COMMIT will persist to the database when the transaction ends and is committed.

Detachment and merge operations

This means that entities returned by the session bean to its clients are always detached, just like the newly created Item entity returned by the ItemManager’s add- Item method:

public Item addItem(String title, String description,
  byte[] picture, double initialPrice, long sellerId) {
  Item item = new Item();
  item.setTitle(title);
  ...
  entityManager.persist(item);
  return item;
}
  • An entity instance can be detached and serialized to a separate tier where the client makes changes to the entity and sends it back to the server. The server can use a merge operation to attach the entity to the persistence context. image.png
public Item updateItem(Item item) {
  entityManager.merge(item);
  return item;
}

As soon as the updateItem method returns, the database is updated with the data from the Item entity. The merge method must only be used for an entity that exists in the database. An attempt to merge a nonexistent entity will result in an IllegalArgumentException. The same is true if the EntityManager detects that the entity you’re trying to merge has already been deleted through the remove method, even if the DELETE statement hasn’t been issued yet.

Merging relationships

By default, entities associated with the entity being merged aren’t merged as well. For example, the Seller , Bid, and Category entities related to the Item aren’t merged when the Item is merged in the previous code snippet.

But this behavior can be controlled using the cascade element of the @OneToOne, @OneToMany , @ManyToOne, and @ManyToMany annotations. The cascade element is added to the annotation on the owning side of the relationship.

If the cascade element is set to either ALL or MERGE, the related entities are merged. For example, the following code will cause the Seller entity related to the Item to be merged because the cascade element is set to MERGE:

public class Item {
    @ManyToOne(cascade=CascadeType.MERGE)
    public Seller getSeller() {

Note that as in most of the EntityManager’s methods, the merge method must be called from a transactional context or it’ll throw a TransactionRequiredException.

Detached entities and the DTO anti-pattern

If you’ve spent even a moderate amount of time using EJB 2, you’re probably thoroughly familiar with the data transfer object (DTO) anti-pattern. In a sense, the DTO anti-pattern was necessary because of entity beans. The fact that EJB 3 detached entities are nothing but POJOs makes the DTO anti-pattern less of a necessity of life. Instead of having to create separate DTOs from domain data just to pass back and forth between the business and presentation layers, you may simply pass detached entities. This is exactly the model followed in this chapter.

But if your entities contain behavior, you might be better off using the DTO pattern anyway, to safeguard business logic from inappropriate usage outside a transactional context. In any case, if you decide to use detached entities as a substitute for DTOs, you should make sure they’re marked java.io.Serializable.

Deleting entites

An important detail to notice about the deleteItem method (repeated next) is that the Item to be deleted was first attached to the EntityManager using the merge method:

public void deleteItem(Item item) {
  entityManager.remove(entityManager.merge(item));
}

This is because the remove method can only delete currently attached entities and the Item entity being passed to the deleteItem method isn’t managed. If a detached entity is passed to the remove method, it throws an IllegalArgumentException. Before the deleteItem method returns, the Item record will be deleted from the database using a DELETE statement like this:

DELETE FROM ITEMS WHERE item_id = 1

Just as with the persist and merge methods, the DELETE statement isn’t necessarily issued immediately but is guaranteed to be issued at some point. Meanwhile, the EntityManager marks the entity as removed so that no further changes to it are synchronized (as we noted in the previous section).

Cascading remove operations

Just as with merging and persisting entities, you must set the cascade element of a relationship annotation to either ALL or REMOVE for related entities to be removed with the one passed to the remove method.

@Entity
public class Bidder {
  @OneToOne(cascade=CascadeType.REMOVE)
  public BillingInfo setBillingInfo() {

In general, you might find that the only relationship types where cascading removal makes sense are one-to-one and one-to-many. You should be careful when using the cascade delete because the related entity you’re cascading the delete to may be related to other entities you don’t know about.

Handling relationships

If your intent was really to cascade-delete the Items associated with the Seller , you should iterate over all instances of Category that reference the deleted Items and remove the relationships first, as follows:

List<Category> categories = getAllCategories();
List<Item> items = seller.getItems();
for (Item item: items) {
  for (Category category: categories) {
    category.getItems().remove(item);
  }
}
entityManager.remove(seller);

Entity queries

  • javax.persistence.Query—Represents either a JPQL or native SQL query. If a query is typed, a javax.persistence.TypeQuery<T> is returned, thus eliminating the need to cast results.
  • javax.persistence.StoredProcedureQuery—Represents a query that invokes a stored procedure.
  • javax.persistence.criteria.CriteriaQuery—Represents a query that’s constructed using the meta-model.

In addition to these types of queries, you also have dynamic and named queries.

  • Dynamic queries are queries created in code—the query is passed off to the EntityManager .
  • A named query is a query that’s either configured in an annotation or pulled from an ORM XML configuration file.

Dynamic queries

Dynamic queries are created on the fly and embedded within the code. Dynamic queries are typically only used once—that is, the query isn’t shared among multiple components in an application.

@PersistenceContext
private EntityManager entityManager;
public List<Category> findAllCategories() {
  TypedQuery<Category> query = entityManager.createQuery(
    "SELECT c FROM Category c",Category.class);
  return query.getResultList();
}

You can also use dynamic queries to execute both native SQL and stored procedure queries as well— with createNativeQuery and createStoredProcedureQuery, respectively.

Named queries

  • A named query must be created before it can be used.
  • It’s defined in the entity using annotations or in the XML file defining O/R mapping metadata.
  • A named query is accessed by its name, thus enabling its use across multiple components in an application.
  • A named query is defined using the @javax.persistence.NamedQuery annotation
@Entity
@NamedQuery(
  name = "findAllCategories",
  query = "SELECT c FROM Category c WHERE c.categoryName
    LIKE :categoryName ")
  public class Category implements Serializable {
    public List<Category> findAllCategories() {
      TypedQuery<Category> query =
       entityManager.createNamedQuery("findAllCategories",Category.class);
    }

  }
  
@Entity
@NamedQueries({
  @NamedQuery(
    name = "findCategoryByName",
    query = "SELECT c FROM Category c WHERE c.categoryName
      LIKE :categoryName order by c.categoryId"
  ),
  @NamedQuery(
    name = "findCategoryByUser",
    query = "SELECT c FROM Category c JOIN c.user u
      WHERE u.userId = ?1"
)})
@Table(name = "CATEGORIES")
public class Category implements Serializable {
  ...
}