重点来了:事务一致性的深入研究&EJB的全生命周期 | 从开发角度看应用架构5

一、前言

本文仅代表作者的个人观点;

本文的内容仅限于技术探讨,不能作为指导生产环境的素材;

本文素材是红帽公司产品技术和手册;

本文分为系列文章,将会有多篇,初步预计将会有9篇。

二、EJB的生命周期

应用程序中的EJB组件在应用程序服务器内的容器上下文(也就是EJB container)中运行。 EJB容器负责管理EJB的生命周期(创建,执行和销毁)。 每种不同类型的EJB(无状态、有状态、单例、MDB)都有其自己的生命周期。

  1. 有状态session bean

三种不同的状态:

  • Does Not Exist: 有状态EJB还未创建、并且不存在于应用程序服务器内存中。
  • Ready:有状态的EJB准备就绪有状态的EJB(对象)是通过JNDI调用或CDI注入在应用程序服务器内存中创建的,并且已准备好让其客户端调用其业务方法。
  • Passivated:由于有状态的EJB,具有在多个客户端调用中持久化的对象状态,因此应用程序服务器可能会将EJB钝化(停用)到辅助存储以优化内存消耗。 当客户端调用EJB上的任何方法时,它将激活EJB回到就绪状态。 开发人员不具有激活和钝化的任何直接控制权,并且应用程序服务器根据某些算法对其进行透明处理。

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)时必须执行一系列操作。

  1. 交易从执行saveOrder()方法开始,该方法将订单存储在订单数据库中。
  2. saveOrder()方法调用raisePurchaseOrder()方法,该方法在财务部门维护的另一个数据库中引发采购订单。
  3. 流程转到updateInventory()方法,该方法更新库存数据库,然后使用sendEmail()方法向客户发送电子邮件。
  4. 如果事务中的所有方法都没有任何错误或失败地执行,那么事务将被提交。例如,如果updateInventory()方法失败,则应用程序必须确保参与事务的以前方法(即raisePurchaseOrder()和saveOrder())的操作被逆转,并且整体状态的系统恢复到交易开始时的状态。这种逆转被称为事务回滚。

Java EE标准定义了Java Transaction API(JTA),它为运行在Java EE兼容应用程序服务器上的应用程序提供事务管理。此API为应用程序中的提交和回滚事务提供了一个方便的高级界面。例如,如果Java持久性API(JPA)与JTA一起使用,则开发人员不必在应用程序源码中编写跟踪SQL提交和回滚语句。 JTA API以独立于数据库的方式处理这些操作。

JTA有两种不同的方式来管理Java EE中的事务:

  • 隐式/容器管理事务(Implicit or Container Managed Transaction:CMT):应用程序服务器管理事务边界并自动提交和回滚事务,而开发人员不需要编写代码来管理事务。 这是默认的方式。
  • 显式/Bean管理事务(Explicit or Bean Managed Transaction:BMT):事务由开发人员在Bean级别(EJB中)的代码中进行管理。 开发人员负责明确控制交易范围和边界。

四、隐式事务管理,又称容器管理事务(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;

因此,这个实验的整体代码调用关系如下:

魏新宇

  • "大魏分享"运营者、红帽资深解决方案架构师
  • 专注开源云计算、容器及自动化运维在金融行业的推广
  • 拥有MBA、ITIL V3、Cobit5、C-STAR、TOGAF9.1(鉴定级)等管理认证。
  • 拥有红帽RHCE/RHCA、VMware VCP-DCV、VCP-DT、VCP-Network、VCP-Cloud、AIX、HPUX等技术认证。

原文发布于微信公众号 - 大魏分享(david-share)

原文发表时间:2018-06-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏转载gongluck的CSDN博客

cocos2dx 打灰机

#include "GamePlane.h" #include "PlaneSprite.h" #include "BulletNode.h" #include...

5406
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

2707
来自专栏java 成神之路

使用 NIO 实现 echo 服务器

4597
来自专栏落花落雨不落叶

canvas画简单电路图

60811
来自专栏一个会写诗的程序员的博客

Spring Reactor 项目核心库Reactor Core

Non-Blocking Reactive Streams Foundation for the JVM both implementing a Reactiv...

2142
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

3985
来自专栏闻道于事

js登录滑动验证,不滑动无法登陆

js的判断这里是根据滑块的位置进行判断,应该是用一个flag判断 <%@ page language="java" contentType="text/html...

6768
来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

2948
来自专栏C#

DotNet加密方式解析--非对称加密

    新年新气象,也希望新年可以挣大钱。不管今年年底会不会跟去年一样,满怀抱负却又壮志未酬。(不过没事,我已为各位卜上一卦,卦象显示各位都能挣钱...)...

4848
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3105

扫码关注云+社区