Jdbc
J2EE支持JDBC事务、JTA事务和容器事务事务,这里说一下怎样实现JDBC事务。
JDBC事务是由Connection对象所控制的,它提供了两种事务模式:自己主动提交和手动提交,默认是自己主动提交。
自己主动提交就是:在JDBC中。在一个连接对象Connection中。默认把每一个SQL语句的运行都当做是一个事务(即每次运行完SQL语句都会马上将操作更新到数据库)。
手动提交就是:当须要一次性运行多个SQL语句,将多个SQL语句组成一个事务(即要么都成功,要么回滚全部的操作)时,就得手动提交。
看一个样例:
import java.sql.*;
public class TransactionTest{
public static void main(String[] args) throws SQLException {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:orcl","TransactionTest","123456");
//将事务模式设置为手动提交事务:
conn.setAutoCommit(false);
//设置事务的隔离级别。
// 其中,MySQL默认采用REPEATABLE_READ隔离级别;Oracle默认采用READ_COMMITTED隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
// 事务隔离级别:(五种)
// DEFAULT--使用后端数据库默认的隔离级别(Spring中的选择项)
READ_UNCOMMITED--允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
READ_COMMITTED--允许在并发事务已经提交后读取。可防止脏读,但幻读和不可重复读仍可发生
REPEATABLE_READ--对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生
SERIALIZABLE--完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的
//运行插入操作
String sqlInsert = "insert into t_user(userName,userPassword) values(?,?
)";
pstmt = conn.prepareStatement(sqlInsert);
pstmt.setString(1, "danny");
pstmt.setString(2, "123");
pstmt.executeUpdate();
//运行改动操作
String sqlUpdate = "update t_user set userPassword=? where userName = ?";
pstmt = conn.prepareStatement(sqlUpdate);
pstmt.setString(1, "123456");
pstmt.setString(2, "danny");
pstmt.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
// 若事务发生异常,回滚事务
conn.rollback();
}finally{
if (pstmt!=null) {
pstmt.close();
}
if (conn!=null) {
conn.close();
}
}
}
}
当须要插入和更新这两个操作同一时候运行时,要么两个操作都成功,假设有一个失败。则撤销全部的操作。
宏观来看须要例如以下四个步骤:
1、将事务模式设置为手动提交事务:
conn.setAutoCommit(false);
2、设置事务的隔离级别:
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
3、提交事务:
conn.commit();
4、假设发生异常,回滚事务:
conn.rollback();
从上面看,JDBC事务使用较为方便,但因为它是由Connection对象所控制的,所以它的缺点是事务的范围仅仅局限于一个数据库的连接,同一个事务中无法操作多个数据库。
Spring
spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
一、事务的作用
将若干的数据库操作作为一个整体控制,一起成功或一起失败。
原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性:指事务前后数据的完整性必须保持一致。
隔离性:指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。
持久性:指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,即时数据库发生故障也不应该对其有任何影响。
二、Spring事务管理高层抽象主要包括3个接口
--Platform TransactionManager 事务管理器(提交、回滚事务)
Spring为不同的持久化框架提供了不同的Platform TransactionManager接口实现。如:
使用Spring JDBC或iBatis进行持久化数据时使用DataSourceTransactionManager
使用Hibernate3.0版本进行持久化数据时使用HibernateTransactionManager
--TransactionDefinition 事务定义信息(隔离、传播、超时、只读)
脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。
幻读:一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。
事务隔离级别:(五种)
DEFAULT--使用后端数据库默认的隔离级别(Spring中的选择项)
READ_UNCOMMITED--允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
READ_COMMITTED--允许在并发事务已经提交后读取。可防止脏读,但幻读和不可重复读仍可发生
REPEATABLE_READ--对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生
SERIALIZABLE--完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的
其中,MySQL默认采用REPEATABLE_READ隔离级别;Oracle默认采用READ_COMMITTED隔离级别
事务传播行为:(七种)
REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作。拥有多个可以回滚的保存点,内部回滚不会对外部事务产生影响。只对DataSourceTransactionManager有效
--TransactionStatus 事务具体运行状态
三、Spring提供了以下方法控制事务
a.编程式事务管理(基于Java编程控制,很少使用)--见demo1包
利用TransactionTemplate将多个DAO操作封装起来
*b.声明式事务管理(基于Spring的AOP配置控制)
-基于TransactionProxyFactoryBean的方式.(很少使用)--见demo2包
需要为每个进行事务管理的类,配置一个TransactionProxyFactoryBean进行增强.
-基于XML配置(经常使用)--见demo3包
一旦配置好之后,类上不需要添加任何东西。
如果Action作为目标对象切入事务,需要在<aop:config>元素里添加proxy-target-class="true"属性。原因是通知Spring框架采用CGLIB技术生成具有事务管理功能的Action类。
-基于注解(配置简单,经常使用)--见demo4包
在applicationContext.xml中开启事务注解配置。(applicationContext.xml中只需定义Bean并追加以下元素)
<bean id="txManager" class="...">
<property name="sessionFactory">
</property>
<tx:annotation-driven transaction-manager="txManager"/>
在目标组件类中使用@Transactional,该标记可定义在类前或方法前。
四、示例(银行转账)
四、示例(银行转账)
--编程式
[java] view plain copy
1. /**
2. * @Description:转账案例的DAO层接口
3. *
4. */
5. public interface AccountDao {
6. /**
7. * @param out
8. * :转出账号
9. * @param money
10. * :转账金额
11. */
12. public void outMoney(String out, Double money);
13.
14. /**
15. *
16. * @param in
17. * :转入账号
18. * @param money
19. * :转账金额
20. */
21. public void inMoney(String in, Double money);
22. }
[java] view plain copy
1. /**
2. * @Description:转账案例的DAO层实现类
3. */
4. public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
5. /**
6. * @param out
7. * :转出账号
8. * @param money
9. * :转账金额
10. */
11. @Override
12. public void outMoney(String out, Double money) {
13. String sql = "update account set money = money-? where name = ?";
14. this.getJdbcTemplate().update(sql, money, out);
15. }
16. /**
17. * @param in
18. * :转入账号
19. * @param money
20. * :转账金额
21. */
22. @Override
23. public void inMoney(String in, Double money) {
24. String sql = "update account set money = money+? where name = ?";
25. this.getJdbcTemplate().update(sql, money, in);
26. }
27. }
[java] view plain copy
1. /**
2. * @Description:转账案例的业务接口
3. *
4. */
5. public interface AccountService {
6. /**
7. * @param out :转出账号
8. * @param in :转入账号
9. * @param money :转账金额
10. */
11. public void transfer(String out,String in,Double money);
12. }
[java] view plain copy
1. /**
2. * @Description:转账案例的业务层实现类
3. */
4. public class AccountServiceImpl implements AccountService {
5. // 注入转账的DAO
6. private AccountDao accountDao;
7.
8. // 注入事务管理的模板
9. private TransactionTemplate transactionTemplate;
10.
11. /**
12. * @param out
13. * :转出账号
14. * @param in
15. * :转入账号
16. * @param money
17. * :转账金额
18. */
19. @Override
20. public void transfer(final String out, final String in, final Double money) {
21.
22. // 未经事务控制的业务处理操作,如果过程中出异常,则导致前面的操作能完成,后面的不能,即转账成功但未收到转账款
23. // accountDao.outMoney(out, money);
24. // int i = 1/0;
25. // accountDao.inMoney(in, money);
26.
27. transactionTemplate.execute(new TransactionCallbackWithoutResult() {
28.
29. @Override
30. protected void doInTransactionWithoutResult(
31. TransactionStatus transactionStatus) {
32. accountDao.outMoney(out, money);
33. // int i = 1 / 0;//事务控制,即出现异常,该段内代码都执行失效
34. accountDao.inMoney(in, money);
35. }
36. });
37. }
38.
39. public void setAccountDao(AccountDao accountDao) {
40. this.accountDao = accountDao;
41. }
42.
43. public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
44. this.transactionTemplate = transactionTemplate;
45. }
46. }
applicationContext1.xml
[html] view plain copy
1. <!-- 引入外部的属性文件 -->
2. <context:property-placeholder location="classpath:jdbc.properties"/>
3.
4. <!-- 配置c3p0连接池 -->
5. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
6. <property name="driverClass" value="${jdbc.driverClass}" />
7. <property name="jdbcUrl" value="${jdbc.url}" />
8. <property name="user" value="${jdbc.username}" />
9. <property name="password" value="${jdbc.password}" />
10. </bean>
11.
12. <!-- 配置业务层类 -->
13. <bean id="accountService" class="com.zs.spring.demo1.AccountServiceImpl">
14. <property name="accountDao" ref="accountDao" />
15. <!-- 注入事务管理的模板 -->
16. <property name="transactionTemplate" ref="transactionTemplate" />
17. </bean>
18.
19. <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
20. <bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl">
21. <property name="dataSource" ref="dataSource" />
22. </bean>
23.
24. <!-- 配置DAO类(未简化) -->
25. <!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
26. <property name="dataSource" ref="dataSource" />
27. </bean>
28. <bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl">
29. <property name="jdbcTemplate" ref="jdbcTemplate" />
30. </bean> -->
31.
32. <!-- ==================================1.编程式的事务管理=============================================== -->
33. <!-- 配置事务管理器 -->
34. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
35. <property name="dataSource" ref="dataSource" />
36. </bean>
37.
38. <!-- 配置事务管理的模板:Spring为了简化事务管理的代码而提供的类 -->
39. <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
40. <property name="transactionManager" ref="transactionManager"/>
41. </bean>
测试:
[java] view plain copy
1. @RunWith(SpringJUnit4ClassRunner.class)
2. @ContextConfiguration("classpath:applicationContext1.xml")
3. public class TransactionTest {
4. @Resource(name = "accountService")
5. private AccountService accountService;
6.
7. @Test
8. public void demo1() {
9. accountService.transfer("aaa", "bbb", 200d);
10. }
11. }
--基于TransactionProxyFactoryBean的方式
[java] view plain copy
1. public class AccountServiceImpl implements AccountService {
2. // 注入转账的DAO
3. private AccountDao accountDao;
4.
5. /**
6. * @param out
7. * :转出账号
8. * @param in
9. * :转入账号
10. * @param money
11. * :转账金额
12. */
13. @Override
14. public void transfer(String out, String in, Double money) {
15. accountDao.outMoney(out, money);
16. // int i = 1/0;
17. accountDao.inMoney(in, money);
18. }
19.
20. public void setAccountDao(AccountDao accountDao) {
21. this.accountDao = accountDao;
22. }
23. }
applicationContext2.xml
[html] view plain copy
1. <!-- 引入外部的属性文件 -->
2. <context:property-placeholder location="classpath:jdbc.properties"/>
3.
4. <!-- 配置c3p0连接池 -->
5. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
6. <property name="driverClass" value="${jdbc.driverClass}" />
7. <property name="jdbcUrl" value="${jdbc.url}" />
8. <property name="user" value="${jdbc.username}" />
9. <property name="password" value="${jdbc.password}" />
10. </bean>
11.
12. <!-- 配置业务层类 -->
13. <bean id="accountService" class="com.zs.spring.demo2.AccountServiceImpl">
14. <property name="accountDao" ref="accountDao" />
15. </bean>
16.
17. <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
18. <bean id="accountDao" class="com.zs.spring.demo2.AccountDaoImpl">
19. <property name="dataSource" ref="dataSource" />
20. </bean>
21.
22. <!-- ==================================2.使用XML配置声明式的事务管理(原始方式)=============================================== -->
23.
24. <!-- 配置事务管理器 -->
25. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
26. <property name="dataSource" ref="dataSource" />
27. </bean>
28.
29. <!-- 配置业务层的代理 -->
30. <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
31. <!-- 配置目标对象 -->
32. <property name="target" ref="accountService" />
33. <!-- 注入事务管理器 -->
34. <property name="transactionManager" ref="transactionManager"></property>
35. <!-- 注入事务的属性 -->
36. <property name="transactionAttributes">
37. <props>
38. <!--
39. prop的格式:
40. * PROPAGATION :事务的传播行为
41. * ISOTATION :事务的隔离级别
42. * readOnly :只读
43. * -EXCEPTION :发生哪些异常回滚事务
44. * +EXCEPTION :发生哪些异常不回滚事务
45. -->
46. <prop key="transfer">PROPAGATION_REQUIRED</prop>
47. <!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
48. <!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
49. </props>
50. </property>
51. </bean>
测试:
[java] view plain copy
1. @RunWith(SpringJUnit4ClassRunner.class)
2. @ContextConfiguration("classpath:applicationContext2.xml")
3. public class TransactionTest {
4. /**
5. * 一定要注入代理类:因为代理类进行增强的操作
6. */
7. // @Resource(name="accountService")
8. @Resource(name = "accountServiceProxy")
9. private AccountService accountService;
10.
11. @Test
12. public void demo1() {
13. accountService.transfer("aaa", "bbb", 200d);
14. }
15. }
--基于XML配置
[java] view plain copy
1. public class AccountServiceImpl implements AccountService {
2. // 注入转账的DAO
3. private AccountDao accountDao;
4.
5. /**
6. * @param out
7. * :转出账号
8. * @param in
9. * :转入账号
10. * @param money
11. * :转账金额
12. */
13. @Override
14. public void transfer(String out, String in, Double money) {
15. accountDao.outMoney(out, money);
16. // int i = 1/0;
17. accountDao.inMoney(in, money);
18.
19. }
20.
21. public void setAccountDao(AccountDao accountDao) {
22. this.accountDao = accountDao;
23. }
24. }
applicationContext3.xml
[html] view plain copy
1. <!-- 引入外部的属性文件 -->
2. <context:property-placeholder location="classpath:jdbc.properties"/>
3.
4. <!-- 配置c3p0连接池 -->
5. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
6. <property name="driverClass" value="${jdbc.driverClass}" />
7. <property name="jdbcUrl" value="${jdbc.url}" />
8. <property name="user" value="${jdbc.username}" />
9. <property name="password" value="${jdbc.password}" />
10. </bean>
11.
12. <!-- 配置业务层类 -->
13. <bean id="accountService" class="com.zs.spring.demo3.AccountServiceImpl">
14. <property name="accountDao" ref="accountDao" />
15. </bean>
16.
17. <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
18. <bean id="accountDao" class="com.zs.spring.demo3.AccountDaoImpl">
19. <property name="dataSource" ref="dataSource" />
20. </bean>
21.
22. <!-- ==================================3.使用XML配置声明式的事务管理,基于tx/aop=============================================== -->
23.
24. <!-- 配置事务管理器 -->
25. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
26. <property name="dataSource" ref="dataSource" />
27. </bean>
28.
29. <!-- 配置事务的通知 -->
30. <tx:advice id="txAdvice" transaction-manager="transactionManager">
31. <tx:attributes>
32. <!--
33. propagation :事务传播行为
34. isolation :事务的隔离级别
35. read-only :只读
36. rollback-for:发生哪些异常回滚
37. no-rollback-for :发生哪些异常不回滚
38. timeout :过期信息
39. -->
40. <tx:method name="transfer" propagation="REQUIRED"/>
41. </tx:attributes>
42. </tx:advice>
43.
44. <!-- 配置切面 -->
45. <aop:config>
46. <!-- 配置切入点 -->
47. <aop:pointcut expression="execution(* com.zs.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
48. <!-- 配置切面 -->
49. <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
50. </aop:config>
测试:
[java] view plain copy
1. /**
2. * @Description:Spring的声明式事务管理的方式二:基于AspectJ的XML方式的配置
3. */
4. @RunWith(SpringJUnit4ClassRunner.class)
5. @ContextConfiguration("classpath:applicationContext3.xml")
6. public class TransactionTest {
7. /**
8. * 一定要注入代理类:因为代理类进行增强的操作
9. */
10. @Resource(name = "accountService")
11. private AccountService accountService;
12.
13. @Test
14. public void demo1() {
15. accountService.transfer("aaa", "bbb", 200d);
16. }
17. }
--基于注解
[java] view plain copy
1. /**
2. * @Transactional中的的属性 propagation :事务的传播行为 isolation :事务的隔离级别 readOnly :只读
3. * rollbackFor :发生哪些异常回滚 noRollbackFor :发生哪些异常不回滚
4. * rollbackForClassName 根据异常类名回滚
5. */
6. @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
7. public class AccountServiceImpl implements AccountService {
8. // 注入转账的DAO
9. private AccountDao accountDao;
10.
11. /**
12. * @param out
13. * :转出账号
14. * @param in
15. * :转入账号
16. * @param money
17. * :转账金额
18. */
19. @Override
20. public void transfer(String out, String in, Double money) {
21. accountDao.outMoney(out, money);
22. // int i = 1/0;
23. accountDao.inMoney(in, money);
24. }
25.
26. public void setAccountDao(AccountDao accountDao) {
27. this.accountDao = accountDao;
28. }
29. }
applicationContext4.xml
[html] view plain copy
1. <!-- 引入外部的属性文件 -->
2. <context:property-placeholder location="classpath:jdbc.properties"/>
3.
4. <!-- 配置c3p0连接池 -->
5. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
6. <property name="driverClass" value="${jdbc.driverClass}" />
7. <property name="jdbcUrl" value="${jdbc.url}" />
8. <property name="user" value="${jdbc.username}" />
9. <property name="password" value="${jdbc.password}" />
10. </bean>
11.
12. <!-- 配置业务层类 -->
13. <bean id="accountService" class="com.zs.spring.demo4.AccountServiceImpl">
14. <property name="accountDao" ref="accountDao" />
15. </bean>
16.
17. <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
18. <bean id="accountDao" class="com.zs.spring.demo4.AccountDaoImpl">
19. <property name="dataSource" ref="dataSource" />
20. </bean>
21.
22. <!-- ==================================4.使用注解配置声明式事务============================================ -->
23.
24. <!-- 配置事务管理器 -->
25. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
26. <property name="dataSource" ref="dataSource" />
27. </bean>
28.
29. <!-- 开启注解事务 -->
30. <tx:annotation-driven transaction-manager="transactionManager"/>
31.
测试:
[java] view plain copy
1. @RunWith(SpringJUnit4ClassRunner.class)
2. @ContextConfiguration("classpath:applicationContext4.xml")
3. public class TransactionTest {
4.
5. /**
6. * 一定要注入代理类:因为代理类进行增强的操作
7. */
8. @Resource(name = "accountService")
9. private AccountService accountService;
10.
11. @Test
12. public void demo1() {
13. accountService.transfer("aaa", "bbb", 200d);
14. }
15. }