Spring Transaction 事务模拟

2019-08-16 12:17:14来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

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 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管道是一样的,若存在多个数据源,则同一线程的同一数据源所获取的管道是一致的。                

原文链接:https://www.cnblogs.com/ytcs8121/p/11331941.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:1、Java小白之路前言

下一篇:Spring事务源码