一、前言
本文仅代表作者的个人观点;
本文的内容仅限于技术探讨,不能作为指导生产环境的素材;
本文素材是红帽公司产品技术和手册;
本文分为系列文章,将会有多篇,初步预计将会有9篇。
二、EJB的生命周期
应用程序中的EJB组件在应用程序服务器内的容器上下文(也就是EJB container)中运行。 EJB容器负责管理EJB的生命周期(创建,执行和销毁)。 每种不同类型的EJB(无状态、有状态、单例、MDB)都有其自己的生命周期。
三种不同的状态:
2.无状态session Bean生命周期
两种不同的状态(因为是无状态的,所以不会有Passivated的状态):
Does Not Exist: 无状态EJB未创建,并且不存在于应用程序服务器内存中。
Ready: 无状态EJB(对象)通过JNDI调用或CDI注入在应用程序服务器内存中创建,并准备好让客户端调用其业务方法。
3. Singleton会话Bean生命周期
两种不同的状态:
Does Not Exist:单例未被创建,并且不存在于应用程序服务器内存中。
Ready:单启动EJB(单个对象)在启动时或在CDI注入时在应用程序服务器内存中创建,并准备好让其客户端调用其业务方法。
由于在其生命周期内只有一个EJB实例,因此没有概念池。 对bean的并发访问策略可以通过部署描述符或代码级别注释来控制。
因此,EJB的全生命周期描述如下:
Bean Type | Annotation | Description |
---|---|---|
Stateful Session Bean | @PostConstruct | method is invoked when a bean is created for the first time. |
@PreDestroy | method is invoked when a bean is destroyed. | |
@PostActivate | method is invoked when a bean is loaded to be used after activation. | |
@PrePassivate | method is invoked when a bean is about to be passivated. | |
Stateless Session Bean | @PostConstruct | method is invoked when a bean is created for the first time. |
@PreDestroy | method is invoked when a bean is removed from the bean pool or is destroyed. | |
Singleton Session Bean | @PostConstruct | method is invoked when a bean is created for the first time. |
@PreDestroy | method is invoked when a bean is destroyed. | |
@Startup | The application server instantiates the singleton at startup. |
三、隐式和显式事务
典型的Java EE企业应用程序,通常会访问、操作一个或多个持久数据存储中的数据,这些数据通常在关系数据库里(RDBMS)。 存储在这些数据库中的关键业务数据通常由多个应用程序同时访问。因此确保数据完整性至关重要。 事务通过控制对数据的并发访问来确保数据的完整性,并确保失败的业务事务不会使系统处于不一致或无效的状态。
事务是一系列必须作为单个原子单位执行的动作。所谓原子性,说白了就是要么事务执行成功,要么失败,不存在成功一半的情况。
接下来,我们举个订单管理系统的例子:
客户下订单(PlaceOrder)时必须执行一系列操作。
Java EE标准定义了Java Transaction API(JTA),它为运行在Java EE兼容应用程序服务器上的应用程序提供事务管理。此API为应用程序中的提交和回滚事务提供了一个方便的高级界面。例如,如果Java持久性API(JPA)与JTA一起使用,则开发人员不必在应用程序源码中编写跟踪SQL提交和回滚语句。 JTA API以独立于数据库的方式处理这些操作。
JTA有两种不同的方式来管理Java EE中的事务:
四、隐式事务管理,又称容器管理事务(CMT)
在CMT中,应用程序服务器隐式地在EJB方法开始时开始事务、并在方法结束时提交事务,除非出现错误或异常。在出现错误或者异常的情况下,会自动触发应用程序服务器的回滚。CMT中,不允许在单个bean方法中嵌套事务。开发人员可以使用称为“事务属性”的注释来覆盖方法级别的默认事务行为。
使用CMT的EJB不得使用任何与应用程序服务器的事务范围和边界冲突的JTA API方法。例如,不能使用诸如commit(),setAutoCommit()和rollback()之类的JTA方法,或者java.sql.Connection之类的JDBC类以及来自javax.jms.Session的commit()和rollback()方法。如果需要显式控制事务流,则必须使用Bean管理的事务。另外,使用CMT的EJB不能使用javax.transaction.UserTransaction接口。
设置交易属性
在CMT中,事务属性控制事务的范围,并允许开发人员在EJB中的各个方法级别声明性地管理事务。
例如,我们查看下面的代码,它描述的是一个无状态EJB从另一个无状态EJB调用方法:
@Stateless
public class TodoService { @Inject
UserService user;
public void login(String user, String password) {
user.authenticate(user,password);
}
...
}
@Stateless
public class UserService {
public boolean authenticate(String user, String password) {
...
}
...
}
@Inject可以注入包含EJB的任何bean,而@EJB只能注入EJB。
事务属性可以用来控制执行UserService类方法的范围和上下文。
Java EE规范定义了六个事务属性。下面参考上面的代码片段来说明它们:
@TransactionAttribute(TransactionAttributeType.REQUIRED)
如果login()方法在事务中运行并调用UserService类中的authenticate()方法,则authenticate()将在同一事务中执行。如果在调用authenticate()时没有事务,则应用程序服务器在执行authenticate()之前启动新的事务。这是默认的事务属性,除非用其他事务属性注释明确覆盖。
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
如果login()方法在事务中运行,并调用UserService类中的authenticate()方法,则应用程序服务器在执行authenticate()之前挂起事务并启动新的事务。一旦authenticate()完成执行,控制就移回到login()并且暂停的事务恢复。如果在调用authenticate()时没有事务,则应用程序服务器在执行authenticate()之前启动新的事务。该属性确保我们的方法始终以新事务运行。
@TransactionAttribute(TransactionAttributeType.MANDATORY)
如果login()方法在事务中运行并调用UserService类中的authenticate()方法,则authenticate()将在同一事务中执行。如果在调用authenticate()时没有事务,则应用程序服务器将引发TransactionRequiredException。如果我们希望方法始终在调用客户端的事务上下文中执行,请使用此属性。
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
如果login()方法在事务中运行并调用UserService类中的authenticate()方法,则应用程序服务器挂起事务并在没有任何事务上下文的情况下运行authenticate()。一旦authenticate()完成执行,控制就移回到login()并且暂停的事务恢复。如果在调用authenticate()时没有事务,则应用程序服务器在执行authenticate()之前不会启动新的事务。将此属性用于不需要事务的方法。
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
如果login()方法在事务中运行并调用authenticate(),则authenticate()将在同一事务中执行。如果在调用authenticate()时没有事务,则应用程序服务器在执行authenticate()之前不会启动新的事务。
@TransactionAttribute(TransactionAttributeType.NEVER)
如果login()方法在事务中运行并调用UserService类中的authenticate()方法,则应用程序服务器将引发RemoteException。如果在调用authenticate()时没有事务,则应用程序服务器在执行authenticate()之前不会启动新的事务。
设置交易属性
通过使用javax.ejb.TransactionAttribute注释注释EJB类或方法并将其设置为javax.ejb.TransactionAttributeType枚举常量之一来声明事务属性。 如果使用@TransactionAttribute在类级别注释EJB,则指定的属性适用于EJB中的所有方法。 使用@TransactionAttribute注解特定方法仅将该属性应用于该方法。 方法级事务属性声明覆盖类级声明。
考虑以下具有多种方法的EJB类:
@Stateless
public class TodoEJB {
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void createTodo(TodoItem item) {
...
}
@TransactionAttribute(TransactionAttributeType.NEVER)
public List<TodoItem> listTodos() {
...
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void logCreateTodo(TodoItem item) {
...
}
createTodo()方法使用@TransactionAttribute(TransactionAttributeType.REQUIRED)事务属性进行注释,而listTodos()方法使用
@TransactionAttribute(TransactionAttributeType.NEVER)事务属性进行注释,因为此方法只执行一个只读操作 该数据库并不插入,删除或更新任何数据。
logCreateTodo()方法使用@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)事务属性进行注释,该属性确保始终使用新事务执行此方法,即使它是从使用其自己的事务上下文执行的另一个方法调用的。
五、显式事务管理,又Bean管理事务(BMT)
在需要对事务的开始和结束时间进行细粒度控制并控制何时执行和回滚的情况下,可以使用Bean Managed Transactions(BMT)。 我们可以使用javax.transaction.UserTransaction接口中的begin(),commit()和rollback()方法来显式地控制事务边界和范围。
在使用BMT的应用程序中,不应使用javax.ejb.EJBContext接口的getRollbackOnly()和setRollbackOnly()方法。 改为使用javax.transaction.UserTransaction接口的getStatus()和rollback()方法。
具有bean管理事务的示例EJB如下所示:
1表示这个类是一个无状态的EJB。
2将此EJB标记为使用@TransactionManagement(TransactionManagementType.BEAN)批注进行管理的Bean。
3注入UserTransaction对象。 这用于在此EJB中开始,提交和回滚事务。
4Begin开始交易。
5如果所有方法都没有任何错误地成功执行,请提交事务。
6如果由于某种故障而出现异常,请执行回滚事务。
六、实验验证:配置Bean管理事务(BMT)
在本实验中,我们使用无状态EJB并将其更新为使用Bean管理的事务。
首先,用IDE导入一个已经存在的maven项目:
查看调用EJB的JSF页面:bean-transactions→src→main→webapp文件夹,然后双击index.xhtml文件。
下面这段源码表示:在Web form提交(在web界面点击subbmit)时调用表达式语言(EL)值#{hello.sayHello()}。
接下来,查看JSF页面使用的请求范围的Hello backing bean。
src/main/java→ com.redhat.training.ui参看: Hello.java,这个源码定义的是一个叫hello的public class:
而该源码中定义对htmlaction bean的定义位置如下:sayhello:
此EJB使用CDI注入PersonService EJB,这是添加事务逻辑的地方。
我们接下来,查看Hello.java CDI注入的bean的class源码:PersonService.java
我们看到,此EJB已标记为@Stateless,但目前不包含任何事务管理。
hello()方法为每个在UI中输入名称并返回包含当前日期和时间的问候的人员在数据库中创建一个新条目(下面标黄部分,显示返回值是hello + 输入值的拼接,以及当前的时间)。
public String hello(String name) {
try {
// TODO start a new transaction
// let's grab the current date and time on the server
LocalDateTime today = LocalDateTime.now();
// format it nicely for on-screen display
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm:ss a");
String fdate = today.format(format);
// Create a new Person object and persist to database
Person p = new Person();
p.setName(name);
entityManager.persist(p);
// respond back with Hello and convert the name to UPPERCASE. Also, send the
// current time on the server.
return "Hello " + name.toUpperCase() + "!. " + "Time on the server is: " + fdate;
} catch(Exception e) {
//TODO roll-back the transaction
throw new EJBException(e);
}
}
}
接下来,我们把PersonService修改成使用Bean管理的事务,在源码中增加如下一行:
刚增加的注释会阻止容器管理事务并允许EJB手动管理事务。
添加以下代码,以使用资源注入将UserTransaction类的实例注入到EJB中以进行手动事务管理:
@Resource告诉容器分配一个新的事务对象并在运行时将其注入到这个EJB中。
添加以下代码以提交事务:
添加以下代码以在发生异常时回滚事务:
接下来,启动EAP:
使用Maven使用以下命令在JBoss EAP上部署应用程序:
通过浏览器访问刚部署的应用:
输入david,点击submit,输出为Hello DAVID:
而上图中蓝色部分的输出值,其在业务逻辑实现就是:
// current time on the server. return "Hello " + name.toUpperCase() + "!. " + "Time on the server is: " + fdate;
因此,这个实验的整体代码调用关系如下:
魏新宇