想写这篇文章是因为看了网上相关的几篇文章没有一篇能真正说清楚如何正确地在WEB容器中获取持久化上下文 要么根本就不知道如何获取要么存在极度大的性能问题要么存在线程安全性的隐患
在EJB容器如果你愿意由容器注入一个EntityManager后你可以把一切交给容器管理如果你愿意使用可扩展事务 当然你已经了解如何管理EntityManager规范在那儿定着没有什么可多说的 在Java SE环境下没有容器能帮助你做任何事一切都要由你纯手工创建当然一切也由你负责管理关闭销毁都是你的事所以反而变得简单(是说获取持久化手段简单了不是操作单了)
一容器管理持久化上下文
而在WEB容器中一方面容器管理的持久化事务不能象EJB容器那样可以使用扩展性事务要想让容器管理那么只能是 JTA事务而且数据源也一定是JTADATASOURCE常有人在网上找到一些persistencexml要么使用了本地事务要么是非JTA的数据源问我为什么在WEB容器中不能成功注EntityManager
要在WEB容器中让容器管理持久化事务可以通过两种方式一是容器注入一是JNDI查找这两种方式并不是每个人都理解的我看到一本叫<<EJB JPA数据库持久化编程详解>>的书在网上受到太多的人的追捧(越是这样的书受害者就越多!) 作者明确说容器托管的EntityManager只能运行在EJB容器中也就是说只有在EJBJAR包中才可以获得容器托管的 EntityManager对象否则只能获得应用托管的EntityManager对象
事实上确实一些WEB容器不能注入EntityManager也不能通过JNDI查找到但是容器不支持并不是规范不支持如果你使用glassfish或者用resin 以上(目前已经)你就可以方便地获得容器管理的EntityManager
<?xml version= encoding=UTF?>
<persistence xmlns=
xmlns:xsi=instance
xsi:schemaLocation= persistence__xsd
version=>
<persistenceunit name=jpaUnit transactiontype=JTA>
<jtadatasource>jdbc/__axman</jtadatasource>
<class>comaxmanCustomerEO</class>
</persistenceunit>
</persistence>
<?xml version= encoding=UTF?>
<persistence xmlns=
xmlns:xsi=instance
xsi:schemaLocation= persistence__xsd
version=>
<persistenceunit name=jpaUnit transactiontype=JTA>
<jtadatasource>jdbc/__axman</jtadatasource>
<class>comaxmanCustomerEO</class>
</persistenceunit>
</persistence>只要这个persistence xml文件在类路径中就可以正确地注入EntityManager:
//为了阅读方便删除了无关内容和注释
<PRE class=java name=code>public class TestAxman extends HttpServlet {
@PersistenceContext(name=jpaUnit)
private EntityManager em;
@Resource
private UserTransaction utx;
protected void service(HttpServletRequest request HttpServletResponse response)
throws ServletException IOException {
PrintWriter out = responsegetWriter();
CustomerEO ce = new CustomerEO();
cesetName(p);
cesetEmail();
cesetAsset(d);
cesetCreateDate(new javasqlDate(new javautilDate()getTime()));
utxbegin();
empersist(ce);
mit();
//容器管理事务不需要自己手工回滚只要告诉容器事务起止边界
}
}
</PRE>
public class TestAxman extends HttpServlet {
@PersistenceContext(name=jpaUnit)
private EntityManager em;
@Resource
private UserTransaction utx;
protected void service(HttpServletRequest request HttpServletResponse response)
throws ServletException IOException {
PrintWriter out = responsegetWriter();
CustomerEO ce = new CustomerEO();
cesetName(p);
cesetEmail();
cesetAsset(d);
cesetCreateDate(new javasqlDate(new javautilDate()getTime()));
utxbegin();
empersist(ce);
mit();
//容器管理事务不需要自己手工回滚只要告诉容器事务起止边界
}
}
public class TestAxman extends HttpServlet
{
@PersistenceContext(name=jpaUnit)
private EntityManager em;
@Resource
private UserTransaction utx;
protected void service(HttpServletRequest request HttpServletResponse response)
throws ServletException IOException {
PrintWriter out = responsegetWriter();
CustomerEO ce = new CustomerEO();
cesetName(p);
cesetEmail();
cesetAsset(d);
cesetCreateDate(new javasqlDate(new javautilDate()getTime()));
utxbegin();
empersist(ce);
mit();
//容器管理事务不需要自己手工回滚只要告诉容器事务起止边界
}
}
这样注入进来的EntityManager完全由容器管理不要做任何EntityManager相关的工作但是一切就这样完美了吗? 当然不是和EJB容器不同的是相当于有状态会话BEAN的Servlet是多线程服务的一个实例变量的EntityManager 完全可能会被多个线程同时访问而出现极大的安全性隐患那么这样的注入是否有意义呢? 当然有意义一种情况是你可以在ServletContextListener这样的线程安全模块中注入另一种情况只要你能控制不让多个线程同时访问一个实例变量的EntityManager那么你就可以享受容器管理带来的方便性
但是任何事物都有两面性如果你要获取方便就要牺牲应用范围的控制和性能要让多个线程不同时访问一个实例变量EntityManager最终还是要进行同步或互斥即一个线程使用变量EntityManager时其它线程都要等待如果能在线程的local方法中(doXXX或父类的service方法中)获取由容器管理的EntityManager 那将会大大提高程序的性能 容器管理的意思其实就是容器产生了一些对象你只需要拿来使用不需要负责它的产生和销毁关键是容器产生了这样的对象后你如何拿到通过注入只能注入成实例字段那么在线程方法中可能通过JNDI 来即时获取容器中的EntityManager(实事上只要改一下容器实现的源码还可以通过在service方法中获取ServletContext对象来动态即时注入但这对于普通程序员是做不到的)
通过JNDI查找的jndiref也可以通过注释或通过webxml配置两种方法都可以: 注入jndiref:
@PersistenceContext(name=persistence/jpaUnitunitName=jpaUnit)
public class TestAxman extends HttpServlet {
protected void service(HttpServletRequest requestHttpServletResponse response)
throws ServletException IOException {
Context env =(Context)newInitialContext()lookup(java:comp/env);
EntityManager em = (EntityManager)envlookup(persistence/jpaUnit);
Query query = emcreateQuery(SELECT c from CustomerEO c);
List <CustomerEO> ls = querygetResultList();
}
}
@PersistenceContext(name=persistence/jpaUnitunitName=jpaUnit)
public class TestAxman extends HttpServlet {
protected void service(HttpServletRequest requestHttpServletResponse response)
throws ServletException IOException {
Context env =(Context)newInitialContext()lookup(java:comp/env);
EntityManager em = (EntityManager)envlookup(persistence/jpaUnit);
Query query = emcreateQuery(SELECT c from CustomerEO c);
List <CustomerEO> ls = querygetResultList();
}
}如果你不想用注释可以在webxml中配置:
<persistencecontextref>
<persistencecontextrefname>persistence/jpaUnit</persistencecontextrefname>
<persistenceunitname>jpaUnit</persistenceunitname>
</persistencecontextref>
<persistencecontextref>
<persistencecontextrefname>persistence/jpaUnit</persistencecontextrefname>
<persistenceunitname>jpaUnit</persistenceunitname>
</persistencecontextref>然后可以同样通过
Context env =(Context)new InitialContext()lookup(java:comp/env);
EntityManager em = (EntityManager)envlookup(persistence/jpaUnit); 查找到EntityManager
需要说明的是JNDI查看本身是耗时的特别是new InitialContext()所以应该在一个全局的位置创建一个env (相当于工场)然后在service方法中通过这个env来查找EntityManager就可以大量地提升性能比如:
public class TestAxman extends HttpServlet {
Context env = null;
protected void init(ServletConfig config) throws ServletException {
env = (Context)newInitialContext()lookup(java:comp/env);
}
protected void service(HttpServletRequest request HttpServletResponse response)
throws ServletException IOException {
EntityManager em = (EntityManager)envlookup(persistence/jpaUnit);
Query query = emcreateQuery(SELECT c from CustomerEO c);
List <CustomerEO> ls = querygetResultList();
}
}
public class TestAxman extends HttpServlet {
Context env = null;
protected void init(ServletConfig config) throws ServletException {
env = (Context)newInitialContext()lookup(java:comp/env);
}
protected void service(HttpServletRequest request HttpServletResponse response)
throws ServletException IOException {
EntityManager em = (EntityManager)envlookup(persistence/jpaUnit);
Query query = emcreateQuery(SELECT c from CustomerEO c);
List <CustomerEO> ls = querygetResultList();
}
}
<PRE class=java name=code>如果你需要容器管理持久化这种方案是最合适的方式虽然EntityManager 每次要在service中lookup出来但它是</PRE>
<PRE class=java name=code>方法内的local变量不象注入成实例变量那样存在多线程安全隐患</PRE>
如果你需要容器管理持久化这种方案是最合适的方式虽然EntityManager 每次要在service中lookup出来但它是 如果你需要容器管理持久化这种方案是最合适的方式虽然EntityManager 每次要在service中lookup出来但它是
方法内的local变量不象注入成实例变量那样存在多线程安全隐患 方法内的local变量不象注入成实例变量那样存在多线程安全隐患
二应用管理持久化上下文:
应用管理持久化上下文事实上就是EntityManager对象不是由容器负责产生和销毁而是由应用程序来创建当然是 由应用程序来销毁要由应用程序来创建持久化上下文就是要由EntityManagerFactory来进行createEntityManager 本着谁生产谁负责的原则当然要程序来负责销毁所以应用管理的EntityManager一定要在finally语句中调用close() 方法这样多少给我们使用它带来不便但它也因为是应用程序创建所以有着广泛的应用范围无论是EJB容器还是WEB 容器或者是纯Java SE环境都可以使用JPA功能
要在WEB容器获取EntityManagerFactory同样可以通过注入和手工创建明白容器管理的意义应该知道注入 是容器已经产生了的对象所以EntityManagerFactory如果是容器注入的同样不需要你手工销毁而如果是手工 创建的则需要手工销毁简单说EntityManagerFactory对象本身也可以容器管理的:
public class TestServlet extends HttpServlet {
@PersistenceUnit(unitName = jpaUnit)
private EntityManagerFactory emf;
public void service(……) throws xxxExceotion{
EntityManager em = emf createEntityManager();
try{
//invoke em;
}
finally{ emclose();} // EntityManager本身是应用程序创建的
//所以必须手工关闭如果这里是写入操作事务还必须在cacth块中手工回滚
}
}
public class TestServlet extends HttpServlet {
@PersistenceUnit(unitName = jpaUnit)
private EntityManagerFactory emf;
public void service(……) throws xxxExceotion{
EntityManager em = emf createEntityManager();
try{
//invoke em;
}
finally{ emclose();} // EntityManager本身是应用程序创建的
//所以必须手工关闭如果这里是写入操作事务还必须在cacth块中手工回滚
}
}如果你不想通过容器注入EntityManagerFactory只要调用
EntityManagerFactory emf = PersistencecreateEntityManagerFactory(jpaUnit);
就可以获取一个手工创建的EntityManagerFactory但是要记得在创建它的对应位置手要销毁它如:
public class TestServlet extends HttpServlet {
private EntityManagerFactory emf;
void init(){
emf = PersistencecreateEntityManagerFactory(jpaUnit);
}
void destory(){
if(emf != null) emfclose();
}
public void service() throws xxxExceotion{
EntityManager em = emf createEntityManager();
try{
//invoke em;
}
finally{ emclose();} // EntityManager本身是应用程序创建的
//所以必须手工关闭
}
}
public class TestServlet extends HttpServlet {
private EntityManagerFactory emf;
void init(){
emf = PersistencecreateEntityManagerFactory(jpaUnit);
}
void destory(){
if(emf != null) emfclose();
}
public void service() throws xxxExceotion{
EntityManager em = emf createEntityManager();
try{
//invoke em;
}
finally{ emclose();} // EntityManager本身是应用程序创建的
//所以必须手工关闭
}
}因为EntityManagerFactory是工场对象所以上面的例子并不好最好的位置是在SerlvetContextListener中注入然后放在 ServletContext中或在ContextListener的contextInitialized中手工生成在contextDestroyed中销毁生成后放入 ServletContext中供全局访问即一个应用只有一个工场而EntityManager是在service方法中通过EntityManagerFactory 即时生成的这样既可以提高性能又保证了线程安全性可以说是一个非常正确的方案
同样上面的那本书中在Servlet的init方法中生成一个实例对象EntityManagerFactory后在doPost方法中获取 EntityManager本来是一个非常好的方案但作者却说是不安全的非要用一个辅助的类来调用ThreadLocal来在doPost方法中获取EntityManager难道不同线程的doPost方法内的local变量也能被其它线程访问?纯是蛇足之举