操作EntityManager

来源:互联网 发布:库存管理优化 编辑:程序博客网 时间:2024/04/28 16:47
 

操作EntityManager

Interacting with an EntityManager

现在你已经学会如何部署和获取指向EntityManager的引用了,接下来你将学习如何正确地操作EntityManager。EntityManager API包含了插入和删除实体的数据库操作方法,将游离的实体实例合并更新到数据库的方法。它还包含了一组丰富的查询API,你可以藉此来创建查询对象。

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

对实体进行持久化就是将其插入到数据库中。你所持久化的是还未曾保存到数据库中的实体。要持久化一个实体,首先是为实体的实例分配内存,然后设置成员属性,并设置好与其他对象可能存在的任何关联关系。换言之,你可以像操作普通Java对象那样初始化一个entity bean。一旦完成这一步,你就可以调用EntityManager.persist()方法来保存该实体了。

Custom cust = new Customer();

cust.setName("Bill");

entityManager.persist(cust);

当调用persist()方法后,EntityManager会将Customer添加到等待数据库插入的队列中,对象实例即处于托管状态。实际的插入操作何时发生则取决于多种因素。如果在事务范围内调用了persist()方法,插入操作可能马上执行,也可能在事务提交时执行,这依赖于flush模式(本章的后续部分会讲到)的取值。任何时候,你都可以通过调用flush()方法在一个事务内强制手工插入。当且仅当entity manager是extended persistence context时,你才可以在事务范围外调用persist()方法。此时,插入操作会被保存到队列中,直到persistence context与某个事务关联之后才被执行。一个由EJB容器注入的extended persistence context会自动与JTA事务关联。而对于通过EntityManagerFactory API手动创建的extended context,你必须调用Entity.Manager.joinTransaction()方法才能使之与事务关联。

如果实体与其他实体存在任何关联关系,且正确设置了级联策略(cascade policies),关联实体同样可以被保存到数据库中。我们将在第7章讨论级联。在第6章里,我们还将看到当persist()方法被调用时,Java Persistence能够自动生成主键。因此在上例中,如果你启用了自动主键生成功能,在persist()方法执行完毕后,你将会看到生成的主键。

如果传入 persist()方法的参数不是实体类型的,persist()方法将抛出Illegal- ArgumentException异常。在transaction-scoped persistence context里,事务范围外的persist()调用会引起TransactionRequiredException异常。而在extended persistence context中,事务范围外的persist()调用则是合法的;不过在persistence context与事务关联之前,数据库插入操作是不会被执行的。

查找实体

Finding Entities

EntityManager提供了两种在数据库中查找对象的机制:一种是使用entity manager提供的简单方法根据主键在数据库中查找实体,另一种则是创建查询对象并执行查询。

find()和getReference()方法

EntityManager提供了两个不同的方法允许你通过主键来查找实体。

public interface EntityManager {

   <T> T find(Class<T> entityClass, Object primaryKey);

   <T> T getReference(Class<T> entityClass, Object primaryKey);

}

这两个方法都接受实体的 class和代表实体主键的对象作为参数。由于它们使用了Java泛型方法,无需任何显示的类型转换即可获得特定类型的实体对象。那么,我们该如何区别使用这两个方法呢?在无法从数据库中找到指定实体时,find()方法会返回null。它还会根据延迟加载策略(lazy-loading,详见第6章相关讨论)初始化entity bean的内部状态。

Customer cust = entityManager.find(Customer.class, 2);

在本例中,我们查找一个主键 ID值为2的Customer对象。find()方法期望第二个参数的类型为Object,而非上述的基本数据类型,那么它是怎么通过编译的呢?此处,我们用到了Java 5的一个新特性,称作自动装箱(autoboxing),它将基本数据类型直接转换为相应的对象类型。因此,常量2实际上是被转换成了 java.lang.Integer类型。

Customer cust = null;

try {

   cust = entityManager.getReference(Customer.class, 2);

} catch (EntityNotFoundException notFound) {

   // 恢复逻辑

}

getReference() 方法与find()方法的不同之处在于:如果在数据库中找不到相应的实体,getReference()方法将抛出 javax.persistence.EntityNotFoundException异常;并且该方法并不保证返回实例的内部状态会被初始化。

如果传入的参数不是实体类型,find()方法和getReference()方法都会抛出Illegal- ArgumentException异常。你可以在事务范围之外调用这两个方法,其行为依据persistence context的不同而有所不同:若EntityManager是transactionscoped persistence context,则会返回游离对象;而若是extended persistence context,则返回托管对象。

查询

持久对象也可以通过EJB QL来查询。与EJB 2.1不同的是,此处不再有任何finder方法了,你必须通过调用EntityManager的createQuery()、createNamedQuery()或createNativeQuery()方法来创建Query对象进行查询。

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);

}

创建并执行EJB QL查询与创建并执行JBDC PreparedStatement非常相似。

Query query = entityManager.createQuery("from Customer c where id=2");

Customer cust = (Customer)query.getSingleResult();

我们将在第9章详细介绍查询和EJB QL。

所有由find(),getResource()或查询对象所返回的实体实例都将保持托管状态,直至其所在的persistence context被关闭为止。亦即,期间再次调用find()或者任何其他的方法,都将返回同样的实体对象实例。

更新实体

Updating Entities

一旦你调用了find(),getReference(),或创建并执行了一次查询,所得的entity bean实例在persistence context关闭前仍将处于托管状态。在此期间,你可以像其他对象那样随便更改entity bean实例的状态,任何更改都将被自动(取决于flush模式)或手工(通过调用flush()方法)地同步到数据库中。

@PersistenceContext EntityManager entityManager;

@TransactionAttribute(REQUIRED)

public void updateBedCount(int id, int newCount) {

   Cabin cabin = entityManager.find(Cabin.class, id);

   cabin.setBedCount(newCount);

}

在这段代码中,persistence context一直与某个事务关联,因此find()方法返回的Cabin实例是受EntityManager托管的。亦即,你可以更改对象实例,一旦EntityManager决定将更改从内存同步到数据库时,数据库便会被自动更新。

合并实体

Merging Entities

在Java Persistence中,你可以使用EntityManager的merge()方法,将游离实体的状态变更合并到数据库中。假设有一个远程Swing客户端,它调用了TravelAgent session bean的远程方法,用以查找数据库中的cabin实体。

@PersistenceContext EntityManager entityManager;

@TransactionAttribute(REQUIRED)

public Cabin findCabin(int id) {

   return entityManager.find(Cabin.class, id);

}

在本例中,由于findCabin()方法处于一个独立的JTA事务中,persistence context将在方法结束时被EJB容器关闭。当Cabin实例被序列化时,它会脱离entity manager的管理并被送到远程的Swing客户端。此时,该Cabin实例是一个普通的Java对象,并且不再关联于任何entity manager。你可以像对待普通Java对象那样,调用该对象的getters和setters。Swing客户端更改了这一Cabin实例的状态,然后将其重新送回服务器。

Cabin cabin = travelAgent.findCabin(1);

cabin.setBedCount(4);

travelAgent.updateCabin(cabin);

TravelAgentBean.updateCabin()方法接受cabin实例作为参数,并通过merge()方法将它合并到当前EntityManager管理的persistence context中。

@PersistenceContext EntityManager entityManager;

@TransactionAttribute(REQUIRED)

public void updateCabin(Cabin cabin) {

   Cabin copy = entityManager.merge(cabin);

}

由远程Swing客户端所作的修改将在EntityManager决定与数据库进行同步时被写回到数据库中。updateCabin()方法在合并cabin时遵循下列规则。

若EntityManager未曾管理过与传入的cabin参数有着相同ID的Cabin实例,则 merge()方法会创建该参数的一份完整拷贝并将其作为方法的返回值。该份拷贝受entity manager管理,并且任何针对该份拷贝的setter方法调用所引起的状态更改,在EntityManager决定执行flush操作时,都会被同步到数据库中。而传入的cabin参数仍将保持游离状态,不受托管。

若与传入的cabin参数有着相同主键的Cabin实例早已处于EntityManager的管理之中,则cabin参数的内容将被复制到托管的对象实例中。merge()方法将返回该托管

实例。而传入的cabin参数将仍然保持游离状态,不受托管。

同样,如果传入merge()方法的参数不是实体类型,merge()方法将会抛出Illegal- ArgumentException异常;如果该方法在transaction-scoped persistence context范围外调用,则会引起TransactionRequiredException异常;而在extended persistence context中,事务范围外的merge()调用是允许的。但是,在persistence context重新与事务关联之前,更新操作都不会被同步到数据库中。

删除实体

Removing Entities

调用EntityManager.remove()可以将实体从数据库中删除。不过,remove()方法并不立即生效,而是在EntityManager决定执行flush操作时,根据定义好的flush规则(将在本章的后续部分讨论),才会执行SQL DELETE操作。

@PersistenceContext EntityManager entityManager;

@TransactionAttribute(REQUIRED)

public void removeCabin(int id) {

   Cabin cabin = entityManager.find(Cabin.class, id);

   entityManager.remove(cabin);

}

调用remove()方法之后,cabin实例将不再受entity manager托管而成为游离对象。如果其他实体对象与该对象存在关联关系,则这些对象也将依据级联规则(将在第7章中讨论)被相应删除。只有通过调用 persist()方法重建实体实例,remove()操作才可以被撤销(undone)。

传入remove()方法的参数若不是实体类型,remove()方法将抛出IllegalArgument- Exception异常;如果该方法在transaction-scoped persistence context范围外调用,则会引起TransactionRequiredException异常;而在extended persistence context中,事务范围外的remove()调用是允许的。但是,在persistence context重新与事务关联之前,数据是不会从数据库中删除的。

refresh()

refresh()

如果发现当前受托管的实体并非数据库中的最新数据,你可以调用EntityManager. refresh()方法。refresh()方法会根据数据库的情况刷新内存中实体的状态,同时覆盖对实体所做的任何修改。

@PersistenceContext EntityManager entityManager;

@TransactionAttribute(REQUIRED)

public void refreshCabin(int id) {

   Cabin cabin = entityManager.find(Cabin.class, id);

   entityManager.refresh(cabin);

}

如果entity bean有关联实体,那么根据在实体映射元数据中设置的级联策略,那些关联实体也会被相应刷新。

传入refresh()方法的参数若不是实体类型,refresh()方法将抛出IllegalArgument- Exception异常;在transaction-scoped persistence context范围外的refresh()调用则会引起TransactionRequiredException异常;而在extended persistence context中,事务范围外的remove()调用是允许的。如果需要刷新的实体对象在数据库中不存在(例如被其他线程或进程删除了),则会抛出 EntityNotFoundException异常。

contains()方法与clear()方法

contains() and clear()

contains()方法接受实体实例作为参数,如果该对象实例目前正受persistence context管理,则返回true。若该参数并非实体类型,则会抛出IllegalArgumentException异常。

你可以使用EntityManager.clear()方法将persistence context中所有的托管实体都变成游离对象。需要注意的是,一旦你调用了clear()方法,对实体所做的任何修改都将被丢弃。因此,使用 clear()时需要格外小心。在调用clear()之前,先调用flush()方法以免丢失更改实为明智之举。

flush()方法和FlushModeType

flush() and FlushModeType

正如上文所述,调用persist(),merge(),remove()方法之后,这些更改操作只有在 EntityManager决定执行flush操作时才会被同步到数据库中。你也可以在任何时候通过调用flush()方法做强行同步。缺省情况下,flush操作会在相关查询执行之前和事务提交之时自动执行(一些低效的Java Persistence实现甚至可能会在任何查询执行之前都做flush操作)。但需注意的是,与一般查询不同,调用find()和 getReference()方法并不会引起flush。这是因为通过主键来查询实体是不会受任何更新操作的影响的。

可以通过枚举类型javax.persistence.FlushModeType来控制和修改这一默认行为。

public enum FlushModeType {

   AUTO,

   COMMIT

}

AUTO是前述的默认行为。COMMIT则表示,仅当事务提交时才对更改做flush操作,而

在任何查询执行之前都不会引发flush操作。你可以通过调用setFlushMode()方法来指定EntityManager的FlushModeType。

可是我们为什么需要手工指定FlushModeType呢?默认的 flush行为听起来很有道理:如果你正在查询数据库,一定想要确保在事务中所做的任何更新都被flush到数据库。这样,查询结果才会反映这些更新。否则,更新就可能不会体现在查询结果中;而在事务提交时,很显然,你希望对更改做flush操作。

尽管如此,出于性能的考虑,FlushModeType.COMMIT 也是有用武之地的。数据库应用调优的最好办法就是减少不必要的数据库访问。一些Java Persistence实现可以在一次batch JDBC调用中执行所有必要的更新操作。而如果updateBeds()方法使用缺省的Flush- ModeType.AUTO模式,那么执行每次查询时都会执行相应的SQL UPDATE。而使用COMMIT,则允许entity manager在一次批处理调用中执行所有的更新操作。这会大大减少数据库访问的次数。此外,UPDATE通常会使记录处于写锁定状态(write- locked)。使用COMMIT模式时,对数据库的锁定只在JTA事务提交期间发生,从而减少了事务对数据库占用的总体时间。

锁定

Locking

EntityManager API同时支持读锁和写锁,由于锁定行为与事务的概念紧密关联,我们将在第16章详细讨论lock()方法的使用。

getDelegate()

getDelegate()

getDelegate()方法允许你获得一个指向底层 persistence provider对象的引用,该persistence provider实现了EntityManager接口。大多数厂商都提供了针对EntityManager接口的API扩展,为了使用这些扩展功能,你可以将获取到的delegate对象强制类型转换为厂商的私有接口。虽然从理论上讲,你应该可以编写出厂商无关的代码。但实际上,多数厂商都提供了大量针对Java Persistence的扩展,你可以在应用程序中充分利用这些功能。而getDelegate()方法提供了一种获取并使用厂商专有API的途径。

原创粉丝点击