java事务笔记【1】

一. 什么是事务:
所谓事务,就是针对数据库的一组操作(多条sql)
位于同一个事务的操作具备同步的特点,也就是要么都成功,要么都失败

二. 事务的作用:
在实际中,我们的很多操作都是需要由多条sql来共同完成的,例如,A账户给B账户转账就会对应两条sql
update account set money = money - 100 where name = ‘a’;
update account set money = money + 100 where name = ‘b’;
假设第一条sql成功了,而第二条sql失败了,这样就会导致a账户损失了100元,而b账户并未得到100元
如果将两条sql放在一个sql中,当第二条语句失败时,第一条sql语句也同样不会生效,

这样a账户就不会有任何的损失


三. 事务的实现原理:
默认情况下,我们向数据库发送的sql语句是会被自动提交的,开启事务就是相当于关闭自动提交功能,改为手动提交,我们只需要将提交事务的操作放在最后一个操作,这样一来,如果在提交事务之前出现异常,由于没有执行提交操作,事务中未提交的操作就会被回滚掉


四. 在程序中使用 jdbc 开启事务
在使用 jdbc 操作数据库时,需要使用 Connection 对象对事务进行管理
// 开启事务
Connection.setAutoCommit(false); //设置自动提交为false
// 回滚事务
Connection.rollback();
//提交事务
Connection.commit();

在 jdbc 程序中我们还可以设置回滚点,让事务回滚到指定的回滚点,而不是自动回滚所有未提交的操作
需要将程序中的异常捕获,在catch语句块中回滚事务,在finally中提交事务
注意 ,将 Commit 操作放在 finally 中是为了保证提交未回滚的事务操作


五. 事务的特性
事务有四大特性,一般来讲,判断一个数据库是否支持事务,就看数据库是否支持这四个特性
1. 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 

2. 一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

3. 隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

4. 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。


六. 事务的隔离级别
1)多线程开启事务
由于数据库是多线程并发访问的,所以很容易出现多个线程同时开启事务的情况
多线程开事务容易引起 赃读、不可重复读、幻读 的情况发生

##  赃读:dirty read  ##
是指一个事务读取了另一个事务未提交的数据,这是相当危险的。
设想一下,A要给B转账100元购买商品, 如果A开启了一个事务做了转账的工作
update account set money = money + 100 while name = ‘b’;
update account set money = money - 100 while name = ‘a’;
A先不提交事务,通知B来查询
这时B来查询账户,由于会读到A开启的事务中未提交的数据,就会发现A确实给自己转了100元,
自然就会给A发货,A等B发货后就将事务回滚,不提交,此时,B就会受到损失

##  不可重复读:non-repeatable read  ##
是指事务中两次查询的结果不一致,原因是在查询的过程中其他事务做了更新的操作 update
例如,银行做报表,第一次查询A账户有100元,第二次查询A账户有200元,原因是期间A存了100元
这样就会导致一行多次统计的报表不一致

和脏读的区别是:
脏读是读取前一事务未提交的脏数据,不可重复读是在事务内重复读取了别的线程已提交的数据。

有的时候大家会认为这样的结果是正确的,没问题
我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。

##  幻读:phantom read  又名虚读  ##
是指在一个事务内两次查询中数据笔数不一致
幻读和不可重复读有些类似,是指两次查询过程中,其他事务做了插入记录的操作,导致记录数有所增加insert
例如银行做报表统计account表中所有用户的总额时,此时总共五个账户,总金额为500元,这时有一个新的账户产生了,并且
存了100元,这时银行再统计会发现帐户总金额为600元了,造成虚读同样会使银行遇到同样的困惑

2)设置事务的隔离级别
为了避免多线程开事务引发的问题,我们需要将事务进行隔离
事务有四种隔离级别,不同的隔离级别可以防止不同的错误发生
serializable:可串行化,能避免脏读、不可重复读、幻读情况的发生
repeatable read:可重读,能避免脏读、不可重复读情况的发生
read committed:读取提交的内容,可避免脏读情况发生
read uncommitted:读取未提交的内容最低级别,避免不了任何情况

不同的需求就会采取不同的隔离级别, 而且不同的隔离级别他的性能也不一样

操作:
设置事务隔离级别
set   transaction isolation level 
查询当前事务隔离级别
select @@tx_isolation

查询看到的都是快照<其实就是设置事务前的一个拷贝,>
位于事务中的多次查询,如果隔离级别设置为repeatable read,那么多次查询读的就是一个快照, 说白了就是不更新快照

一. 为什么要传递Connection?

在前面的概述中我们知道, JDBC事务处理的作用对象为Connection, 因此要想控制操作在同一个事务里面, 

我们必须要传递Connection, 确保使用的是同一个Connection.


二. 如何传递Connection?

本实例使用转账的例子: 即从A账户转100元到B账户, 这需要做两次update表操作

1. 代码结构图:

java事务笔记【1】

2. 建表语句:

[sql]  view plain copy
  1. DROP TABLE IF EXISTS `account`;  
  2. CREATE TABLE `account` (  
  3.   `id` int(10) NOT NULL,  
  4.   `namevarchar(20) NOT NULL,  
  5.   `money` int(20) NOT NULL,  
  6.   PRIMARY KEY  (`id`)  
  7. )   
  8.   
  9. INSERT INTO `account` VALUES ('1''lucy''1000');  
  10. INSERT INTO `account` VALUES ('2''lili''1000');  

3. 实体类:

[java]  view plain copy
  1. public class Account {  
  2.     private int id;  
  3.     private String name;  
  4.     private int money;  
  5.   
  6.     // getter and setter  
  7. }  

4. C3P0连接池配置:

[html]  view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <c3p0-config>  
  3.     <default-config>  
  4.         <property name="driverClass">com.mysql.jdbc.Driver</property>  
  5.         <property name="jdbcUrl">jdbc:mysql:///test</property>  
  6.         <property name="user">root</property>  
  7.         <property name="password">root</property>  
  8.   
  9.         <property name="acquireIncrement">50</property>  
  10.         <property name="initialPoolSize">100</property>  
  11.         <property name="minPoolSize">50</property>  
  12.         <property name="maxPoolSize">1000</property>  
  13.     </default-config>   
  14. </c3p0-config>   

5. JDBC工具类:

[java]  view plain copy
  1. public class JDBCUtils {  
  2.     private static DataSource dataSource;  
  3.     static {  
  4.         // 加载C3P0连接池  
  5.         dataSource = new ComboPooledDataSource();  
  6.     }  
  7.   
  8.     public static DataSource getDataSource() {  
  9.         return dataSource;  
  10.     }  
  11.   
  12.     public static Connection getConnection() throws SQLException {  
  13.         return dataSource.getConnection();  
  14.     }  
  15.   
  16. }  

6. 业务逻辑类:

[java]  view plain copy
  1. /** 
  2.  * 业务逻辑层 
  3.  */  
  4. public class AccountService {  
  5.       
  6.     public void transfer(Account outAccount, Account inAccount, int money) throws SQLException {  
  7.           
  8.         // 开启 事务   
  9.         Connection conn = JDBCUtils.getConnection();  
  10.         conn.setAutoCommit(false);  
  11.   
  12.         // 查询两个账户  
  13.         AccountDAO accountDAO = new AccountDAO();  
  14.         outAccount = accountDAO.findAccountById(outAccount.getId());  
  15.         inAccount = accountDAO.findAccountById(inAccount.getId());  
  16.   
  17.         // 转账 - 修改原账户金额   
  18.         outAccount.setMoney(outAccount.getMoney() - money);  
  19.         inAccount.setMoney(inAccount.getMoney() + money);  
  20.   
  21.         try {  
  22.             // 更新账户金额, 注意: 这里往Dao层传递连接  
  23.             accountDAO.update(outAccount, conn);  
  24.             // int x = 1 / 0;   
  25.             accountDAO.update(inAccount, conn);  
  26.               
  27.             // 转账成功, 提交事务  
  28.             conn.commit();  
  29.         } catch (Exception e) {  
  30.             // 转账失败, 回滚事务  
  31.             conn.rollback();  
  32.             e.printStackTrace();  
  33.         }  
  34.     }  
  35. }  

7. Dao类:

[java]  view plain copy
  1. /** 
  2.  * DAO层: CRUD 
  3.  */  
  4. public class AccountDAO {  
  5.       
  6.     // 查询账户  
  7.     public Account findAccountById(int id) throws SQLException {  
  8.         String sql = "select * from account where id = ?";  
  9.         Object[] params = {id};  
  10.         QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());  
  11.         return queryRunner.query(sql, new BeanHandler<Account>(Account.class), params);  
  12.     }  
  13.       
  14.     // 更新账户: 接受传递的连接  
  15.     public void update(Account account, Connection conn) throws SQLException {  
  16.         String sql = "update account set name = ?, money = ? where id = ?";  
  17.         Object[] params = {account.getName(), account.getMoney(), account.getId()};  
  18.         QueryRunner queryRunner = new QueryRunner();  
  19.         queryRunner.update(conn, sql, params);  
  20.     }  
  21. }  

8. 测试类:

[java]  view plain copy
  1. public class TransferTest {  
  2.       
  3.     @Test  
  4.     public void transferTest() throws SQLException {  
  5.         Account out = new Account();  
  6.         out.setId(1);  
  7.           
  8.         Account in = new Account();  
  9.         in.setId(2);  
  10.         AccountService accountService = new AccountService();  
  11.         accountService.transfer(out, in, 100);  
  12.     }  
  13. }  


三. 总结:

上面传递Connection对象的方法虽然可以完成事务处理的目的, 但是这样的做法是丑陋的, 原因在于: 为了完成事务处理的目的, 

我们需要将一个底层Connection类在service层和Dao层之间进行传递, Dao层的方法需要接受这个Connection对象, 这种做法是典型的API污染.


源码下载: http://download.csdn.net/detail/zdp072/7908249

一. 为什么使用ThreadLocal:

在上一篇博文中, 我们通过传递Connection的方式来控制事务, 这种方法可以达到目的, 但让人看的不爽, 

如果涉及到调用多个service, 那我是不是还得从controller层传递Connection?


ThreadLocal的用法见上一篇博客, 该类保证一个类的实例变量在各个线程中都有一份单独的拷贝, 

从而不会影响其他线程中的实例变量,所以ThreadLocal可以实现线程范围内数据共享。


二. 如何使用ThreadLocal:

1. 写一个TransactionManager类:

[java]  view plain copy 在CODE上查看代码片
  1. /** 
  2.  * 管理事务 
  3.  */  
  4. public class TransactionManager {  
  5.     private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();  
  6.   
  7.     // 开启事务  
  8.     public static void beginTransaction() throws SQLException {  
  9.         Connection conn = JDBCUtils.getConnection();  
  10.         conn.setAutoCommit(false);   
  11.         // 将连接存入threadLocal  
  12.         local.set(conn);  
  13.     }  
  14.   
  15.     // 回滚事务  
  16.     public static void rollback() throws SQLException {  
  17.         Connection conn = local.get();  
  18.         if (conn != null) {  
  19.             conn.rollback();  
  20.             conn.close();  
  21.             // 清空threadLocal  
  22.             local.remove();  
  23.         }  
  24.     }  
  25.   
  26.     // 提交事务  
  27.     public static void commit() throws SQLException {  
  28.         Connection conn = local.get();  
  29.         if (conn != null) {  
  30.             conn.commit();  
  31.             // 清空threadLocal  
  32.             local.remove();  
  33.         }  
  34.     }  
  35.       
  36.     // 关闭连接  
  37.     public static void close() throws SQLException {  
  38.         Connection conn = local.get();  
  39.         if (conn != null) {  
  40.             conn.close();  
  41.             // 清空threadLocal  
  42.             local.remove();  
  43.         }  
  44.     }  
  45.   
  46.     // 获取数据库连接  
  47.     public static Connection getConnection() {  
  48.         return local.get();  
  49.     }  
  50. }  
使用ThreadLocal, 确保相同的线程获取到的是同一个连接.


2.修改业务处理类

[java]  view plain copy 在CODE上查看代码片
  1. /** 
  2.  * 业务逻辑层 
  3.  */  
  4. public class AccountService {  
  5.   
  6.     public void transfer(Account outAccount, Account inAccount, int money) throws SQLException {  
  7.         // 开启 事务   
  8.         TransactionManager.beginTransaction();  
  9.   
  10.         // 查询两个账户  
  11.         AccountDAO accountDAO = new AccountDAO();  
  12.         outAccount = accountDAO.findAccountById(outAccount.getId());  
  13.         inAccount = accountDAO.findAccountById(inAccount.getId());  
  14.   
  15.         // 转账 - 修改原账户金额   
  16.         outAccount.setMoney(outAccount.getMoney() - money);  
  17.         inAccount.setMoney(inAccount.getMoney() + money);  
  18.   
  19.         try {  
  20.             // 更新账户金额  
  21.             accountDAO.update(outAccount);  
  22.             accountDAO.update(inAccount);  
  23.   
  24.             // 转账成功, 提交事务  
  25.             TransactionManager.commit();  
  26.         } catch (Exception e) {  
  27.             // 转账失败, 回滚事务  
  28.             TransactionManager.rollback();  
  29.             e.printStackTrace();  
  30.         } finally {  
  31.             // 关闭连接  
  32.             TransactionManager.close();  
  33.         }  
  34.     }  
  35. }  

使用TransactionManager来管理事务, 代码变得更加简洁.


 3. 修改Dao类

[java]  view plain copy 在CODE上查看代码片
  1. /** 
  2.  * DAO层: CRUD 
  3.  */  
  4. public class AccountDAO {  
  5.     // 查询账户  
  6.     public Account findAccountById(int id) throws SQLException {  
  7.         String sql = "select * from account where id = ?";  
  8.         Object[] params = {id};  
  9.         QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());  
  10.         return queryRunner.query(sql, new BeanHandler<Account>(Account.class), params);  
  11.     }  
  12.       
  13.     // 更新账户  
  14.     public void update(Account account) throws SQLException {  
  15.         String sql = "update account set name = ?, money = ? where id = ?";  
  16.         Object[] params = {account.getName(), account.getMoney(), account.getId()};  
  17.           
  18.         // 从threadLocal中获取连接, 同一个线程拿到的是同一个连接  
  19.         Connection conn = TransactionManager.getConnection();  
  20.         QueryRunner queryRunner = new QueryRunner();  
  21.         queryRunner.update(conn, sql, params);  
  22.     }  
  23. }  

不需要传递Connection, 直接从TransactionManager中获取连接.


三. 总结:

service和dao都是通过TransactionManager来获取Connection, 同一个线程中, 它们在整个事务处理过程中使用了相同的Connection对象, 所以事务会处理成功, dao中没有接受和业务无关的对象, 消除了api污染, 另外使用TransactionManager来管理事务, 使service层的代码变简洁了.

源码下载: http://download.csdn.net/detail/zdp072/7908261

一. 前言:

在上一篇博文中, 我们使用TransactionManager和ThreadLocal完成线程安全的事务管理,不知大家有没有发现,AccountService代码充斥着很多事务处理的代码,其实这些代码在很多方法里面都是重复出现,我们可以使用Template模式进行优化。


二. 实现:

1. 写一个模板类:TransactionTemplate

[java]  view plain copy 在CODE上查看代码片
  1. /** 
  2.  * 模板类 
  3.  */  
  4. public abstract class TransactionTemplate {  
  5.   
  6.     public void doJobInTransaction() throws SQLException {  
  7.         try {  
  8.             // 开启事务  
  9.             TransactionManager.beginTransaction();  
  10.               
  11.             // 执行业务方法  
  12.             this.doJob();  
  13.               
  14.             // 提交事务  
  15.             TransactionManager.commit();  
  16.         } catch (Exception e) {  
  17.             // 出错了, 回滚事务  
  18.             TransactionManager.rollback();  
  19.         } finally {  
  20.             // 关闭连接  
  21.             TransactionManager.close();  
  22.         }  
  23.     }  
  24.   
  25.     protected abstract void doJob() throws Exception; // 具体的业务用户自己实现  
  26. }  
在TransactionTemplate类中定义一个doJobInTransaction方法,在该方法中首先使用TransactionManager开始事务,

然后调用doJob方法完成业务功能,doJob方法为抽象方法,完成业务功能的子类应该实现该方法,
最后,根据doJob方法执行是否成功决定commit事务或是rollback事务。


2. 改造业务处理类AccountService

[java]  view plain copy 在CODE上查看代码片
  1. /** 
  2.  * 业务逻辑层 
  3.  */  
  4. public class AccountService {  
  5.   
  6.     public void transfer(final Account out, final Account in, final int money) throws SQLException {  
  7.           
  8.         new TransactionTemplate() {  
  9.             @Override  
  10.             protected void doJob() throws Exception {  
  11.                 // 查询两个账户  
  12.                 AccountDAO accountDAO = new AccountDAO();  
  13.                 Account outAccount = accountDAO.findAccountById(out.getId());  
  14.                 Account inAccount = accountDAO.findAccountById(in.getId());  
  15.   
  16.                 // 转账 - 修改原账户金额   
  17.                 outAccount.setMoney(outAccount.getMoney() - money);  
  18.                 inAccount.setMoney(inAccount.getMoney() + money);  
  19.                   
  20.                 // 更新账户金额  
  21.                 accountDAO.update(outAccount);  
  22.                 accountDAO.update(inAccount);  
  23.             }  
  24.         }.doJobInTransaction();  
  25.   
  26.     }  
  27. }  

在AccountService的transfer方法中,我们创建了一个匿名的TtransactionTemplate类,并且实现了doJob方法(doJob方法中两次调用DAO完成业务操作),然后调用调用TransactionTemplate的doJobInTransaction方法。doJobInTransaction内部帮我们调用doJob并实现事务控制


参考资料:http://www.davenkin.me/post/2013-02-23/40049802880

源码下载:  http://download.csdn.net/detail/zdp072/7908279

一. 前言:

在上一篇博文中, 我们使用模板模式进行事务管理, 代码看起来已经很简洁了, 但是还是不太完美, 

我们依然需要在service层编写和事务相关的代码, 即我们需要在service层宗声明一个TransactionTemplate.

本篇文章中, 我们将使用Java提供的动态代理来完成事务处理, 你将看到无论在service层还是在dao层都不会

有事务处理代码


二. 例子:

1. 代码结构图:

java事务笔记【1】


2. TransactionProxy

[java]  view plain copy 在CODE上查看代码片
  1. /** 
  2.  * 动态代理 
  3.  */  
  4. public class TransactionProxy {  
  5.   
  6.     public static Object proxyFor(Object object) {  
  7.         return Proxy.newProxyInstance(  
  8.                                         object.getClass().getClassLoader(),   
  9.                                         object.getClass().getInterfaces(),   
  10.                                         new TransactionInvocationHandler(object)  
  11.                                      );  
  12.     }  
  13. }  
  14.   
  15. class TransactionInvocationHandler implements InvocationHandler {  
  16.     private Object proxy;  
  17.   
  18.     TransactionInvocationHandler(Object object) {  
  19.         this.proxy = object;  
  20.     }  
  21.   
  22.     public Object invoke(Object obj, Method method, Object[] objects) throws Throwable {  
  23.         TransactionManager.beginTransaction();  
  24.         Object result = null;  
  25.         try {  
  26.             // 调用业务方法  
  27.             result = method.invoke(proxy, objects);  
  28.             TransactionManager.commit();  
  29.         } catch (Exception e) {  
  30.             TransactionManager.rollback();  
  31.         } finally {  
  32.             TransactionManager.close();  
  33.         }  
  34.         return result;  
  35.     }  
  36. }  
拦截service层的transfer方法, 在调用之前加入事务准备工作, 然后调用原来的transfer方法,

之后根据transfer方法是否执行成功决定commit还是rollback


3. 接口类AccountService

[java]  view plain copy 在CODE上查看代码片
  1. /** 
  2.  * 业务逻辑层接口 
  3.  */  
  4. public interface AccountService{  
  5.     public void transfer(Account outAccount, Account inAccount, int money) throws SQLException;  
  6. }  
使用动态代理, 被代理类和代理类必须要实现相同的接口


4. 业务实现类AccountServiceImpl

[java]  view plain copy 在CODE上查看代码片
  1. /** 
  2.  * 业务逻辑层 
  3.  */  
  4. public class AccountServiceImpl implements AccountService {  
  5.       
  6.     @Override  
  7.     public void transfer(Account outAccount, Account inAccount, int money) throws SQLException {  
  8.         // 查询两个账户  
  9.         AccountDAO accountDAO = new AccountDAO();  
  10.         outAccount = accountDAO.findAccountById(outAccount.getId());  
  11.         inAccount = accountDAO.findAccountById(inAccount.getId());  
  12.   
  13.         // 转账 - 修改原账户金额   
  14.         outAccount.setMoney(outAccount.getMoney() - money);  
  15.         inAccount.setMoney(inAccount.getMoney() + money);  
  16.           
  17.         // 更新账户金额  
  18.         accountDAO.update(outAccount);  
  19.         accountDAO.update(inAccount);  
  20.     }  
  21. }  

5. 测试类:
[java]  view plain copy 在CODE上查看代码片
  1. public class TransferTest {  
  2.       
  3.     @Test  
  4.     public void transferTest() throws SQLException {  
  5.         Account out = new Account();  
  6.         out.setId(1);  
  7.           
  8.         Account in = new Account();  
  9.         in.setId(2);  
  10.           
  11.         AccountService accountService = new AccountServiceImpl();  
  12.           
  13.         // 获取accountService代理  
  14.         AccountService accountServiceProxy = (AccountService) TransactionProxy.proxyFor(accountService);  
  15.         accountServiceProxy.transfer(out, in, 100);  
  16.     }  
  17. }  
调用proxyFor方法, 传入需要被代理的对象, 返回一个代理对象, 代理对象条用transfer方法会被加入事务处理


三. 总结:

通过动态代理, AccountServiceImpl中所有public方法都被代理了, 即它们都被加入事务中, 这对于service层中所有方法都需要和数据库打交道的情况是可以的, 然而对于service层中不需要和数据库打交道的public方法, 这样做虽然不会报错, 但是却显得多余.

参考文章: http://www.davenkin.me/post/2013-02-24/40049235086

源码下载: http://download.csdn.net/detail/zdp072/7908797

你可能感兴趣的:(java事务笔记【1】)