• 事务学习


    查询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传播机制,这些方法也能运行在事务环境下。

  • 相关阅读:
    log4net的使用
    数据库概念及构成
    基于C#WPF框架——动画
    基于C# WPF框架的贪吃蛇
    使用Teigha.net读取CAD的常用功能模块
    Teigha.net实体属性注释
    .net(C#数据库访问) Mysql,Sql server,Sqlite,Access四种数据库的连接方式
    Delphi解析修改Json文件,基于superobject.pas(ISuperObject)
    C++结构体与Delphi结构体相互传参,结构体中包含结构体的嵌套,数组指针
    C++ Json解析CJsonObject的详细使用
  • 原文地址:https://www.cnblogs.com/tjqBlog/p/10159824.html
Copyright © 2020-2023  润新知