介绍
对我来说,最令人困惑和不清楚的事情之一是,作为Java开发人员,一直是围绕事务管理的谜团,尤其是JPA如何处理事务管理。 事务什么时候开始,什么时候结束,实体的持久化方式,持久性上下文等等。 诸如Spring之类的框架也无助于理解概念,因为它们提供了另一层抽象,这使事情难以理解。 在今天的帖子中,我将尝试揭露JPA关于实体管理的规范,其事务规范以及如何更好地理解该概念如何帮助我们有效地设计和编码的某些秘密。 我们将努力保持讨论
尽管我们将同时研究Java SE(其中Java EE容器不可用)和基于Java EE的示例。
基本概念
在深入探讨更多细节之前,让我们快速遍历一些基础课程及其在JPA中的含义。
- EntityManager –管理实体的持久状态(或生命周期)的类。
- 持久性单元 –是实体类的命名配置。
- 持久性上下文 –是一组托管的实体实例。 实体类是持久性单元配置的一部分。
- 托管实体 –如果实体实例是持久性上下文的一部分,并且该实体管理器可以对其执行操作,则该实体实例将受到管理。
从上面的第一点和第三点,我们可以推断出实体管理器总是管理持久性上下文。 因此,如果我们了解持久性上下文,那么我们将了解EntityManager。
细节
JPA中的EntityManager
JPA中定义了EntityManager的三种主要类型。
- 容器管理和交易范围的实体管理器
- 容器管理和扩展范围实体管理器
- 应用程序管理的实体管理器
现在,我们将更详细地介绍其中的每一个。
容器管理的实体管理器
当应用程序的一个容器(例如Java EE容器或任何其他自定义容器,例如Spring)管理实体管理器的生命周期时,该实体管理器被称为“容器管理”。 获取容器管理的EntityManager的最常见方法是在EntityManager属性上使用@PersistenceContext批注。 这是定义EntityManager的示例。
public class EmployeeServiceImpl implements EmployeeService { @PersistenceContext(unitName="EmployeeService") EntityManager em; public void assignEmployeeToProject(int empId, int projectId) { Project project = em.find(Project.class, projectId); Employee employee = em.find(Employee.class, empId); project.getEmployees().add(employee); employee.getProjects().add(project); }
在上面的示例中,我们在EntityManager类型实例变量上使用了@PersistenceContext批注。 PersistenceContext批注具有属性“ unitName”,用于标识该上下文的持久性单元。
容器管理的实体管理器有两种形式:
- 交易范围的实体管理器
- 扩展范围实体管理器
请注意,上述范围实际上是指实体管理器管理的持久性上下文的范围。 它不是EntityManager本身的范围。
让我们依次查看它们中的每一个。
交易范围实体管理器
这是应用程序中最常用的实体管理器。 同样在上面的示例中,我们实际上是在创建事务作用域实体管理器。 每当解析由@PersistenceContext创建的引用时,都会返回事务作用域实体管理器。
使用事务作用域实体管理器的最大好处是它是无状态的。 这也使事务范围的EntityManager线程安全,因此实际上无需维护。 但是我们只是说EntityManager管理实体的持久性状态,而实体的持久性状态是注入EntityManager的持久性上下文的一部分。 那么,上述关于无国籍的说法如何呢?
答案在于所有容器管理的实体管理器都依赖于JTA事务。 每次在实体管理器上调用操作时,容器代理(容器在实例化时在实体管理器周围创建一个代理)都会检查JTA事务上是否存在任何持久性上下文。 如果找到一个,则实体管理器将使用此持久性上下文。 如果找不到,则将创建一个新的持久性上下文并将其与事务关联。
让我们以上面讨论的相同示例来了解实体管理器和事务创建的概念。
public class EmployeeServiceImpl implements EmployeeService { @PersistenceContext(unitName="EmployeeService") EntityManager em; public void assignEmployeeToProject(int empId, int projectId) { Project project = em.find(Project.class, projectId); Employee employee = em.find(Employee.class, empId); project.getEmployees().add(employee); employee.getProjects().add(project); }
在上面的示例中,assignEmployeeToProject方法的第一行正在EntityManager上调用find方法。 调用find将强制容器检查现有交易。 是否存在事务(例如,对于Java EE中的无状态会话Bean,容器在每次调用Bean上的方法时都保证事务可用)。 如果事务不存在,它将抛出异常。 如果存在,它将检查持久性上下文是否存在。 自从首次调用EntityManager的任何方法以来,持久性上下文尚不可用。 然后,实体管理器将创建一个并使用它来查找项目bean实例。
在下一个查找调用中,实体管理器已经具有关联的事务以及与之关联的持久性上下文。 它使用相同的事务来查找员工实例。 在该方法的第二行末尾,将同时管理项目和员工实例。 在方法调用结束时,将提交事务,并保留人员和员工的托管实例。 要记住的另一件事是,当事务结束时,持久性上下文消失了。
扩展范围实体管理器
如果并且当您希望持久性上下文在方法范围之外可用时,请使用具有扩展范围的实体管理器。 理解扩展范围实体管理器的最好方法是以一个类为例,该类需要维护某种状态(该状态是由于诸如myEntityManager.find(“ employeeId”)之类的事务请求而创建的,然后使用该雇员),并且通过各种业务方法共享状态。
因为Persistence Context在方法调用之间共享并且用于维护状态,所以除非您在有状态会话Bean中使用它们(容器负责使其变为线程安全),否则它通常不是线程安全的。 重申一下,如果您使用的是Java EE容器,则将在Stateful Session Bean(带有@Stateful注释的类)内使用扩展范围实体管理器。 如果您决定在有状态bean之外使用它,则该容器不能保证您可以安全地执行线程操作,而必须自己处理。 如果您使用像Spring这样的第三方容器,情况也是如此。
让我们看一下使用有状态会话Bean时Java EE环境中扩展作用域实体管理器的示例。
该示例中的目标是创建一个业务类,该业务类具有在LibraryUser Entity实例上工作的业务方法。 让我们将此业务类称为LibraryUserManagementService,它具有业务接口UserManagementService。 LibraryUserManagementService在LibraryUsers实体实例上工作。 图书馆可以将多本书借给LibraryUser。
这是描述上述情况的有状态会话Bean的示例。
@Stateful
public class LibraryUserManagementService implements UserManagementService { @PersistenceContext(unitName="UserService") EntityManager em; LibraryUser user; public void init(String userId) { user = em.find(LibraryUser.class, userId); } public void setUserName(String name) { user.setName(name); } public void borrowBookFromLibrary(BookId bookId) { Book book = em.find(Book.class, bookId); user.getBooks().add(book); book.setLendingUser(user); } // ... @Remove public void finished() { }
}
在上面使用用户实例的情况下,更自然的是先获得一个实例,然后逐步进行操作,只有完成后,我们才应保留用户实例。 但是,问题在于实体管理器是事务范围的。 这意味着init将在其自己的事务中运行(因此具有其自己的持久性上下文),而roweBookFromLibrary将在其自己的事务中运行。 结果,init方法一结束,用户对象就变得不受管理。
为了确切地解决此类问题,我们使用PersistenceContextType.EXTENDED类型的实体管理器。
这是带有PersistenceContextType EXTENDED的修改后的示例,可以很好地工作。
@Stateful
public class LibraryUserManagementService implements UserManagementService { @PersistenceContext(unitName="UserService" , type=PersistenceContextType.EXTENDED) EntityManager em;LibraryUser user; public void init(String userId) { user = em.find(LibraryUser.class, userId); } public void setUserName(String name) { user.setName(name); } public void borrowBookFromLibrary(BookId bookId) { Book book = em.find(Book.class, bookId); user.getBooks().add(book); book.setLendingUser(user); } // ... @Remove public void finished() { }
}
在上述场景中,用于管理用户实例的PersistenceContext是由Java EE容器在Bean初始化时创建的,并且在调用完成的方法(在该时间提交事务)之前,该持久性一直可用。
应用范围的实体管理器
不是由容器而是由应用程序本身创建的实体管理器是应用程序范围的实体管理器。 为了使定义更清晰,每当我们通过在EntityManagerFactory实例上调用createEntityManager来创建实体管理器时,实际上是在创建应用程序范围的实体管理器。 所有基于Java SE的应用程序实际上都使用应用程序范围的实体管理器。 JPA为我们提供了一个Persistence类,该类用于最终创建应用程序范围的实体管理器。
以下是如何创建应用程序范围的EM的示例:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit"); EntityManager em = emf.createEntityManager();
请注意,要创建应用程序范围的EntityManager,在应用程序的META-INF文件夹中需要有一个persistence.xml文件。
EntityManager可以通过两种方式创建。 上面已经显示了一个。 创建EntityManager的另一种方法是将一组属性作为参数传递给
createEntityManagerFactory方法。
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit" , myProperties); EntityManager em = emf.createEntityManager();
如果要创建自己的应用程序托管实体管理器,请确保在每次使用完它后都将其关闭。 这是必需的,因为您现在正在管理应如何以及何时创建和使用EntityManager。
交易管理
交易与实体直接相关。 实质上,管理事务意味着要管理实体生命周期(创建,更新,删除)的管理方式。 理解事务管理的另一个关键是要了解持久性上下文如何与事务交互。 值得注意的是,从最终用户的角度来看,即使我们使用EntityManager的实例,EntityManager的唯一作用是确定持久性上下文的生存期。 它在决定持久化上下文的行为时不起作用。 重申一下,持久性上下文是一组实体实例的托管集合。 每当事务开始时,Persistence Context实例都会与之关联。 当事务结束时(例如,提交),持久性上下文将被刷新并与事务解除关联。
JPA支持两种类型的事务管理类型。
- 资源本地交易
- JTA或全球交易
资源本地事务是指JDBC驱动程序的本机事务,而JTA事务是指JEE服务器的事务。 资源本地事务涉及单个事务资源,例如JDBC连接。 每当在单个事务中需要两个或多个资源(例如JMS连接和JDBC连接)时,都可以使用JTA事务。
容器管理的实体管理器始终使用JTA事务,因为容器负责事务生命周期管理并在多个事务资源中生成事务。 应用程序管理的实体管理器可以使用资源本地事务或JTA事务。
通常,在JTA或全局事务中,第三方事务监视器会在事务中获取不同的事务资源,为提交做准备,最后提交事务。 首先准备事务资源(通过空运行)然后提交(或回滚)的过程称为两阶段提交。
有关XA协议的附带说明 –在全球交易中,交易监视器必须不断与不同的交易资源进行对话。 不同的交易资源会说不同的语言,因此交易监视器可能无法理解。 XA是一个协议规范,为事务监视器与不同的事务资源进行交互提供了通用基础。 JTA是使用XA的全球事务监控器规范,因此能够管理多个事务资源。 兼容Java EE的服务器具有内置的JTA实现。其他容器(例如Spring)可以自己编写或使用其他实现(例如Java Open Transaction Manager,JBoss TS等)来支持JTA或全局事务。
持久性上下文,事务和实体管理器
持久性上下文可以与单个或多个事务关联,也可以与多个实体管理器关联。 持久性上下文已向事务注册,以便在提交事务时可以刷新持久性上下文。 事务启动时,实体管理器将查找活动的持久性上下文实例。 如果不可用,它将创建一个并将其绑定到事务。 通常,持久性上下文的范围与事务紧密相关。 当事务结束时,与该事务关联的持久性上下文实例也结束。 但是有时,在大多数情况下,在Java EE世界中,我们需要事务传播,这是在单个事务中的不同实体管理器之间共享单个持久性上下文的过程。
持久性上下文可以有两个范围:
- 事务范围的持久性上下文
- 扩展范围的持久性上下文
我们已经讨论了事务/扩展范围的实体管理器,并且我们也知道实体管理器可以是事务或扩展范围的。 关系不是偶然的。 事务范围的实体管理器创建事务范围的持久性上下文。 扩展范围实体管理器使用扩展持久性上下文。 扩展持久性上下文的生命周期与Java EE环境中的有状态会话Bean有关。
让我们简要讨论一下这些持久性上下文
事务范围的持久性上下文
TSPC仅在需要时由实体管理器创建。 仅当首次调用实体管理器上的方法时,事务作用域实体管理器才创建TSPC。 因此,持久性上下文的创建是懒惰的。 如果已经存在传播的持久性上下文,则实体管理器将使用该持久性上下文。
了解持久性上下文传播对于识别和调试代码中与事务相关的问题非常重要。 让我们看一个如何传播事务范围的持久性上下文的示例。
ItemDAOImpl.java:
public class ItemDAOImpl implements ItemDAO { @PersistenceContext(unitName="ItemService") EntityManager em; LoggingService ls; @TransactionAttribute()public void createItem(Item item) { em.persist(item); ls.log(item.getId(), "created item"); } // ...
}
LoggingService.java:
public class LoggingService implements AuditService { @PersistenceContext(unitName="ItemService") EntityManager em; @TransactionAttribute()public void log(int itemId, String action) { // verify item id is valid if (em.find(Item.class, itemId) == null) { throw new IllegalArgumentException("Unknown item id"); } LogRecord lr = new LogRecord(itemId, action); em.persist(lr); } }
调用ItemDAOImpl的createItem方法时,将在实体管理器实例上调用persist方法。 假设这是对实体管理器方法的第一次调用。 实体管理器将查找单元名称为“ ItemService”的任何传播的持久性上下文。 它找不到一个,因为这是对实体管理器的第一个调用。 因此,它创建了一个新的持久性上下文实例并将其附加到自身。 然后,它继续保存Item对象。 持久化项目对象后,我们调用以记录刚刚持久化的项目信息。 请注意,LoggingService有其自己的EnitityManager实例,方法日志中具有注释@TransactionAttribute(如果在Java EE envt中并且将bean声明为EJB,则不需要此注释)。
由于TransactionAttribute的默认TransactionAttributeType为REQUIRED,因此LoggingService中的实体管理器将查找以前的事务中可能可用的任何持久性上下文。 它找到一个在ItemDAOImpl的createItem方法内部创建的对象,并使用相同的对象。 这就是为什么即使实际项目尚未持久到数据库(因为尚未提交事务),LoggingService中的实体管理器仍能够找到它,因为持久性上下文已从ItemDAOImpl传播到LoggingService 。
扩展持久性上下文
事务范围持久性上下文是为每个事务创建的(在不传播的情况下),而扩展持久性上下文仅创建一次,并由管理扩展持久性上下文的生命周期的类范围内的所有事务使用。 对于Java EE,由状态会话Bean管理扩展的持久性上下文的生命周期。 有状态会话bean的创建是EAGER。 如果是容器托管事务,则在类上的方法被调用后立即创建它。 对于应用程序管理的事务,将在调用userTransaction.begin()时创建。
摘要
在这篇博客文章中,已经讨论了很多东西,实体管理器,事务管理,持久性上下文,所有这些东西如何相互作用和相互配合。
我们讨论了容器管理的和应用程序管理的实体管理器,事务范围和扩展范围持久性上下文,事务传播之间的区别。 该博客的大部分材料是阅读精彩书籍: Pro JPA 2的结果 。 如果您想更深入地了解JPA的工作原理,我建议您阅读它。
翻译自: https://www.javacodegeeks.com/2013/06/jpa-2-entitymanagers-transactions-and-everything-around-it.html