事务
要么都成功,要么都失败。
在一个事件中的事:从通知开启事务,到提交事务之间,任何一句语句有错误,则哪一句语句都不应该提交。
1、事务的几个状态
1、开启事务
2、事务提交 commit()。在做事务提交和回滚前,关键的头尾是:(1)通知开启事务,false是开启(connection.setAutoCommit(false);) (2)提交或回滚(提交写在try部分,回滚写在catch中)
3、事务回滚 rollback()。事务一旦提交(commit)了,就无法回滚(rollback)了。所以,应该在提交之前判断,如果崩了就不要提交,直接回滚。
4、关闭事务
转账:
A:1千块
B:1千块
A --> B: A(900)、B(1100)
不能出现A转给了B100块钱,A成为900,但是系统崩溃了,B还没收到100块钱,出现了A900块钱,B1000块钱的情况。
1、搭建环境,进行测试

1 Create TABLE account( 2 id INT PRIMARY KEY auto_increment, 3 `name` VARCHAR(40), 4 money FLOAT 5 ); 6 INSERT INTO account(`name`,money) VALUES ('A',1000); 7 INSERT INTO account(`name`,money) VALUES ('B',1000); 8 INSERT INTO account(`name`,money) VALUES ('C',1000);
2、项目构成
1、建的普通Maven项目(不用模板)
2、在java下建con.wang.utils文件夹,并在该文件夹下建两个class文件。
3、两个class文件中,一个是JDBC的工具类(JdbcUtils),公共的资源配置放在这;另一个是具体的操作数据库的类(Test_jdbc)
4、连接数据库的用户名,密码,数据库驱动和url都放在resources下的db.properties文件夹中。

driver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/jdbc_example?useUnicode=true&characterEncoding=utf8&useSSL=true username = root password = 123456
5、导入mysql依赖。此外,为实现上一节提到的junit单元测试,还要导入junit依赖

<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
3、写JDBC的工具类(JdbcUtils)

package com.wang.utils; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class JdbcUtils { private static String driver = null; private static String url = null; private static String username = null; private static String password = null; static { try { InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties"); Properties properties = new Properties(); properties.load(in); driver = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); //驱动只用加载一次 Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } //获取连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } //释放连接资源 public static void release(Connection conn, Statement st, ResultSet rs){ if(rs!=null){ try { rs.close(); }catch (SQLException e){ e.printStackTrace(); } } if(st!=null){ try { st.close(); }catch (SQLException e){ e.printStackTrace(); } } if(conn!=null){ try { conn.close(); }catch (SQLException e){ e.printStackTrace(); } } } }
4、写具体的操作数据库的类(Test_jdbc)
—— 模拟转账,并测试失败情况下的回滚和成功情况下的事务提交
情况1:当写第4步,故意制造错误的时候,看看第3步的SQL语句,提交还是回滚

1 package com.wang.utils; 2 import org.junit.Test; 3 4 import java.sql.Connection; 5 import java.sql.DriverManager; 6 import java.sql.SQLException; 7 public class Test_jdbc { 8 @Test 9 public void test() throws Exception { 10 //配置信息 11 //String driver = "com.mysql.jdbc.Driver"; 12 String url = "jdbc:mysql://localhost:3306/jdbc_example?useUnicode=true&characterEncoding=utf8&useSSL=true"; 13 String username = "root"; 14 String password = "123456"; 15 Connection connection = null; 16 try { 17 //第一步,加载驱动 18 Class.forName("com.mysql.jdbc.Driver"); 19 //第二步,连接数据库 20 connection = DriverManager.getConnection(url, username, password); 21 //第三步,通知开启事务。false是开启 22 connection.setAutoCommit(false); 23 //第四步,执行SQL语句 24 String sql = "update account set money = money - 100 where name = 'A'"; 25 connection.prepareStatement(sql).executeUpdate(); 26 //第五步,制造错误 27 int i = 1/0; 28 //第六步,写第二句SQL语句。理论上,第五步出现错误,所以这一句应该也不执行,不应该提交,应该回滚 29 String sql2 = "update account set money = money + 100 where name = 'B'"; 30 connection.prepareStatement(sql2).executeUpdate(); 31 //第七步,提交 32 connection.commit(); 33 //第八步,若上面两天SQl都提交成功,则输出“提交成功” 34 System.out.println("提交成功!"); 35 } catch (Exception e) { 36 try { 37 //如果try中的方法有错误,就走catch,通知数据库执行回滚事务 38 connection.rollback(); 39 } catch (SQLException e1) { 40 e1.printStackTrace(); 41 } 42 e.printStackTrace(); 43 } finally { 44 //释放资源 45 connection.close(); 46 } 47 } 48 }
2、当注释掉第五步,代码没有错误,则提交事务

package com.wang.utils; import org.junit.Test; import java.sql.*; public class Test_jdbc { @Test public void test() throws Exception { Connection connection = null; Statement st = null; ResultSet rs = null; try { //第一步,连接数据库 connection = JdbcUtils.getConnection(); //第二步,通知开启事务。false是开启 connection.setAutoCommit(false); //第三步,执行SQL语句 String sql = "update account set money = money - 100 where name = 'A'"; connection.prepareStatement(sql).executeUpdate(); //第四步,故意制造错误 //int i = 1/0; //第五步,写第二句SQL语句。 //注释掉第四步错误,第三步和第五步的sql和sql2语句都提交 String sql2 = "update account set money = money + 100 where name = 'B'"; connection.prepareStatement(sql2).executeUpdate(); //第六步,提交 connection.commit(); //第七步,若上面两条SQl都提交成功,则输出“提交成功” System.out.println("提交成功!"); } catch (Exception e) { try { //如果try中的方法有错误,就走catch,通知数据库执行回滚事务 connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } finally { //释放资源 JdbcUtils.release(connection,st,rs); } } }
5、上述的关键在第3步,通知开启事务:connection.setAutoCommit(false); false是开启
若不通知开启事务,即使有错误,也不会回滚,直接就提交了。