zoukankan      html  css  js  c++  java
  • 事务学习

    查询mysql默认隔离级别,可以看到 全局和session的级别均为可重复读。

    可重复读:是指在同一事务内 多次查询得到的结果是一致的。意味着在同一事务A内,多次查询的结果是一致的,不管其他事务B C D对于当前事务的操作是否提交更新,事务A中的读取结果始终一致,只有当事务A提交 当前session重新开启新事务 读到的结果才是B C D操作后的数据结果

     1 import org.slf4j.LoggerFactory;
     2 
     3 import java.sql.Connection;
     4 import java.sql.DriverManager;
     5 import java.sql.PreparedStatement;
     6 import java.sql.SQLException;
     7 
     8 public class LocalTranJdbcApplication {
     9 
    10     private static final Logger LOG = LoggerFactory.getLogger(LocalTranJdbcApplication.class);
    11 
    12     public static void main(String[] args) throws SQLException {
    13 
    14 
    15         String plusAmountSQL = "UPDATE T_USER SET amount = amount + 100 WHERE username = ? ";
    16         String minusAmountSQL = "UPDATE T_USER SET amount = amount - 100 WHERE username = ? ";
    17 
    18         Connection dbConnection = getDBConnection();
    19         LOG.debug("Begin");
    20         dbConnection.setAutoCommit(false);
    21 
    22         PreparedStatement plusAmountPS = dbConnection.prepareStatement(plusAmountSQL);
    23         plusAmountPS.setString(1, "aa");
    24         plusAmountPS.executeUpdate();
    25 
    26         PreparedStatement minusAmountPS = dbConnection.prepareStatement(minusAmountSQL);
    27         minusAmountPS.setString(1, "bb");
    28         minusAmountPS.executeUpdate();
    29 
    30         dbConnection.commit();//在这里打断点,并且debug启动,LocalTranJdbcApplication2 会阻塞(37行会被阻塞),
    31         //因为mysql会先等到我们这个事务提交,LocalTranJdbcApplication2中的更新才会执行
    32         LOG.debug("Done!");
    33 
    34         plusAmountPS.close();
    35         minusAmountPS.close();
    36         dbConnection.close();
    37     }
    38 private static Connection getDBConnection() throws SQLException {
    39         String DB_DRIVER = "com.mysql.jdbc.Driver";
    40         String DB_CONNECTION = "jdbc:mysql://localhost:3306/dist_tran_course";
    41         String DB_USER = "root";
    42         String DB_PASSWORD = "123456";
    43         try {
    44             Class.forName(DB_DRIVER);
    45         } catch (ClassNotFoundException e) {
    46             LOG.error(e.getMessage());
    47         }
    48         return DriverManager.getConnection(DB_CONNECTION, DB_USER, DB_PASSWORD);
    49     }
    50 }
     1 import org.slf4j.Logger;
     2 import org.slf4j.LoggerFactory;
     3 
     4 import java.sql.*;
     5 
     6 public class LocalTranJdbcApplication2 {
     7 
     8     private static final Logger LOG = LoggerFactory.getLogger(LocalTranJdbcApplication2.class);
     9 
    10     public static void main(String[] args) throws SQLException {
    11 
    12 //      String sql = "SELECT * FROM T_USER Where id =1 FOR UPDATE";
    13         //在里面的FOR UPDATE的意思是将获取的结果加锁,打开这条语句的时候,程序将会阻塞在第23行
    14         //既然要加锁,那么首先得获取锁(也就意味着需要等到其他操作这条数据事务线程提交(释放锁)),另外使用FOR UPDATE的时候,
    15         //一般锁需要对查询结果加限制条件 比如上面的id=1,不然容易锁全表,加限制条件 少量结果行
    16         String sql = "SELECT * FROM T_USER ";
    17         String plusAmountSQL = "UPDATE T_USER SET amount = ? WHERE username = ?";
    18 
    19         Connection dbConnection = getDBConnection();
    20         LOG.debug("Begin session2");
    21 
    22         PreparedStatement queryPS = dbConnection.prepareStatement(sql);
    23         ResultSet rs = queryPS.executeQuery();
    24         Long superManAmount = 0L;
    25         while (rs.next()) {
    26             String name = rs.getString(2);
    27             Long amount = rs.getLong(3);
    28             LOG.info("{} has amount:{}", name, amount);
    29             if (name.equals("aa")) {
    30                 superManAmount = amount;
    31             }
    32         }
    33 
    34         PreparedStatement updatePS = dbConnection.prepareStatement(plusAmountSQL);
    35         updatePS.setLong(1, superManAmount + 100);
    36         updatePS.setString(2, "aa");
    37         updatePS.executeUpdate();
    38 
    39         LOG.debug("Done session2!");
    40         queryPS.close();
    41         updatePS.close();
    42         dbConnection.close();
    43     }
    44 
    45     private static Connection getDBConnection() throws SQLException {
    46         String DB_DRIVER = "com.mysql.jdbc.Driver";
    47         String DB_CONNECTION = "jdbc:mysql://localhost:3306/dist_tran_course";
    48         String DB_USER = "root";
    49         String DB_PASSWORD = "123456";
    50         try {
    51             Class.forName(DB_DRIVER);
    52         } catch (ClassNotFoundException e) {
    53             LOG.error(e.getMessage());
    54         }
    55         return DriverManager.getConnection(DB_CONNECTION, DB_USER, DB_PASSWORD);
    56     }
    57 }

      ThreadLocal在bean的管理中的使用

      当运行于多线程环境下的某个对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程独立分配一个变量副本,每个线程都使用自己的变量副本,从而不影响其他其他线程。

      

    public class SequenNumber {
        private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
            public Integer initialValue() {
                return 0;
            }
        };
        
        public int getNextNum() {
            seqNum.set(seqNum.get()+1);
            return seqNum.get();
        }
        
        public static void main(String[] args) {
            SequenNumber sn = new SequenNumber();
            TestClient t1 = new TestClient(sn);
            TestClient t2 = new TestClient(sn);
            TestClient t3 = new TestClient(sn);
            t1.start();
            t2.start();
            t3.start();
        }
        
        private static class TestClient extends Thread {    
            private SequenNumber sn;
    
            public TestClient(SequenNumber sn) {
                super();
                this.sn = sn;
            }
        
            @Override
            public void run() {
                System.out.println(sn.hashCode());
                for(int i=0;i<3;i++) {
                    System.out.println(Thread.currentThread().getName()+"---"+sn.getNextNum());
                }
                
            }
            
        }
    }
    1304030204
    1304030204
    1304030204
    Thread-0---1
    Thread-1---1
    Thread-0---2
    Thread-2---1
    Thread-0---3
    Thread-1---2
    Thread-1---3
    Thread-2---2
    Thread-2---3

    结果里面可以看出,每个线程都共享一个SequenNumber对象,但是实际上他们没有相互干扰,各自产生自己的序列号,因为ThreadLocal为每个线程提供一个单独的副本。

       一般来说,只有无状态bean才能在多线程环境下被设计为单例的,但是spring里面的bean绝大部分是单例的, 然而这些bean并不是无状态的,spring使用ThreadLocal对这些无状态的bean进行封装,让他们能以单例在多线程环境下运行。例如 web应用中 划分为三层 Controller Service Dao,一般来说从接受请求到返回响应的所有经过过程调用均是同一线程,这样用户就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一请求响应过程中,所有对象访问的是同一ThreadLocal变量所绑定的。下面演示这一思路。

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestDao {
        private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<>();
        public static Connection getConnection() {
            Connection conn = connThreadLocal.get();
            if(conn==null) {
                try {
                    conn = DriverManager.getConnection("自己的数据库配置");
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                connThreadLocal.set(conn);
            }
            return conn;
        }
        
        public void addData() throws Exception{
            Statement statement =getConnection().createStatement();
            //执行数据库操作
        }
    }

    不同的请求对应的线程在使用TestDao时先通过ThreadLocal对象判断当前线程是否持有Connection对象,如果没有那么创建一个 并且将其设置到ThreadLocal中,如果不为null,说明当前线程已经拥有Connection对象 可以直接使用,这样就可以保证不同的线程使用自己独立的Connection对象,而不会使用其他线程的Connection对象,此时 TestDao就可以被设置为单例的了。当然 这个例子是比较粗糙的 只能实现TestDao的多个方法共享一个Connection对象,无法实现其他dao也共同使用一个Connection对象,,要做到这一点需要在一个外部的共同类使用ThreadLocal保存Connection对象,这个例子只是说明一下spring改造bean的思路。

      

     spring 通过TransactionSynchronizationManager将Dao Service中影响线程安全的所有状态(资源,事务名,事务状态,隔离级别,事务激活状态等等)统一抽取到这个类中,因此Dao和Service可以摘掉非线程安全的帽子 使用单例来运行。

      事务的传播行为,事务传播是spring事务管理的重要概念 重要不言而喻。

      

      spring中默认的事务传播行为时PROPAGATION_REQUIRED,当多个事务方法嵌套调用时,那么这些事务方法就工作在同一事务环境下,后面的事务方法会加入到前面的事务环境下,也就是说 能统一回滚。

      基于jdk事务增强的方法必须是 public修饰的并且不能是 public static修饰的

      基于CGlib的事务增强的方法不能是private ,static,final 修饰的。 

      上面两类不能增强的情况是指这些方法不能启动事务,并不是说这些方法就不能运行在事务环境下,当存在方法嵌套时,如果这些方法嵌套外层是事务环境,那么根据spring传播机制,这些方法也能运行在事务环境下。

  • 相关阅读:
    LeetCode 769. Max Chunks To Make Sorted
    LeetCode 845. Longest Mountain in Array
    LeetCode 1059. All Paths from Source Lead to Destination
    1129. Shortest Path with Alternating Colors
    LeetCode 785. Is Graph Bipartite?
    LeetCode 802. Find Eventual Safe States
    LeetCode 1043. Partition Array for Maximum Sum
    LeetCode 841. Keys and Rooms
    LeetCode 1061. Lexicographically Smallest Equivalent String
    LeetCode 1102. Path With Maximum Minimum Value
  • 原文地址:https://www.cnblogs.com/tjqBlog/p/10159824.html
Copyright © 2011-2022 走看看