事务可以分为本地事务和分布式事务两种类型。这两种事务类型是根据访问并更新的数据资源的多少来进行区分的。本地事务是在单个数据源上进行数据的访问和更新,而分布式事务是跨越多个数据源来进行数据的访问和更新。在这里要说的事务是基于数据库这种数据源的。
在JAVA中,我们使用JDBC来连接数据库,访问和更新数据。那么在JDBC中是如何实现事务的,事务是被谁来管理的?这个答案当然是数据库,JDBC本身并没有处理事务的能力,而是依赖于底层数据库,底层数据库来提供事务的服务。在很多资料上会提到,JDBC的事务是基于连接的,也就是那个Connection对象,这个连接的本质其实是连接到数据库的一个Socket,与数据库建立连接以后就可以向数据库发送指令和数据,最终数据库会根据接收到的指令和数据来进行增删改查以及事务的处理。另外,事务是被限制在单个连接上的,这就好像我们去银行的营业厅办理业务,营业厅有多个窗口,我们只会在自己的窗口上与银行工作人员进行沟通并处理自身的业务,而不能跨窗口,告诉别的窗口的工作人员把那哥们的钱转到自己卡上,这事也就只能想想。
下面先看一个MYSQL数据库事务的例子:
1、创建表
创建用户表
CREATE TABLE USERS ( USER_ID INT NOT NULL AUTO_INCREMENT COMMENT '自增主键', USER_NAME VARCHAR(25) NOT NULL COMMENT '用户名', PASSWORD VARCHAR(25) NOT NULL COMMENT '密码', GENDER VARCHAR(1) COMMENT '性别', PHONE_NO VARCHAR(11) NOT NULL COMMENT '手机号', CREATE_TIME DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() COMMENT '创建时间', PRIMARY KEY (USER_ID) ) COMMENT = '用户表';
创建用户和角色的关联关系表
CREATE TABLE USER_ROLE_RELATION ( REL_ID INT NOT NULL AUTO_INCREMENT COMMENT '自增主键', USER_ID INT NOT NULL COMMENT '用户ID', ROLE_ID INT NOT NULL COMMENT '角色ID', PRIMARY KEY (REL_ID) ) COMMENT = '用户和角色对应关系表'
2、创建存储过程,在存储过程中使用事务
BEGIN #声明一个标志位,默认为0 DECLARE T_ERROR INTEGER DEFAULT 0; #如果事务执行过程中出现异常,那么把标志位设置为1 DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET T_ERROR = 1; #开始事务 START TRANSACTION;
INSERT INTO USERS (USER_NAME, PASSWORD, GENDER, PHONE_NO) VALUES ('zhangsan', '123456', '男', '11111111111'); INSERT INTO USER_ROLE_RELATION (USER_ID, ROLE_ID) VALUES ('333', '1'); #如果执行成功,直接提交,否则回滚 IF T_ERROR = 0 THEN COMMIT; ELSE ROLLBACK; END IF; END
3、执行存储过程
存储过程执行成功以后,数据库的这两个表中会分别出现一条记录。然后把
INSERT INTO USER_ROLE_RELATION (USER_ID, ROLE_ID) VALUES ('333', '1');
这个语句改为:
#这条INSERT语句违反了非空约束,会抛出异常 INSERT INTO USER_ROLE_RELATION (USER_ID, ROLE_ID) VALUES (NULL, '1');
重新保存并执行存储过程,结果是两个表中都没有新增记录。两条SQL语句要么同时成功,要么同时失败。
JDBC中如果需要手动提交事务的话,需要用到Connection对象中的三个方法:
//设置是否自动提交 setAutoCommit() //提交 commit() //回滚 rollback()
在JDBC中事务的提交方式默认是自动提交的,手动提交的事务的话需要更改默认设置:
Connection.setAutoCommit(false)
下面这个例子是假设新增一个用户并给用户赋默认权限,那么需要同时向两张表中分别插入一条记录,把新增用户和赋权限看做是一个执行单元,放在一个事务中。
package person.lb.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class TestJDBCTransAction { static { try { //加载驱动类 Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) { //执行事务 executeTransaction(); } private static void executeTransaction() { Connection conn = null; Statement st1 = null; Statement st2 = null; ResultSet rs = null; try { //获取数据库连接 conn = (Connection) DriverManager.getConnection( "jdbc:mysql://192.168.0.105:3306/TEST", "root", "root"); conn.setAutoCommit(false); st1 = (Statement) conn.createStatement(); st2 = (Statement) conn.createStatement(); //插入用户信息 st1.execute("INSERT INTO USERS (USER_NAME, PASSWORD, GENDER, PHONE_NO) " + "VALUES ('zhangsan', '123456', '男', '11111111111')" , Statement.RETURN_GENERATED_KEYS); //获取增长列的新值 rs = st1.getGeneratedKeys(); String userID = ""; if(rs.next()) { userID = rs.getString(1); } //插入用户和角色关联数据 st2.execute("INSERT INTO USER_ROLE_RELATION (USER_ID, ROLE_ID) VALUES (" +userID +", '1')"); conn.commit(); } catch (SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } finally { try { if(rs != null) { rs.close(); } if(st1 != null) { st1.close(); } if(st2 != null) { st2.close(); } if(conn != null) { conn.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
程序执行结果如下:
USERS表
USER_ROLE_RELATION表
到这里,JAVA的本地事务算是写完了。