并记录下读书笔记,以方便以后参考。
Java EE应用大致可分为如下几层:
1,Domain Object(领域对象)层:此层由系列的POJO组成。
2,DAO(Data Access Object,数据访问对象)层
3,业务逻辑层
4,控制器层
5,表现层
JBoss是一款很著名的开源的JavaEE应用服务器
事务控制也是Java EE应用中必须要处理的问题,他可以保证一系列数据库操作能够准确完成。事务及时保证底层数据库完整性的重要手段,也是保证应用业务逻辑成功执行的重要基础。对于一个实际企业级应用而言,既需要局部事务控制,也需要全局事务控制。
JTA(Java Transaction API)则提供了事务划分的标准接口,尤其是当应用程序执行两个需要依赖于不同数据库的操作时,应用程序就需要使用JTA来将两个操作包含成一个全局事务。
事务是由一步或几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行。一段程序中可能包含多个事务。
事务具备4个特性:原子性Atomicity,一致性Consistency,隔离性Isolation和持续性Durability,简称ACID特性。
原子性:事务是应用中最小执行单位,事务是应用中不可再分的最小逻辑执行体。
一致性:事务执行的结果,必须使数据库从一个一致状态变到另一个一致性状态。一致性是通过原子性来保证的。
隔离性:各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务都是隔离的。也就是说,并发执行的事务之间不能看到对方的中间状态,并发执行的事务之间也不能互相影响。
持续性:也称为持久性,指事务一旦提交,对数据所做的任何改变都要记录到永久存储器中,通常就是保存进物理数据库。
Java EE应用中的事务处理一般可以分为如下两类:
局部事务,Local Transaction Processiong
分布式事务,Distributed Transaction Processiong,DTP
对于绝大部分企业应用而言,它只需要涉及单一的数据库资源,因此只需要采用局部事务即可。局部事务通常采用单阶段提交;但是也有一些更复杂的企业级应用采用分布式事务处理。分布式事务处理的关键是必须有一种方法保证多个数据库所做的全部动作,他们也可以作为一个整体,这样才能保证党业务逻辑跨越多个数据库资源时让多个数据库的数据保持一致。
必须借助于中间件的事务管理器来进行协调,中间件的事务管理器负责通知和协调所有参与事务的相关数据库的提交或回滚,最后由各个单独的数据库将自己所做的操作进行实质提交或回滚。
从应用程序的角度来看,中间件的事务管理器消除了底层事务处理的复杂性,从而简化了分布式事务处理的编程步骤——应用程序无需理会底层多个事务资源局部事务的开始、提交或回滚操作,应用程序只要面向中间件的事务管理器编程,用全局事务管理的API来开始、提交或回滚事务即可,而底层则可能转换为多个低级别的事务命令。
当一个业务操作需要涉及多个数据库时,就可称为分布式事务处理了。为了协调多个事务资源的分布式事务处理,多个事务资源底层必须使用一种通用的事务协议,目前流行的分布式事务处理规范就是XA规范。
X/Open组织(即现在的Open Group)定义了分布式事务处理模型。X/Open DTP模型(1994)包括应用程序AP、事务管理器TM、资源管理器RM、通信资源管理器CRM,一共4个部分。一般来说,常见的事务管理器TM就是事务中间件(通常由应用服务器来实现),常见的资源管理器是数据库,常见的通信资源管理器是消息中间件。
X/Open组织为分布式事务处理指定了事务中间件与数据库之间的接口规范,这种规范就是XA规范。事务中间件用它来通知数据库事务的开始、提交或回滚等。
该组织仅仅指定了分布式事务处理的XA规范,但是具体的实现则由不同的数据库厂商自行提供。
XA规范的理论基础就是两阶段提交(2 Phase Commit,2PC)协议,该协议定义了单个的事务管理器如何协调和管理一个或多个数据库的局部事务,该协议大致可分为如下5个步骤:
1,应用程序面向事务管理器编程,应用程序调用事务管理器的提交方法。
2,事务管理器通知参与全局事务的每个数据库,告诉他们准备开始提交事务——第一个阶段开始。
3,参与全局事务的各个数据库进行局部事务的预提交。
4,事务管理器收集到各个数据库预提交的结果。
5,第二阶段开始,事务管理器收集到所有参与全局事务预提交的结果之后做出相应的判断:如果所有数据库的局部事务预提交结果都可以成功,事务管理器向每个数据库都发送进行实际提交的命令;如果任意一个数据库的局部事务预提交的结果失败了,事务管理器将向每个数据库发送进行实际回滚的命令,让所有数据库退回修改之前的状态。
对于单个数据库局部事务预提交的过程,进一步解释:当某一个数据库收到预提交要求后,如果可以提交属于自己的事务分支,则将自己在该事务分支中所做的操作记录下来,并给事务中间件一个同意提交的应答,此时数据库将不能再想该事务分支中加入任何操作,但此时数据库并没有真正提交改事务,底层数据库对共享资源的操作还未释放(处于上锁状态)。如果由于某种原因数据库无法提交属于自己的事务分支,将回滚自己的所有操作,释放对共享资源上的锁,并返回给事务中间件失败的应答。
在典型的Java EE应用服务器中,全局事务管理器是必须的组件。它会负责协调和管理参与全局事务的多个数据库的局部事务处理。
在一个涉及多个数据库的分布式事务处理中,为保证全局事务的完整性,两阶段提交时必须的。但是典型的两阶段提交,对数据库来说事务从开始到结束时间相对较长,在事务处理期间数据库使用的资源将一直处于锁定状态,直到事务结束才会释放。因此,使用典型的两阶段提交相对来说会占用更多的资源,因此会带来一定的性能损失。
通过使用JTA编程,开发者可以用一种与事务管理器无关的方式处理事务,Java EE应用服务器通过Java事务服务(Java Transaction Service,JTS)来实现事务管理器。但应用程序代码无需直接调用JTS方法,只需要面向JTA方法即可,由JTA来调用底层的JTS进行处理。
如果希望使用JTA事务,可调用javax.transaction.UserTransaction接口的begin、commit、rollback等方法来控制事务。
Context ctx = new InitialContext();
DataSource oracleDs = (DataSource) ctx.lookup("oracle");
DataSource otherDs = (DataSource) ctx.lookup("other");
UserTransaction tx = (UserTransaction) ctx.lookup("javax.transaction.UserTransaction");
Connection oracleConn = oracleDs.getConnection();
Connection otherConn = otherDs.getConnection();
Statement oracleStmt = null;
Statement otherStmt = null;
tx.begin();
try {
oracleStmt = oracleConn.createStatement();
otherStmt = otherConn.createStatement();
int result = oracleStmt.executeUpdate("insert into dept values(50,'研发部','广州')");
System.out.println(result == 1 ? "插入成功" : "插入失败");
otherStmt.executeUpdate("insert into dept values(40,'市场部','广州')");
tx.commit();
} catch (SQLException e) {
e.printStackTrace();
tx.rollback();
} finally {
oracleStmt.close();
otherStmt.close();
oracleConn.close();
otherConn.close();
}
上面的代码是一个典型的
JTA
事务控制逻辑,适用于
WebLogic
。
jboss中如果在Resource->Datasources->XA Datasources中配置后,在server\default\deploy目录下就会生成一个xxx-ds.xml的文件。
DataSource oracleDs = (DataSource) ctx.lookup("java:/oracle");
DataSource otherDs = (DataSource) ctx.lookup("java:/other");
//适用于jboss
UserTransaction tx = (UserTransaction) ctx.lookup("UserTransaction");
事务控制是保证应用程序底层数据完整性的重要手段,但事务控制对程序性能的影响也是很大的,如果应用程序的事务属性设置不合适,应用程序的性能将会大大降低。
事务隔离指的是事务处理中如何管理事务的隔离性,所谓隔离性实际上就意味着数据库如何处理并发操作的问题,当两个或者多个操作处理同一个数据时,可能引起访问的冲突和数据库的不一致。
根据JDBC规范的定义,事务的隔离设置一共分为5种级别,这5中级别的详细规定依赖于具体使用的数据库。下面是简单的介绍:
TRANSACTION_NONE:对事务和数据不进行任何隔离限制。
TRANSACTION_READ_UNCOMMITTED:这种隔离级别允许事务读取另一个事务的未提交数据。举例来说,A线程将某个账户的余额减少了1000元,即使在事务A未提交之前,当事务B试图读取该账户余额时,也将读到减少后的账户余额。在这种隔离级别下,尽管事务A没有提交,但事务B读到的总是最新的数据。但是这种隔离级别有一个很严重的问题:读取脏数据,对于前面介绍的情形,当事务B读取到未提交的账户余额之后,如果事务A又回滚了,那意味着B读的数据是不正确的。
TRANSACTION_READ_COMMITTED:这种隔离级别保证所有读到的数据都是已经提交的数据,这样可以避免第二种情况的出现,但他不允许重复读。举例来说,当事务A开始读到某条记录的余额为2000元,在事务A未提交之前,事务B把这条记录的账户余额改为1000元,并提交了事务B,当事务A再次读取时将读到1000元,这就是不可重复读。因为对于同一条数据记录,事务A读取了两次,但两次读到的结果并不相同。对于Oracle而言,TRANSACTION_READ_COMMITTED是默认的隔离级别,在应用中通常应采用这种隔离设置。
TRANSACTION_REPEATABLE_READ:这种隔离级别可以防止第二、第三种情形的出现,他是一种可重复读的事务隔离。但当其他关联表中的数据行被插入或删除时,读事务依然可能出现一种边界效应,从而造成某种程度的错觉。假设现在数据库中有两个表:表一记录当前全部学生的详细记录(假设每条记录代表一个学生);表二则记录了表一的统计信息,包括学生总人数、男生人数、女生人数等。事务A读取表一记录,并计算出学生总人数、男生人数、女生人数等统计信息,接下来事务B向表一中插入记录并修改表二的统计记录,如果此时恰好事务A去读取表二中的统计信息,并用读到的统计信息与之前通过表一计算出来的统计信息进行对比,将会发现两组统计信息并不相等(新读到的统计数据更大)。
TRANSACTION_SERIALIZABLE:这种隔离设置可以防止第二、第三、第四种情形的出现,其代表真正可串行化的事务,提供了最高级别的保护,但是这种事务方式的性能也是最低级的。
通过Connection接口定义的setTransactionIsolation()方法可以设置隔离级别,Connection接口中定义了上面的5个常量。
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
由于事务隔离级别需要底层数据库的支持,但并不是每个数据库都支持所有的隔离级别,不同数据库也可能采用不同的锁定算法,因此设定时还需要查阅数据库的相关资料。