事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败。
事务有四个特性:
Atomicity(原子性),Consistency(一致性),Isolation(隔离性),Durability(持久性)
Spring对事务的支持很强大,但是从本质上来说,事务是否生效取决于数据库底层是否支持(MySQL的MyISAM引擎不支持事务),
同时,一个事务的多个操作需要在同一个connection上。下面手写一个Demo分析Spring事务底层实现。
- 工程结构
- connection部分
Spring在配置多个数据源DataSource时,需要通过DataSource来得到操作数据库的管道Connection。
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
public class ConnectionHolder {
//map存放的数据源与连接管道的映射
private Map<DataSource, Connection> map= new HashMap<DataSource, Connection>();
//根据dataSource获取Connection
public Connection getConnectionByDataSource(DataSource datasource) throws SQLException{
Connection connection = map.get(datasource);
if(connection == null || connection.isClosed()){
connection = datasource.getConnection();
map.put(datasource, connection);
}
return connection;
}
}
ConnectionHolder在多线程的情况下,map是线程不安全的,可能会存在链接在使用的同时,另一线程在关闭的情况。
另一种想法是使用线程安全的ConcurrentHashMap。但是我们真正要做的是保证在一个线程下,一个事务的多个操作拿到的是一个Connection,使用ConcurrentHashMap并不能保证
这个问题。
故考虑使用ThreadLocal类型,ThreadLocal类型是线程共享变量,属线程内的全局变量。且ThreadLocal在多个线程使用的情况下,会为每个线程创建一个副本,线程对ThreadLocal变量的操作,只会影响本线程内的副本。
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
public class SingleThreadConnectionHolder {
//ThreadLocal封装Map为线程共享变量
private static ThreadLocal<ConnectionHolder> threadLocal = new ThreadLocal<ConnectionHolder>();
private static ConnectionHolder getConnectionHolder() {
ConnectionHolder connectionHolder = threadLocal.get();
if(connectionHolder == null) {
connectionHolder = new ConnectionHolder();
threadLocal.set(connectionHolder);
}
return connectionHolder;
}
public static Connection getConnection(DataSource dataSource) throws SQLException{
return getConnectionHolder().getConnectionByDataSource(dataSource);
}
}
通过ThreadLocal封装后,即可保证一个线程内一个DataSource获取到的Connection是唯一的。
- manage部分
新建TransactionManage,用于事务控制
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import resource.connection.SingleThreadConnectionHolder;
public class TransactionManager {
private DataSource dataSource;
public TransactionManager(DataSource dataSource) {
// TODO Auto-generated constructor stub
this.dataSource = dataSource;
}
private Connection getConnection() throws SQLException{
return SingleThreadConnectionHolder.getConnection(dataSource);
}
//开启事务
public void start() throws SQLException{
Connection connection = getConnection();
connection.setAutoCommit(false);
}
//回滚事务
public void rollback() {
Connection connection = null;
try {
connection = getConnection();
connection.rollback();
}catch(SQLException e) {
e.printStackTrace();
}
}
//关闭事务
public void close() throws SQLException{
Connection connection = getConnection();
connection.commit();
connection.setAutoCommit(false);
connection.close();
}
}
- DAO层 ,用于操作数据库链接,包括UserAcountDao,UserOrderDao
用户购买操作Dao
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import resource.connection.SingleThreadConnectionHolder;
public class UserAcountDao {
private DataSource dataSource;
public UserAcountDao(DataSource dataSource){
this.dataSource = dataSource;
}
public void buy() throws SQLException {
Connection connection = SingleThreadConnectionHolder.getConnection(dataSource);
//进行业务操作
//。。。。
System.out.println("当前用户购买线程:" + Thread.currentThread().getName() +
",使用管道 (hashcode):" + connection.hashCode());
}
}
用户订单操作Dao
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import resource.connection.SingleThreadConnectionHolder;
public class UserOrderDao {
private DataSource dataSource;
public UserOrderDao(DataSource dataSource){
this.dataSource = dataSource;
}
public void order() throws SQLException {
Connection connection = SingleThreadConnectionHolder.getConnection(dataSource);
//进行业务操作
//。。。。
System.out.println("当前用户订单线程:" + Thread.currentThread().getName() +
",使用管理(hashcode):" + connection.hashCode());
}
}
- service层,用户进行业务操作
import javax.sql.DataSource;
import resource.dao.UserAcountDao;
import resource.dao.UserOrderDao;
import resource.manage.TransactionManager;
public class UserService {
private UserAcountDao userAcountDao;
private UserOrderDao userOrderDao;
private TransactionManager transactionManager;
public UserService(DataSource dataSource) {
userAcountDao = new UserAcountDao(dataSource);
userOrderDao = new UserOrderDao(dataSource);
transactionManager = new TransactionManager(dataSource);
}
public void action() {
try {
//进行购买,下单操作
transactionManager.start();
userAcountDao.buy();
userOrderDao.order();
transactionManager.close();
}catch(Exception e) {
//发生异常,则事务回滚
e.printStackTrace();
transactionManager.rollback();
}
}
}
- Test测试程序
import org.apache.commons.dbcp2.BasicDataSource;
import resource.service.UserService;
public class TestTransaction {
public static final String jdbcDriver = "com.mysql.jdbc.Driver";
public static final String jdbcURL = "jdbc:mysql://localhost:3306/my_web?useSSL=false";
public static final String jdbcUsername = "******";//mysql用户名
public static final String jdbcPassword = "******";//密码
public static void main(String[] args) {
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName(jdbcDriver);
basicDataSource.setUsername(jdbcUsername);
basicDataSource.setPassword(jdbcPassword);
basicDataSource.setUrl(jdbcURL);
final UserService userService = new UserService(basicDataSource);
//模拟用户并发请求
for(int i = 0; i < 10; i++) {
new Thread((Runnable)()-> {userService.action();}).start();
}
try {
Thread.sleep(10000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
通过测试程序,并发的模拟用户请求,总计10个线程,每个线程均会调用DAO层进行数据操作。操作结果如下
当前用户购买线程:Thread-10,使用管道 (hashcode):1671328438
当前用户订单线程:Thread-10,使用管道(hashcode):1671328438
当前用户购买线程:Thread-5,使用管道 (hashcode):1172249069
当前用户订单线程:Thread-5,使用管道(hashcode):1172249069
当前用户购买线程:Thread-1,使用管道 (hashcode):863698743
当前用户订单线程:Thread-1,使用管道(hashcode):863698743
当前用户购买线程:Thread-7,使用管道 (hashcode):1206124853
当前用户订单线程:Thread-7,使用管道(hashcode):1206124853
当前用户购买线程:Thread-2,使用管道 (hashcode):1861628772
当前用户购买线程:Thread-6,使用管道 (hashcode):1394656535
当前用户订单线程:Thread-2,使用管道(hashcode):1861628772
当前用户订单线程:Thread-6,使用管道(hashcode):1394656535
当前用户购买线程:Thread-8,使用管道 (hashcode):1883267477
当前用户订单线程:Thread-8,使用管道(hashcode):1883267477
当前用户购买线程:Thread-9,使用管道 (hashcode):1475410105
当前用户订单线程:Thread-9,使用管道(hashcode):1475410105
当前用户购买线程:Thread-3,使用管道 (hashcode):1472283137
当前用户订单线程:Thread-3,使用管道(hashcode):1472283137
当前用户购买线程:Thread-4,使用管道 (hashcode):678585609
当前用户订单线程:Thread-4,使用管道(hashcode):678585609
根据结果可以看出,同一线程获取的connection管道是一样的,若存在多个数据源,则同一线程的同一数据源所获取的管道是一致的。
Spring Transaction 事务模拟
原文地址:https://www.cnblogs.com/ytcs8121/p/11331941.html