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