首页 > 代码库 > Spring -- 5.1

Spring -- 5.1

Spring事务管理的一些基础知识

  • JDBC对事务的支持
    首先要知道并不是所有的数据库都支持事务,即使支持也并非支持所有的事务隔离级别,我们可以通过Connection#getMetaData()方法获取DataBaseMetaData(数据库元数据)对象,并通过该对象的supportsTransactions()、supportsTransationIsolationLevel(int level)来查看底层数据库的事务支持情况。

    JDBC的Connection对象默认是自动提交事务的,也即每条执行的SQL都对应一个事务。现在再回头想想自己刚开始写JDBC链接数据库的时候,对事务不清楚,也没有提交事务的意识,之所以没有出错很可能就是这个自动提交事务的作用。但是如果我们要将多条sql语句封装到一个事务中,这中写法就不行了(对每一条sql创建一个事务,执行完立刻提交的默认方式),我们必须手动做一些事务上操作:首先Connection#setAutoCommit(false)阻止Connection自动提交事务,通过Conneciton#setTransactionIsolation()设置事务的隔离级别,然后通过Connection#commit()提交事务,Connection#rollback()回滚事务(撤销同一个事务中的数据库操作)。一个典型的的jdbc事务数据操作代码,附图:

    咱们再深入一点来看看这个JDBC的事务。
    在jdk2.0中,事务最终只能有两个操作:提交和回滚。在jdk1.4及以后的版本中,引入了一个全新的保存点特性,savePoint接口允许用户将事务分割为多个阶段,用户可以指定回滚到特定的保存点。


    并非所有的数据库都支持保存点功能,我们可以通过DataBaseMetaData#supportsSavepoints()方法查看是否支持。
  • ThreadLocal基础和Spring使用ThreadLocal解决数据库访问的线程安全问题

    About ThreadLocal

    java.lang.ThreadLocal是为解决多线程程序的并发问题而提供的一种新的解决思路。ThreadLocal,顾名思义,是线程的一个本地化变量。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程分配一个独立的变量副本。每一个线程都可以独立的改变自己的变量副本,而不会影响其他线程的变量副本。我们从线程的角度看,这个变量就像是线程的本地变量,这也是类名中Local所要表达的意思。简而言之,同一个类,在不同的线程中,从ThreadLocal中取得自己对应的变量副本,解决多线程共享状态变量的安全性问题。
    我们考究一下ThreadLocal如何做到为每一个线程维护一份独立的变量副本呢?实现思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
    写一个例子吧
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
        //通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值。
        private static ThreadLocal<Integer> tdLocal = new ThreadLocal<Integer>(){
            public Integer initialValue(){
                return 0;
            }
        };
         
        //获取下一个序列值。
        public int getNextNum(){
            tdLocal.set(tdLocal.get()+1);
            return tdLocal.get();
        }
         
        //私有的测试线程类。
        private static class TestClient extends Thread{
            private SequenceNum seqNum;
            public TestClient(SequenceNum seqNum){
                this.seqNum = seqNum;
            }
            public void run(){
                for(int i=0;i<3;i++){
                    System.out.println("Thread is ["
                            + Thread.currentThread().getName() + "],序列号是  "
                            + seqNum.getNextNum());
                }
            }
        }
         
         
        public static void main(String[] args) {
            SequenceNum seqNum = new SequenceNum();
            TestClient client1 = new TestClient(seqNum);
            TestClient client2 = new TestClient(seqNum);
            TestClient client3 = new TestClient(seqNum);
            TestClient client4 = new TestClient(seqNum);
            //运行线程。
            client1.start();
            client2.start();
            client3.start();
            client4.start();
        }
    ==================================================
    Thread is [Thread-1],序列号是  1
    Thread is [Thread-2],序列号是  1
    Thread is [Thread-0],序列号是  1
    Thread is [Thread-3],序列号是  1
    Thread is [Thread-3],序列号是  2
    Thread is [Thread-0],序列号是  2
    Thread is [Thread-2],序列号是  2
    Thread is [Thread-2],序列号是  3
    Thread is [Thread-1],序列号是  2
    Thread is [Thread-0],序列号是  3
    Thread is [Thread-3],序列号是  3
    Thread is [Thread-1],序列号是  3

    我们看到这四个线程共享了同一个SequenceNum实例,但是线程之间并没有相互的影响,而是各自产生独立的序列号,不存在线程安全性的问题,这是因为我们通过ThreadLocal为每一个线程提供了单独的变量副本。这也是Spring解决线程安全的机制。
    ????
    书中有一句话很生动形象的对比了同步机制和ThreadLocal解决线程安全问题不同点:对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化;而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独立化。前者提供一份变量,让不同的线程排队访问,而后者为每一份线程提供了一份变量,因此可以同时访问而互不影响。

    Spring使用ThreadLocal解决线程安全的问题。
    我们大概都有这么一个概念,只有无状态的Bean才能在多线程环境下共享,在Spring中,绝大部分的Bean都可以声明为singleton作用域。因为Spring对一些Bean(如RequestContextHolder,TranscationSynchronizationManager,LocalContextHolder)中非线程安全的“状态性对象”采用ThreadLocal进行封装,把他们变成线程安全的“状态性对象”,因此有状态的Bean能够以Singleton的方式在多线程中正常工作了。这里小可我说一下我的具体感受:原来Spring注入的JdbcTemplate是单例模式!其实就一个模板对象,把他注入到多个Service中,原来怎么没有注意到这一点呢?还有对有状态对象的理解,这个说的直白一点就是这个变量在不同的线程中不一样,尼玛取了一个有状态对象(变量)感觉好迷糊人啊!呵呵
    看看书中对多线程的理解:
    一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写不同的逻辑,下次通过接口向上层开发接口调用。在一般的情况下,从接收请求到返回相应所经过的所有程序调用都同属于一个线程(看到这句话,我就想到了Servlet和CGI的区别,一个请求到来Servlet就会新建一个线程去响应这个请求,而CGI则是需要新建一个进程,所以Servlet的效率更高。尼玛这是以前看过的一个面试题啊,终于对上了),如图:

    这样根据需要,把一些非线程安全的变量存放到ThreadLocal中,在同一次请求响应的调用线程中,所有对象访问同一ThreadLocal变量都是当前线程绑定的。
    怎么理解Spring中DAO和Service都是以单实例的bean形式存在这句话,就是不同的线程调用的都是同一个DAO和Service的实例,只有一个实例。
    http://blog.csdn.net/c289054531/article/category/1473443