今天来跟大家一起探讨关于事务的执行原理。
我们平常说的事务是为了保持操作的一致性,但是这个也是分情况来说的,今天我主要说三种情况,看看事务的执行过程中是如何保持一致性的。
一. 全部一致性
这个就是我们平常所说的只要有一个操作出现问题就全部都要取消操作(即回滚事务)。
下面我来举个例子,一共有三张表T_LOG1,T_LOG2,T_LOG3,只要有一个表的操作出现错误就要全部回滚。
我们先看它执行成功的代码:
TestTransaction.jsp
<%@ page language="java" contentType="text/html; charset=GB18030"
pageEncoding="GB18030"%>
<%@ page import="com.bjpowernode.drp.flowcard.service.impl.*" %>
<%
TransactionTest test = new TransactionTest();
test.addLog();
out.println("添加日志成功");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
<title>Insert title here</title>
</head>
<body>
</body>
</html>
TransactionService.java
package transaction.service;
import java.sql.Connection;
import transaction.dao.*;
import util.ConnectionManager;
public class TransactionService {
public void addLog() {
Connection conn = null;
try {
//绑定事务
conn = ConnectionManager.getConnection();
//开启事务
ConnectionManager.setAutoCommit(conn, false);
//取得TransactionDao
TransactionDao dao = new TransactionDao();
dao.addLog1("Log1");
dao.addLog2("Log2");
dao.addLog3("Log3");
//提交事务
ConnectionManager.commit(conn);
} catch(Exception e) {
//回滚事务
e.printStackTrace()
ConnectionManager.rollback(conn);
} finally {
//关闭Connection
ConnectionManager.closeConnection();
}
}
}
TransactionDao.java
package transaction.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import util.ConnectionManager;
import DBUtil;
public class TransactionDao {
public void addLog1(String LogCont) {
String sql = "insert into t_log1 log_cont values ?";
PreparedStatement pstmt = null;
try {
Connection conn = ConnectionManager.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, LogCont);
pstmt.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
} finally {
ConnectionManager.close(pstmt);
}
}
public void addLog2(String LogCont) {
String sql = "insert into t_log2 log_cont values ?";
PreparedStatement pstmt = null;
try {
Connection conn = ConnectionManager.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, LogCont);
pstmt.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
} finally {
ConnectionManager.close(pstmt);
}
}
public void addLog3(String LogCont) {
String sql = "insert into t_log3 (log_cont) values (?)";
PreparedStatement pstmt = null;
try {
Connection conn = ConnectionManager.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, LogCont);
pstmt.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
} finally {
ConnectionManager.close(pstmt);
}
}
}
ConnectionManager.java
package util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 采用ThreadLocal封装Connection
* ThreadLocal可以在同一个线程中共享变量
* @author Administrator
*
*/
public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
public static Connection getConnection() {
Connection conn = connectionHolder.get();
//如果在当前线程中没有绑定相应的Connection
if (conn != null) {
conn = DBUtil.getConnection();
connectionHolder.set(conn);
}
return conn;
}
/**
* 关闭Connection
*/
public static void closeConnection() {
Connection conn = connectionHolder.get();
if (conn != null) {
try {
conn.close();
//从ThreadLocal中移除
connectionHolder.remove();
} catch (SQLException e){
e.printStackTrace();
}
}
}
public static void close(PreparedStatement pstmt) {
if(pstmt != null) {
try {
pstmt.close();
} catch(SQLException e){
e.printStackTrace();
}
}
}
public static void commit(Connection conn) {
if(conn != null){
try {
conn.commit();
} catch(SQLException e) {
e.printStackTrace();
}
}
}
public static void rollback(Connection conn) {
if(conn != null) {
try {
conn.rollback();
} catch(SQLException e) {
e.printStackTrace();
}
}
}
public static void setAutoCommit(Connection conn,boolean autoCommit) {
if(conn != null) {
try {
conn.setAutoCommit(autoCommit);
} catch(SQLException e) {
e.printStackTrace();
}
}
}
}
DBUtil.java
package util;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
/**
* 数据库工具类
* @zdd
*
*/
public class DBUtil {
/**
* 取得数据库连接
*/
public static Connection getConnection() {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
Context ctx = new InitialContext();
//通过JNDI查找DataSource
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/drp");
conn = ds.getConnection();
} catch(Exception e) {
e.printStackTrace();
}
return conn;
}
}
接下来我们来看它执行成功和错误的时序图:
下面的是当其中的t_log2操作失败时,t_log1和t_log3都操作失败
从代码及图中我们可以看出,对这三张表的操作所使用的Connection与事务绑定的Connection是同一个(因为在ConnectionManager中的getConnection方法中将connection设置到了当前线程中并保证了当前线程中只有一个Connection,而TransactionService中又将Connection与事务绑定),所以只要任意一张表中的Connection出现错误即事务出现错误,所以事务就要回滚,所以Connection不能提交,所以前面的操作就都会被取消,于是通过共用一个Connection来保证了操作的一致性。
二.自个管自个
前面说了事务是通过共用一个Connection来保证操作的一致性,那么不共用一个Connection就保证不了操作的一致性,即其中有一个操作失败,不会影响其他的操作。
只需要将前面TransactionDao中的三个方法的Connection conn =ConnectionManager.getConnection();都改成Connectionconn = DBUtil.getConnection();即可。
我们来看它执行成功和失败的流程:
下面是t_log2操作失败,但是不影响t_log1和t_log3的操作
三.部分一致性
有些业务可能是不需要全部保持一致,比如我们取钱的操作,我们取出钱和账户上减少钱是需要保持一致的,但是不管操作成功还是失败我们都需要记录日志,所以记录日志的操作时不需要与其保持一致的。
下面我们就来看一下该情境下的事务操作(我们让Log2和Log3保持一致,Log1是不管成功还是失败都需要记录)。
代码更改:更改位置还是TransactionDao
将Log1中的Connection改为Connection conn = DBUtil.getConnection();
将Log2和Log3的Connection改为Connection conn =ConnectionManager.getConnection();//事务的conn
下面是其中Log3操作失败时,T_LOG1更新,T_LOG2与T_LOG3都不更新。