自己动手,了解jdbc的事务特性
先建测试表
[a] [int] NULL ,
[b] [int] NULL
) ON [PRIMARY]
GO
下面是测试方法,在一个事务中,先count(*)一次,再插入一条数据错误数据,一条正确数据,再count(*)一次,然后提交。
都是对空表操作
2 Connection conn = getConnection_tx();
3 try {
4 conn.setAutoCommit(false);
5 Statement st = conn.createStatement();
6
7 ResultSet r1 = st.executeQuery("select count(*) from aaa");//query_1
8 r1.next();
9 System.out.println(r1.getInt(1));
10
11 try {
12 st.executeUpdate("INSERT INTO aaa values ('abc',22)"); //execute_1
13 } catch (Exception e) {
14 e.printStackTrace();
15 }
16 st.executeUpdate("INSERT INTO aaa values (33,22)");//execute_2
17 ResultSet i = st.executeQuery("select count(*) from aaa");//query_2
18 i.next();
19 System.out.println(i.getInt(1));
20 conn.commit();
21 } catch (SQLException e) {
22 if (conn != null) {
23 try {
24 conn.rollback();
25 } catch (SQLException e1) {
26 e1.printStackTrace();
27 }
28 }
29 e.printStackTrace();
30 } finally {
31 if (conn != null) {
32 try {
33 conn.close();
34 } catch (SQLException e1) {
35 e1.printStackTrace();
36 }
37 }
38 }
39 }
40
41
分析:以上执行过程中,query_1返回0,query_2返回1。这说明了:
- execute_1执行失败,但并未影响execute_2执行。
这点说明了sql执行错误是在execute_1的时候就抛出,而不是在commit的时候,因此事务没有rollback。
- 第二次执行count语句(query_2)虽然在commit之前,但数量依然比第一次增加了1。
这点说明在一个事务中,select语句并不只针对数据库的实际内容,而还要算上事务提交之前的所有做过的改变。
再看下面两个线程,SelectThread1 查询了两次,设间隔5秒
SelectThread2 插入一次,8秒后提交.
两个线程同时对空表操作
结果:query_1返回0,query_2返回1,但是query_1和query_2的实际执行间隔并不是5秒,而是接近8秒。
2
3 public void run() {
4 Connection conn = DBControl.getConnection_tx();
5 try {
6 try {
7 TimeUnit.MILLISECONDS.sleep(500);
8 } catch (InterruptedException e) {
9 return;
10 }
11 Statement st = conn.createStatement();
12 ResultSet r1 = st.executeQuery("select count(*) from aaa");//query_1 #2
13 r1.next();
14 System.out.println(r1.getInt(1));
15 try {
16 TimeUnit.SECONDS.sleep(5);
17 } catch (InterruptedException e) {
18 return ;
19 }
20 ResultSet i = st.executeQuery("select count(*) from aaa");//query_2 #5
21 i.next();
22 System.out.println(i.getInt(1));
23
24 } catch (SQLException e) {
25 e.printStackTrace();
26 }finally{
27 if(conn!=null){
28 try {
29 conn.close();
30 } catch (SQLException e) {
31 e.printStackTrace();
32 }
33 }
34 }
35
36 }
37 }
38
39 class SelectThread2 implements Runnable{
40
41 public void run() {
42 Connection conn = DBControl.getConnection_tx();
43 try {
44 conn.setAutoCommit(false);// #1
45 Statement st = conn.createStatement();
46 try {
47 TimeUnit.SECONDS.sleep(3);
48 } catch (InterruptedException e) {
49 return ;
50 }
51 st.executeUpdate("INSERT INTO aaa values (33,22)"); //#3
52 //st.executeQuery("select count(*) from aaa");
53 try {
54 TimeUnit.SECONDS.sleep(8);
55 } catch (InterruptedException e) {
56 return ;
57 }
58 conn.commit(); //#4
59
60 } catch (SQLException e) {
61 if (conn != null) {
62 try {
63 conn.rollback();
64 } catch (SQLException e1) {
65 e1.printStackTrace();
66 }
67 }
68 e.printStackTrace();
69 } finally {
70 if (conn != null) {
71 try {
72 conn.close();
73 } catch (SQLException e1) {
74 e1.printStackTrace();
75 }
76 }
77 }
78
79 }
80 }
81
82 public class SelectThread{
83
84 public static void main(String[] args){
85 ExecutorService es = Executors.newCachedThreadPool();
86 es.execute(new SelectThread1());
87 es.execute(new SelectThread2());
88 es.shutdown();
89 }
90
91 }
92
93
分析:我使用了sleep()人工影响到各关键步骤的执行顺序。
再加上运行结果,标出了关键步骤执行的先后顺序(#开头)。
从这个顺序可以得到几点比较关键的结论:
- 一个事务对数据库独占以后,不在该事务中的select语句的执行也被阻塞。
- 事务并不是从conn.setAutoCommit(false);开始,而是从st.executeUpdate("INSERT INTO aaa values (33,22)");开始。
也就是有实际增删改操作时,事务才开始。
- 如果把#3那一步换成下面那句注释语句,则query_1和query_2实际执行间隔又成了5秒,也就是#4和#5先后顺序就反过来了。
说明执行select语句并不会开始一个事务,也完全不需要commit。