Spring Transaction 事务模拟

    事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败。
    事务有四个特性:
        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 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管道是一样的,若存在多个数据源,则同一线程的同一数据源所获取的管道是一致的。
 
 
 
         

猜你喜欢

转载自www.cnblogs.com/ytcs8121/p/11331941.html