java操作数据库的事务支持
一、需求背景:
我们生活经常遇到一个情况:在购买商品的时候,已经支付的了,那么商品应该处于已购买订单里。而不是付款之后,已购买商品没有。
还有转账的时候,转出方和转入方都需要扣减相应的金额,而不是一方减少或者增加。
因为上面的例子都是对数据操作,所以需要我们操作数据库的事务。
如何确定一个事务范围?
事务是由一系列数据库操作组成,他和业务场景有关。当操作完成的时候,如果操作过程没有出现失败,则这个事务产生的数据库操作一并提交(commit)。反之,如果出现失败,则一并回滚(rollback)。
所有事务和业务层(service)确定。
事务的特点:
ACID
1、原子性:事务里面的操作单元不可切割,要么全部成功,要么全部失败
2、一致性:事务执行前后,业务状态和其他业务状态保持一致.
3、隔离性:一个事务执行的时候最好不要受到其他事务的影响
4、持久性:一旦事务提交或者回滚.这个状态都要持久化到数据库中
不考虑隔离性会出现的读问题:
脏读:在一个事务中读取到另一个事务没有提交的数据
不可重复读:在一个事务中,两次查询的结果不一致(针对的update操作)
虚读(幻读):在一个事务中,两次查询的结果不一致(针对的insert操作)
通过设置数据库的隔离级别来避免上面的问题(理解)
read uncommitted 读未提交 上面的三个问题都会出现
read committed 读已提交 可以避免脏读的发生
repeatable read 可重复读 可以避免脏读和不可重复读的发生
serializable 串行化 可以避免所有的问题
两种方式:
1)数据库上控制事务:
默认情况下,mysql数据库中的事务是自动提交。
1 mysql> show variables like "%autocommit%"; 2 +---------------+-------+ 3 | Variable_name | Value | 4 +---------------+-------+ 5 | autocommit | ON | 6 +---------------+-------+ 7 1 row in set (0.00 sec
我们可以在数据库上进行事务的开启和关闭。但是这种方式显然不好。
2)java代码实现。
查看connection的方法:
1:void commit() 提交事务。Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection
object.
2:void rollback()回滚事务.Undoes all changes made in the current transaction and releases any database locks currently held by this Connection
object
3:void setAutocommit(boolean ) false 表示开启事务,true表示事务关闭。Sets this connection's auto-commit mode to the given state.
注意:事务要作用同一个数据库连接。
java代码实现有两种方式:
1、自上到下传递参数:
通过service传递到dao层的connection的变量,保证整个过程使用的是同一个数据连接。
2、当前线程绑定事务:
通过绑定当前线程一个变量(connection),在dao中获取这个connection,保证整个事务的过程中,使用的是同一个连接。
其中threadLocal方法:ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID)
该字段在类中要以private static 来进行修饰。
1) T get() 返回当前线程绑定的值。
2)void remove() 从当前线程解绑。
3)void set(T value) 绑定当前线程。
内部实现原理就是map方法对currentThread 进行put 和set remove。
jdbc工具类:
1 package jd.com.tool_jdbc; 2 3 import org.apache.commons.dbcp2.BasicDataSource; 4 import org.apache.commons.dbcp2.BasicDataSourceFactory; 5 6 7 import java.io.InputStream; 8 import java.sql.Connection; 9 import java.sql.PreparedStatement; 10 import java.sql.SQLException; 11 import java.util.Properties; 12 13 14 15 public class ds_tool { 16 private static BasicDataSource ds=null; 17 private static Connection con=null; 18 private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>(); 19 public static BasicDataSource getDs(){ 20 try { 21 InputStream inp= ds_tool.class.getClassLoader().getResourceAsStream("database.properties"); 22 Properties pro=new Properties(); 23 pro.load(inp); 24 ds=new BasicDataSourceFactory().createDataSource(pro); 25 ds.setInitialSize(4); 26 ds.setMaxTotal(8); 27 ds.setMaxIdle(2); 28 return ds; 29 }catch (Exception ex){ 30 throw new RuntimeException("初始化数据库失败"+ex); 31 } 32 33 } 34 //给当前线程绑定变量。 35 public static BasicDataSource setDs(){ 36 try { 37 if(con==null){ 38 ds=getDs(); 39 con=ds.getConnection(); 40 threadLocal.set(con); 41 }else{ 42 threadLocal.set(con); 43 } 44 return ds; 45 }catch (Exception ex){ 46 ex.printStackTrace(); 47 throw new RuntimeException("初始化错误"+ex); 48 } 49 50 } 51 //开启事务 52 public static void openEven(){ 53 try { 54 setDs(); 55 con=threadLocal.get(); 56 con.setAutoCommit(false); 57 }catch (Exception ex){ 58 ex.printStackTrace(); 59 } 60 61 } 62 //提交事务 63 public static void commEven(){ 64 try { 65 con=threadLocal.get(); 66 con.commit(); 67 }catch (Exception ex){ 68 ex.printStackTrace(); 69 } 70 71 } 72 //回滚事务 73 public static void rollbackEvent(){ 74 try { 75 con=threadLocal.get(); 76 System.out.println(con); 77 con.rollback(); 78 }catch (Exception ex){ 79 ex.printStackTrace(); 80 } 81 82 } 83 public static void closedDs(BasicDataSource ds, PreparedStatement pre){ 84 try { 85 ds.close(); 86 pre.close(); 87 threadLocal.remove(); 88 }catch (Exception ex){ 89 ex.printStackTrace(); 90 } 91 92 } 93 94 }
然后在业务层上进行事务的控制。
3)1、queryRunner()初始化的时候,不带参数,是默认是开启事务。
在执行的时候,update(connection con sql object ...parm)传入connection。需要关闭资源 connection。
2、如果quryRunner(Datasource ds)的时候,内部代码会帮我们实现资源回收。不需要connection.close()