EJB原理之(六)--范例--一个简单的事务容器

来源:互联网 发布:c#数据库实例 编辑:程序博客网 时间:2024/05/22 03:23

范例代码,可以下载参考:

http://download1.csdn.net/down3/20070613/13195846536.rar

事务的类型有很多中,在EJB体系中有七种事务类型,很多的资料都进行过比较详尽的介绍,由于以介绍原理为目的,这里主要针对事务容器中最常见的两种事务类型
最常见的事务有两种类型:Required和Required New
“Requied”事务类型指当前的事务环境之前如果已经存在事务环境,就将当前的事务环境跟随到已存在的事务环境中,如果之前不存在事务环境,那么就启动一个新的事务环境,如果在这个事务环境中任何一点回滚,那么整个的事务环境都进行回滚。
“Required New”事务类型指不管当前事务环境之前是否存在事务环境,都需要建立一个新的事务环境,当自己的事务处理结束的时候就直接提交,外部的事务回滚不影响现在的事务。
以上面发帖的功能为例,我们这里实现一个简单的事务控制方法对事务进行统一处理,在纯SQL的环境中,事务控制代码是重复的,SQL语句也是重复的,但是从整个系统的结构来说,这些重复的部分都应该进行封装重用。
在发帖功能中,一共有以下几个部分:
1) 新增一个用户
2) 这个用户发一篇文章
3) 这个用户再给自己的这篇文章写一个回复
4) 在发文章和回复的时候给用户加分
5) 在进行任何一个操作时,都记录一个日志

如果以重用的方式进行设计,那么满足这些功能需要以下几个独立的方法:
1) addUser()//增加一个用户
2) addArticle()//增加一篇文章
3) addComment()//增加一个回复
4) addPoint()//增加分数
5) log()//记录日志
在纯SQL的环境中,addPoint方法是无法实现的,因为addPoint方法需要被addArticle和addComment方法重复调用,但是由于无法统一的管理事务,addPoint中的SQL语句都被分散到了addArticle和addComment方法中,所以在一个简单的事务控制范例中,主要解决通过解决addPoint方法的重用问题来实现简单的事务控制方法,当addPoint方法被addArticle调用的时候,就跟随addArticle的事务,如果被addComment调用的时候,就跟随addComment的事务。
Log方法的使用是为了演示另外一个事务类型Required New,因为作为日志,不管外面的环境成功或者失败,这里都是一个独立的过程,都需要记录一定的信息。
这里以addArticle为例,说明几个事务之间的关系

  在整个过程中,一共会有三个事务启动,“增加文章”和“用户加分”两个功能必须同时成功,所以这两个方法应该属于同一个事务环境,“写日志”功能因为和任何环境都无关,不管外界是否成功,日志总是需要独立写入的,所以“写日志”方法每一次启动都会有一个新的事务与之对应。
○1 ~○10之间是一个完整的事务环境,从○1开始,到○10结束,“增加文章”和“用户加分”因为都需要同一个事务环境,那么就选择了跟随当前环境,从EJB事务控制的角度来看,就属于“Required”事务类型,“写日志”方法由于每一次都需要新的事务环境,那么在○4,○8两处,“写日志”方法又启动了两个新的事务环境,并在○5,○9处进行了关闭。
这里有一个简单事务容器的例子,实现较为简单,很多复杂的东西都没有包含,主要是为了实现一个基本的事务容器。
在整个的事务容器中,并没有加入一些新的东西,一切都以JDBC作为基础进行了实施。所有的处理到最终都是被转化为了JDBC的基本操作模式进行执行。在简单的事务容器中,由于暂时不考虑分布式事务,所以就直接使用了java.sql.Connection中的事务控制方法进行了事务的控制,如果需要使用分布式事务,那么在这个事务控制的环节就需要引入JTA进行分布式事务的控制,但是基本的框架不会发生改变,下面是这个简单事务容器的基本结构。

 

这其中主要的部分是container部分,这一部分实现了基本的容器功能,Context是一个事务环境,用来管理事务的环境,ContextElement是事务环境中的元素,其实ContextElement的结构很简单,主要是将java.sql.Connection和java.sql.Statement两个类封装在了其中。
从前面用JDBC实现的例子来看,基本的事务处理只有几行代码:

 

在这些代码里,所有的事务由Connnection对象进行控制,所有的SQL代码都由Statement对象进行执行,所以如果需要控制事务,就控制这两个对象即可。
在这个简单的事务容器中,一共为两个部分,ContextElement封装了Connectiond对象和Statement对象,负责执行SQL语句并保证事务完整,Context对象负责根据调用者的实际情况选择相对应的ContextElement执行语句。
 事务容器的使用会有两个过程,
1) 初始化过程
2) 事务执行过程
初始化过程主要负责事务容器的初始化工作,如建立必要连接,进行环境的准备等,简单的初始化过程如下:


 
容器的初始化一般都在UserApplication初始化的时候进行一次性初始化,以后的操作基本不需要再进行初始化。
容器初始化之后就可以进行事务的操作,事务的操作包括三个步骤
1) 启动事务环境
2) 执行SQL语句
3) 关闭事务环境

 
为了可以使不同方法之间的事务可以协同工作,在Context中建立了一个堆栈,当Context初始化时,堆栈中至少放置一个ContextElement对象,这个对象就是根事务对象,UserApplication开始执行事务的时候,Context先判断事务类型,如果是Required,那么就直接使用根事务对象进行事务的处理,不会建立新的ContextElement对象,如果是Required New,那么就建立一个新的ContextElement对象,因为前面的事务对象还没有提交,所以不可以关闭,就将这个新的ContextElement对象放置在堆栈的顶端,这种事务的嵌套最终会形成一个树状结构,事务一层层打开同时从最内端一层层关闭,保证了事务的完整性。
事务容器的初始化过程可以参照“Context.java”中的open()方法,事务环境的使用可以参见“ContainerManualExample.java”中的runExample()方法。
现在实现的这个容器是一个很简单的容器,只支持Required和Required New两种事务,所以上面的过程都以这两种事务类型为依据,其他的事务类型都较简单,可以逐渐完善。
现在,一个具备基本功能的事务容器就可以进行工作了,但是这个容器使用起来还是过于烦琐,没有EJB直观,比如执行一个addUser的过程还是没有太大的简化。

(ContainerManualExample.java文件,addUser方法)
…………………………
try {
            
            
//开始一个新的事务环境
            context.startContext ( TransactionType.REQUIRED );
            
            
//SQL语句
            String sql = "INSERT INTO USER_INFO VALUES ( '" + user_id + "', '" + userName + "', " + user_point + " )";
           
            
//执行语句
            context.excuteUpdate ( sql );
            
            
//结束事务环境
            context.stopContext ( TransactionType.REQUIRED );
            
            
        }
 catch ( Exception e ) {
            
            
//结束事务环境
            context.stopContext ( TransactionType.REQUIRED );
            
            
throw e;
           
        }
  


好像还是需要进行事务的手动控制,没有太大改观,但是这种改观在增加文章的时候就很有优势

(ContainerManualExample.java文件,addUser方法)
…………………………
try {
            
            
//开始一个新的事务环境
            context.startContext ( TransactionType.REQUIRED );
            
            
//SQL语句
            String sql = "INSERT INTO ARTICLE VALUES ( '" + article_id + "', '" + article_content + "', '" + user_id + "' )";
           
            
//执行语句
            context.excuteUpdate ( sql );
            
            
//给用户加分
            addUserPoint ( user_id, 2 );
           
            
//结束事务环境
            context.stopContext ( TransactionType.REQUIRED );
            
            
        }
 catch ( Exception e ) {
            
            
//结束事务环境
            context.stopContext ( TransactionType.REQUIRED );
            
            
throw e;
           
        }
  

 这里我们看到addPoint方法在这里可以直接使用而不用再让SQL语句重复出现,这就是事务重用的最终目的。
前面说过,事务容器的最终目的就是减少两种代码的重复,
1) 事务控制代码
2) SQL重复代码
现在实现的这个事务容器似乎只能去处重复的SQL代码,还是有一大堆的事务控制代码没有去处,EJB中就没有这些事务控制代码。
当然现在只是一个基本的实现功能,后面还需要一种方法来去处重复的事务控制代码,当然,那种方法最终还是需要现在的实现作为基础。为了讲解EJB的原理,这里是从JDBC开始说起,逐渐封装,改进,但最后还是可以进行回溯的,就像现在的这个简单事务容器,其实还是没有跳出JDBC的基本范畴,只是在JDBC的外面做了一点包装,后面还会继续进行包装,但有一点不会改变,每一部的改进最终都会回溯到最初的方式,就像不管计算机可以做多么复杂的运算,最终回溯到的,其实是最简单的布尔运算。