在WEB容器中获取持久化上下文
想写这篇文章,是因为看了网上相关的几篇文章,没有一篇能真正说清楚如何正确地在WEB容器中获取持久化上下文, 要么根本就不知道如何获取,要么存在极度大的性能问题,要么存在线程安全性的隐患.
在EJB容器,如果你愿意,由容器注入一个EntityManager后,你可以把一切交给容器管理.如果你愿意使用可扩展事务, 当然你已经了解如何管理EntityManager.规范在那儿定着,没有什么可多说的. 在Java SE环境下,没有容器能帮助你做任何事,一切都要由你纯手工创建,当然一切也由你负责管理,关闭,销毁.都是你的事,所以反而变得简单(是说获取持久化手段简单了,不是操作单了).
一.容器管理持久化上下文
而在WEB容器中,一方面容器管理的持久化事务不能象EJB容器那样可以使用扩展性事务,要想让容器管理,那么只能是 JTA事务,而且数据源也一定是JTA-DATA-SOURCE.常有人在网上找到一些persistence.xml,要么使用了本地事务,要么是非JTA的数据源,问我为什么在WEB容器中不能成功注EntityManager.
要在WEB容器中让容器管理持久化事务,可以通过两种方式,一是容器注入,一是JNDI查找.这两种方式并不是每个人都理解的,我看到一本叫<<EJB JPA数据库持久化编程详解>>的书,在网上受到太多的人的追捧(越是这样的书受害者就越多!), 作者明确说"容器托管的EntityManager只能运行在EJB容器中,也就是说只有在EJB-JAR包中,才可以获得容器托管的 EntityManager对象,否则只能获得应用托管的EntityManager对象."
事实上确实一些WEB容器不能注入EntityManager,也不能通过JNDI查找到.但是容器不支持并不是规范不支持,如果你使用glassfish,或者用resin 3.1.2以上(目前已经3.1.6)你就可以方便地获得容器管理的EntityManager.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd"
version="1.0">
<persistence-unit name="jpaUnit" transaction-type="JTA">
<jta-data-source>jdbc/__axman</jta-data-source>
<class>com.axman.CustomerEO</class>
</persistence-unit>
</persistence>
只要这个persistence .xml文件在类路径中,就可以正确地注入EntityManager:
//为了阅读方便删除了无关内容和注释
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 = response.getWriter();
CustomerEO ce = new CustomerEO();
ce.setName("p4");
ce.setEmail("p4@email.com");
ce.setAsset(12.7d);
ce.setCreateDate(new java.sql.Date(new java.util.Date().getTime()));
utx.begin();
em.persist(ce);
utx.commit();
//容器管理事务不需要自己手工回滚,只要告诉容器事务起止边界
}
}
这样注入进来的EntityManager完全由容器管理,不要做任何EntityManager相关的工作.但是一切就这样完美了吗? 当然不是,和EJB容器不同的是,相当于有状态会话BEAN的Servlet是多线程服务的,一个实例变量的EntityManager 完全可能会被多个线程同时访问而出现极大的安全性隐患.那么这样的注入是否有意义呢? 当然有意义,一种情况是你可以在ServletContextListener这样的线程安全模块中注入,另一种情况只要你能控制不让多个线程同时访问一个实例变量的EntityManager,那么你就可以享受容器管理带来的方便性.
但是任何事物都有两面性,如果你要获取方便,就要牺牲应用范围的控制和性能,要让多个线程不同时访问一个实例变量EntityManager,最终还是要进行同步或互斥.即一个线程使用变量EntityManager时其它线程都要等待.如果能在线程的local方法中(doXXX或父类的service方法中)获取由容器管理的EntityManager, 那将会大大提高程序的性能. "容器管理"的意思,其实就是容器产生了一些对象,你只需要拿来使用,不需要负责它的产生和销毁,关键是容器产生了这样的对象后你如何"拿到".通过注入只能注入成实例字段,那么在线程方法中,可能通过JNDI 来即时获取"容器中的EntityManager".(实事上,只要改一下容器实现的源码,还可以通过在service方法中获取ServletContext对象来动态即时注入,但这对于普通程序员是做不到的).
通过JNDI查找的jndi-ref也可以通过注释或通过web.xml配置.两种方法都可以: 注入jndi-ref:
@PersistenceContext(name="persistence/jpaUnit",unitName="jpaUnit")
public class TestAxman extends HttpServlet {
protected void service(HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {
Context env =(Context)newInitialContext().lookup("java:comp/env");
EntityManager em = (EntityManager)env.lookup("persistence/jpaUnit");
Query query = em.createQuery("SELECT c from CustomerEO c");
List <CustomerEO> ls = query.getResultList();
}
}
如果你不想用注释可以在web.xml中配置:
<persistence-context-ref>
<persistence-context-ref-name>persistence/jpaUnit</persistence-context-ref-name>
<persistence-unit-name>jpaUnit</persistence-unit-name>
</persistence-context-ref>
然后可以同样通过
Context env =(Context)new InitialContext().lookup("java:comp/env");
EntityManager em = (EntityManager)env.lookup("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)env.lookup("persistence/jpaUnit");
Query query = em.createQuery("SELECT c from CustomerEO c");
List <CustomerEO> ls = query.getResultList();
}
}
如果你需要容器管理持久化,这种方案是最合适的方式.虽然EntityManager 每次要在service中lookup出来,但它是
方法内的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{ em.close();} // EntityManager本身是应用程序创建的,
//所以必须手工关闭.如果这里是写入操作,事务还必须在cacth块中手工回滚
}
}
如果你不想通过容器注入EntityManagerFactory,只要调用
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaUnit");
就可以获取一个手工创建的EntityManagerFactory,但是要记得在创建它的对应位置手要销毁它.如:
public class TestServlet extends HttpServlet {
private EntityManagerFactory emf;
void init(......){
emf = Persistence.createEntityManagerFactory("jpaUnit");
}
void destory(......){
if(emf != null) emf.close();
}
public void service(......) throws xxxExceotion{
EntityManager em = emf. createEntityManager();
try{
//invoke em;
}
finally{ em.close();} // EntityManager本身是应用程序创建的,
//所以必须手工关闭.
}
}
因为EntityManagerFactory是工场对象,所以上面的例子并不好,最好的位置是在SerlvetContextListener中注入然后放在 ServletContext中,或在ContextListener的contextInitialized中手工生成,在contextDestroyed中销毁,生成后放入 ServletContext中供全局访问.即一个应用只有一个工场,而EntityManager是在service方法中通过EntityManagerFactory 即时生成的,这样既可以提高性能,又保证了线程安全性.可以说是一个非常正确的方案.
同样上面的那本书中,在Servlet的init方法中生成一个实例对象EntityManagerFactory后,在doPost方法中获取 EntityManager,本来是一个非常好的方案,但作者却说是不安全的,非要用一个辅助的类来调用ThreadLocal来在doPost方法中获取EntityManager.难道不同线程的doPost方法内的local变量也能被其它线程访问?纯是蛇足之举.
分享到:
相关推荐
3. **配置SessionManager**:在每个Tomcat服务器的应用上下文(`context.xml`)文件中,配置`Manager`元素。使用`org.apache.catalina.session.PersistentManager`或类似的第三方实现,如`org.springframework....
会话配置允许调整会话持久化和复制策略,异步请求分派用于处理高并发场景,传输链配置涉及网络层优化,而定制属性可以适应特定的企业需求。Web服务器配置则包括SSL证书、虚拟主机等高级设置。Web模块属性,如上下文...
在Spring配置中可以启用会话DAO,以便持久化会话信息。 7. **启动Shiro**:在Spring的初始化方法中启动Shiro,例如使用`@PostConstruct`注解的方法。 8. **测试与调试**:编写单元测试和集成测试,确保Shiro的认证...
在本项目中,SQL Server作为数据存储平台,与EF结合,提供数据持久化支持。 这个压缩包文件可能包含了实现上述功能的代码示例,包括API控制器、Swagger配置、JWT认证中间件、Autofac的注册配置、数据库上下文和领域...
通过对实体、持久化单元和持久化上下文的理解,开发者可以更高效地利用 JPA 来管理应用程序中的数据。此外,JPA 还支持事务管理、线程模型注入等功能,进一步增强了其在复杂 Web 应用场景下的可用性。
扩展的`EntityManager`则与有状态会话bean绑定,保持持久化上下文直至bean被删除。 ### 结论 JPA是Java开发中强大的数据持久化工具,通过面向对象的方式简化了数据库操作。理解和掌握JPA的核心概念,如实体、实体...
Application对象,也称为ServletContext对象,它代表了整个Web应用程序的上下文。它在整个Web应用程序的生命周期内都存在,不局限于单个用户的会话。在上述案例中,我们看到了如何使用Application对象来读写属性值...
- **流程管理的持久化上下文传播**:支持在不同HTTP请求间传播持久化上下文。 - **Portlet 2.0资源请求支持**:增强了对Portlet 2.0的支持。 - **自定义对话管理器**:允许开发者自定义对话生命周期的管理机制。 - *...
在这个过程中,`applicationContext.xml`文件扮演着至关重要的角色,它是Spring应用上下文的配置文件,定义了bean的创建、依赖关系以及服务的配置。 首先,让我们了解Spring框架的基本概念。Spring是一个开源的Java...
学习实体、持久化上下文、查询语言(HQL)是JPA的重点。 7. **框架应用**:Spring MVC或Struts等框架极大地简化了Java Web开发。了解这些框架的基本原理、配置和使用方式,可以帮助你更高效地开发应用。 8. **安全与...
- **conversationScope**:会话作用域,支持会话间的持久化。 - **requestParameters**:访问请求参数。 - **currentEvent**:当前事件的上下文。 - **currentUser**:当前登录的用户。 - **messageContext**:消息...
2. **状态持久化**:SWF自动处理了会话状态的保存和恢复,无论用户是在流程中中断还是在多个请求之间跳转,都能保持一致的用户体验。 3. **模型绑定**:SWF支持模型对象与表单字段的自动绑定,简化了数据验证和转换...
- **上下文(Context)**:Flask维护了一个请求上下文,其中包含请求相关的信息,如请求方法、URL参数等。 - **扩展(Extensions)**:Flask有丰富的第三方扩展,如Flask-SQLAlchemy用于数据库操作,Flask-WTF用于...
- `<context-root>`:设置Web应用的上下文根,即在URL中应用的路径部分。 - `<work-dir>`:指定Web应用的工作目录,用于存储临时文件和编译后的JSP页面。 - `<naming:web-container>`:定义Web容器的配置,包括安全...
学生应了解实体类、持久化上下文和查询语言(JPQL)。 9. **JSF(JavaServer Faces)**:一种用于构建用户界面的JavaEE技术,可以简化开发过程。尽管现在不那么流行,但理解其基本原理仍然有益。 10. **Web容器**...
3. **配置web.xml**:配置Struts的ActionServlet和Spring的DispatcherServlet,以及初始化Spring上下文。 4. **配置struts-config.xml**:定义Action、ActionMapping和Form Bean。 5. **配置spring-beans.xml**:...
持久性无知(Persistence Ignorance)和有界上下文(Bounded Contexts)的概念在书中也有提及。持久性无知是一种设计原则,它使得代码不依赖于具体的持久性技术(如数据库),从而使得代码更具有可移植性和可测试性...
3. **服务支持**:JBoss内核支持多种Java EE服务,如JNDI(Java Naming and Directory Interface)、JTA(Java Transaction API)和JPA(Java Persistence API),为Web应用程序提供数据持久化、事务处理和目录服务...
1. **EJB规范相关的JAR**:如`ejb3-persistence.jar`,它包含了EJB 3.x规范中的持久化API;`jboss-ejb3-api_3.2_spec.jar`,提供了EJB 3.2规范的接口定义。 2. **JBoss运行时库**:比如`jboss-all-client.jar`,它...