互联网架构 JTA 控制数据库的事务
开始之前先看一个JDBC例子
JDBC事务控制
eg:
public class MainTest {
@Test
public void test1() {
//jdbc事务控制
Connection conn= null;
Connection conn1 = null;
try {
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/hp_spring";
String uname = "root";
String upwd = "zzj6660534";
//注册数据库驱动程序 newInstance构建对象实例 driver类加载到内存当中 动态加载 最终生成对象
Class.forName(driver)/*.newInstance()只能调用一个类的无参构造,调用不了有参构造*/;
/*new 与 newInstance 区别
都是创建对象
不同点:
1.new 调用一个类里任何构造
2.newInstance 调用对象的默认的无参构造方法 调不了有参构造*/
/*driver 静态块里已经有创建对象过程*/
//创建数据库连接对象
conn = DriverManager.getConnection(url, uname, upwd);
//关闭自动事务提交
conn.setAutoCommit(false);
//创建执行sql语句对象
PreparedStatement pstmt = conn.prepareStatement("insert into employees(id,last_name,email,dept_id) values(?,?,?,?)");
pstmt.setInt(1,7);
pstmt.setString(2, "d1");
pstmt.setString(3, "l1");
pstmt.setInt(4,7);
pstmt.executeUpdate();
conn.commit();//提交当前未正式提交的事务操作
pstmt.close();
conn.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
if(conn != null) {
try {
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
}
JDBC事务针对于同一个数据库而言 不同的数据表
针对于一个项目,会涉及多个数据库,如果要做事物的集群,
包含若干个数据库的服务器,对多个数据库操作,造成事务管理没有统一的标准,跨库数据管理(JTA)
事物和JTA
事务控制也是JavaEE应用中必须处理的问题,它可以保证一系列数据库操作能够准确完成。
事务既是保证底层数据库完整性的重要手段。也是保证应用业务逻辑成功执行的重要基础。
对于一个实际企业及应用而言。有的只需要采用局部事务控制即可。有的则需要全局事务控制
JTA(Java Transaction API)则提供了事务划分的标准接口,尤其是当应用程序执行两个需要依赖于不用数据库的操作时,
应用程序就需要使用JTA来将这两个操作包含成一个全局事务
1.事务的基本概念
事务是由一步或几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,
要么全部不放弃执行。程序和食物是两个不同的概念。一般而言,一段程序中可能包含多个事务、(事务:针对数据库操作的一系列集合)
2.事务特性(ACID)特性
1.原子性:事务是应用中最小的一个单位 密不可分 不可再分的逻辑执行体
2.一致性:事务执行结果必须使数据库从一个一致性状态变成另一个一致性状态
3.隔离性:各个事物的执行互不干扰
4.持续性:事务一旦提交 永久保存
3.局部事务(LTP)
单一数据库资源,通常采用单阶段提交;
4.分布式事务 (DTP)
一个以上的事务性资源,访问多个数据库。关键是必须有一种方法保证多个数据库所做的全部动作,
它们也可以作为一个整体,要么全部提交,要么全部回滚,这样能保证当业务逻辑跨越多个数据库资源时让多个数据库的数据保持一致
5.分布式事务管理 XA规范 2PC协议
1.当一个业务操作涉及多个数据库时,这就可为分布式事务处理事务了,
实现分布式处理事务的关键是采用一种手段保证事务设计的所有数据库所做的全部动作要么全部生效,要么全部回滚。
为了协调多个事务资源的分布事务处理,多个事务资源底层必须使用一种通用的事务协议,目前流行的分布式事务处理规范就是XA规范。
协调多个事务资源分布式的处理 遵照协议的标准XA规范
2.X/Open组织(即现在的 Open Group)定义了分布式事务处理模型。四部分:
X/Open DTP模型包括:AP(应用程序);
TM(事务管理器:事务中间件);
RM(资源管理器:数据库);
CRM(通讯资源管理器:消息中间件)
3.通常来说,应用程序对单个数据库内部的多个DML操作会组成一个局部事务,因为无需跨越多个事务性资源,
因此直接使用底层数据库的事务支持就足够了;如果应用程序的数据库访问涉及对多个数据库的修改,
那就面临着分布式事务处理了,分布式事务处理的对象是全局事务,通过全局事务才可以保证多个数据库之间的一致性
4.X/Open组织为分布式事务处理指定了事务中间件与数据库之间的接口规范,这种规范就是XA规范。
事务中间件用它来通知数据库事务的开始,提交或回滚等。
5.X/Open组织仅仅指定了分布式事务处理的XA规范,但具体的实现则有不同的数据库厂商自行提供;
对于大部分主流的商业数据库,如Oracle,SQL,Server等,它们都提供了支持XA规范的驱动;
但一些小型的开源数据库如MySQL,则没有提供支持XA规范的驱动
6.而XA规范的理论基础就是两阶段提交(2 Phase Commit,2PC)协议,该协议定义了单个的事务管理器如何协调和管理一个或多个数据库的局部事务,该协议大致可分为如下5个步骤:
1.应用程序面向事务管理器编程,应用程序调用事务管理器的提交方法
2.事务管理器通知参与全局事务的每个数据库,告诉他们准备开始提交事务——第一个阶段从现在开始
3.参与全局事务的各个数据库进行局部事务的预提交
4.事务管理器收集到各个数据库预提交的结果
5.第二阶段开始,事务管理器收集到所有参与全局事务预提交的结果之后做出相应的判断;如果所有数据库的局部事务预提交的结果都可以成功,事务管理器向每个数据库都发送进行实际提交的命令;如果任意一个数据库的局部事务预提交的结果失败了,事务管理器将每个数据库发送进行实际回滚的命令,让所有数据库退回修改之前的状态
当一个全局事务之涉及一个数据库的时候可以将两阶段提交优化成单阶段提交。
第一阶段提交 同时提交俩 正常进行后锁定 进行第二阶段提交
第二阶段提交 正式提交 提交完 解锁
6.使用JTA全局事务保证多数据库的一致性
通过使用JTA编程,开发者可以用同一种与事务管理器无关的方式来开始,提交或回滚事务,
javaEE应该服务通过Java事务服务(Java Transaction Service,JTA)来实现事务管理器。
但应用程序代码无需直接调用JTS方法,它只需要面向JTA方法即可,由JTA来调用底层的JTS进行处理。
JTA事务由javaEE事务管理器负责控制,它可以保证多个数据库更新的一致性,通过JTA即可实现全局事务控制
eg:
1.新建两个数据库mx1:mx_user;mx2:mx_user
2.新建JTATest类
public class JTATest {
@Test
public void test11() throws Exception {
//定义数据库链接的相关对象与执行sql语句对象 针对于mx1
XADataSource xaDs1 = JTATest.getDataSource("jdbc:mysql://localhost:3306/mx1", "root", "zzj6660534");
XAConnection xaCon1 = null;
//资源文件
XAResource xaRes1 = null;
Connection conn1 = null;
Statement stmt1 = null;
//定义数据库链接的相关对象与执行sql语句对象 针对于mx2
XADataSource xzDs2 = JTATest.getDataSource("jdbc:mysql://localhost:3306/mx2", "root", "zzj6660534");
XAConnection xaCon2 = null;
XAResource xaRes2 = null;
Connection conn2 = null;
Statement stmt2 = null;
int ret1 = 0;
int ret2 = 0;
//记录ID标识
Xid xid1 = new MyXid(100, new byte[] {0x01}, new byte[] {0x02});
Xid xid2 = new MyXid(100, new byte[] {0x01}, new byte[] {0x03});
//实例化
xaCon1 = getXAConnection(xaDs1);
conn1 = getConnection(xaCon1);
xaRes1 = xaCon1.getXAResource();
stmt1 = conn1.createStatement();
xaCon2 = getXAConnection(xzDs2);
conn2 = getConnection(xaCon2);
xaRes2 = xaCon2.getXAResource();
stmt2 = conn2.createStatement();
//开始第一个资源的事务处理(2PC两次提交第一次),TMNOFLAGS指示不选择任何的标志值
xaRes1.start(xid1, XAResource.TMNOFLAGS);
stmt1.execute("insert into mx_user(uid,uname,upwd) values(5,'tom','123')");
//结束第一个资源的事务处理
xaRes1.end(xid1, XAResource.TMSUCCESS);
//开启第二个资源的事务处理(2PC两次提交第一次)
xaRes2.start(xid2, XAResource.TMNOFLAGS);
stmt2.execute("insert into mx_user(uid,uname,upwd) values(5,'tom','123')");
//结束第二个资源的事务处理
xaRes2.end(xid2, XAResource.TMSUCCESS);
//事务跨库处理 获取整型值
ret1 = xaRes1.prepare(xid1);
ret2 = xaRes2.prepare(xid2);
//2PC两次提交第二次
//与XA_OK常量整型值一致
if(XAResource.XA_OK == ret1 && XAResource.XA_OK == ret2) {
xaRes1.commit(xid1, false);
xaRes2.commit(xid2, false);
System.out.println("提交分布式事务!");
}else {
xaRes1.rollback(xid1);
xaRes2.rollback(xid2);
System.out.println("回滚分布式事务!");
}
}
//遵循XA规范
public static XADataSource getDataSource(String url,String user,String password) {
//1.获取分布式数据源对象
//获取mysql分布式数据源对象
MysqlXADataSource dataSource = new MysqlXADataSource();
//为数据源对象设置属性 url user password
dataSource.setUrl(url);
dataSource.setUser(user);
dataSource.setPassword(password);
return dataSource;
}
//通过数据源对象拿到连接对象
public static XAConnection getXAConnection(XADataSource dataSource) {
//2.获取分布式数据库的连接对象
XAConnection XAConn = null;
try {
XAConn = dataSource.getXAConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
return XAConn;
}
}
public static Connection getConnection(XAConnection XAConn) {
//3.基于分布式数据库连接对象获取一个普通数据库连接对象
Connection conn = null;
try {
//通过分布式数据库连接对象获取一个普通数据库连接对象
conn = XAConn.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
return conn;
}
}
public static void closeConnection(Connection conn) {
//4.关闭数据库连接
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("数据库连接失败");
}
}
}
XADateScource
继承CommonDataScource 内部使用 XADateScource对象工厂
XAConnection
继承PooledConnection
getXAConnection 建立可在分布式事务中使用的物理数据库的连接对象
步骤
1.先数据库连接
2.通过数据库连接生成分布式连接对象
3.基于分布式连接对象生成每一个数据库的连接对象
4.执行sql语句
3.新建MyXid类实现Xid接口(记录全局事务ID)
public class MyXid implements Xid {
private int formatId;
private byte[] globalTid;
private byte[] branchQ;
public MyXid(int formatId, byte[] globalTid, byte[] branchQ) {
super();
this.formatId = formatId;
this.globalTid = globalTid;
this.branchQ = branchQ;
}
@Override
public byte[] getBranchQualifier() {
// TODO Auto-generated method stub
//获取XID事务分支标识符部分作为一个字节数组
return this.branchQ;
}
@Override
public int getFormatId() {
// TODO Auto-generated method stub
//获取XID格式标识符部分
return this.formatId;
}
@Override
public byte[] getGlobalTransactionId() {
// TODO Auto-generated method stub
//获取XID全局事务标识符作为字节数组
return this.globalTid;
}
}
Xid:javax.transaction.xa.Xid;
Xid接口是X/Open事务标识符XID结构的java映射。此接口指定三个存取方法,以检索全局事务格式ID,
全局事务ID和分支限定符.Xid接口供事务管理器和资源管理器使用。此接口对应用程序不可见