Spring事务管理

事务
什么是事务?事务是指的是逻辑上的一组操作,这次操作要么全部成功,要么全部失败。
事务的特性:
原子性是指事务是一个不可分割的工作单位,事务中的操作,要么全都发生,要么都不发生。
一致性指事务前后数据的完整性必须保持一致。
隔离性指多个用户并发访问数据库时,一个用户的事物不能被其他用户的事物所干扰,多个并发事务之间数据要相互隔离。
持久性是指一个事务一旦被提交,它对数据库中的数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。 
接口介绍
spring事务管理高层抽象主要包括3个接口:
  PlatformTransactionManager 平台事务管理器;
  TransactionDefinition 事务定义信息(隔离、传播、超时、只读)
  TransactionStatus 事务具体运行状态
spring为不同持久化框架提供了不同的PlatformTransactionManager接口实现。
TransactionDefinition定义事务隔离级别,spring默认隔离级别和使用的数据库一样。mysql默认repeatable_read,oracle默认read_commited。
TransactionDefinition定义事务传播行为,事务传播行为解决业务层方法之间的相互调用的问题。事务七种传播行为,分为三类:在同一事务、不在同一事务、事务嵌套(若bbb方法失败,aaa回滚到事务保存点或全回滚)。 
转账环境的搭建
spring支持两种方式事务管理。
1、编程式的事务管理:
在实际应用中很少很用,通过TransactionTemplate手动管理事务。
2、使用XML配置声明式事务:
开发中推荐使用(代码侵入性小),spring的声明式事务是通过AOP实现的。 
创建数据表account
CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `money` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', 'aaa', '1000');
INSERT INTO `account` VALUES ('2', 'bbb', '1000');
INSERT INTO `account` VALUES ('3', 'ccc', '1000');

项目需要jar包、配置文件:

jar包:
com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.apache.commons.logging-1.1.1.jar
com.springsource.org.apache.log4j-1.2.15.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
mysql-connector-java-5.0.8-bin.jar
spring-aop-3.2.0.RELEASE.jar
spring-aspects-3.2.0.RELEASE.jar
spring-beans-3.2.0.RELEASE.jar
spring-context-3.2.0.RELEASE.jar
spring-core-3.2.0.RELEASE.jar
spring-expression-3.2.0.RELEASE.jar
spring-jdbc-3.2.0.RELEASE.jar
spring-test-3.2.0.RELEASE.jar
spring-tx-3.2.0.RELEASE.jar
核心配置文件:applicationContext.xml, log4j.properties, jdbc.properties
jdbc.properties:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\://192.168.216.101\:3306/spring_transaction
jdbc.username=user
jdbc.password=123456
log4j.properties:
log4j.rootLogger=INFO,logfile,stdout           
#log4j.logger.org.springframework.web.servlet=INFO,db 
#log4j.logger.org.springframework.beans.factory.xml=INFO
#log4j.logger.com.neam.stum.user=INFO,db 
#log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} %p [%c] %X{remoteAddr}  %X{remotePort}  %X{remoteHost}  %X{remoteUser} operator\:[\u59D3\u540D\:%X{userName} \u5DE5\u53F7\:%X{userId}] message\:<%m>%n
#write log into file
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.Threshold=warn
log4j.appender.logfile.File=${webapp.root}\\logs\\Spring-transaction.log
log4j.appender.logfile.DatePattern=.yyyy-MM-dd
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=[SpringTransaction] %d{yyyy-MM-dd HH\:mm\:ss} %X{remoteAddr} %X{remotePort} %m %n
#display in console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Threshold=info
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[SpringTransaction] %d{yyyy-MM-dd HH\:mm\:ss} %X{remoteAddr} %X{remotePort} %m %n 
建maven项目。
建包cn.it.spring.demo1,下面的文件除了配置文件全建在这个包里。
面向接口开发,所以创建接口:AccountService。
/**
* 转账安全的业务层接口
*/
public interface AccountService {
/**
* @param out :转出账号
* @param in :转入账号
* @param money :转账金额
*/
public void transfer(String out,String in,Double money);
}

实现类AccountServiceImpl.java:

//注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。
private AccountDao accountDao;
/**
* @param out :转出账号
* @param in :转入账号
* @param money :转账金额
*/
@Override
public void transfer(String out, String in, final Double money) {
  accountDao.outMoney(out, money);
  //int i = 1/0;
  accountDao.inMoney(in, money);
}
public void setAccountDao(AccountDao accountDao) {
  this.accountDao = accountDao;
}

AccountDao.java:

public interface AccountDao {
/**
* @param out :转出账号
* @param money :转账金额
*/
public void outMoney(String out,Double money);
/**
*
* @param in :转入账号
* @param money :转账金额
*/
public void inMoney(String in,Double money);
}

实现类AccountDaoImpl.java:

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {//继承JdbcDaoSupport就可以使用jdbc的模版了。继承了之后就可以查看相应的源代码。找到spring-jdbc-3.2.0.RELEASE.sources.jar(在spring-framework-3.2.0.RELEASE官方JAR包),里面可以看到提供了一个jdbc模板,下面有setJdbcTemplate,所在直接向dao注入模板就可以了。还有一种办法,里面有setDataSource设置了连接池,所以只要给dao连接池的话,它就会创建一个模板。所以可以直接向dao里注入连接池。因为只要给连接池,它就可以帮我们创建jdbc的模板。下面的配置有向dao注入连接池,只要注入连接池就有jdbc模板。
/**
* @param out :转出账号
* @param money :转账金额
*/
@Override
public void outMoney(String out, Double money) {
  String sql = "update account set money = money-? where name = ?";
  this.getJdbcTemplate().update(sql, money, out);    //使用这个模板。后面两个是可变参数。
}
/**
* @param in :转入账号
* @param money :转账金额
*/
@Override
public void inMoney(String in, Double money) {
  String sql = "update account set money = money+? where name = ?";
  this.getJdbcTemplate().update(sql,money,in);
}
}

上面这些类要用spring管理,所以要配置:

applicationContext:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xmlns:task="http://www.springframework.org/schema/task"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-3.1.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
         http://www.springframework.org/schema/tx
         http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
         http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd">
</beans>

下面的代码写在beans里。

<!-- 引入外部的属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置业务层类 -->
<bean id="accountService" class="com.zs.spring.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
<bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource" />    //在dao里注入连接池。
</bean>

测试类 TransactionTest :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext1.xml")
public class TransactionTest {
  @Resource(name="accountService")//name就是applicationContext里配置的名字。
  private AccountService accountService;
  @Test
  public void demo1(){
    accountService.transfer("aaa", "bbb", 200d);
  }
}

这时环境就搭好了,进行测试查看数据库,然后把AccountServiceImpl两句转账语句间加int i = 8/0进行测试。发现有事务问题。 

编程式事务管理
spring为了简化编写代码,它给我们提供了事务管理的模板,叫TransactionTemplate,需要在哪个地方使用事务,就在哪个地方注入这个模板。
打开spring的API,找到PlatformTransactionManager接口,实现类有很多,比如HibernateTransactionManager, DataSourceTransactionManager等,这里使用第二个,首先配置事务管理器,在applicationContext.xml里配置:
<!-- ==================================1.编程式的事务管理。因使用事务的时候要自己手动写代码。哪个类需要事务管理呢,业务层的类,所以在业务层的类中进行模板的注入。=============================================== -->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 使用jdbc的方式进行事务管理,所以bean里需要配置属性。因为jdbc进行事务管理是获得到连接对象connection.setAutoCommit(false);就不自动提交了,执行完代码的commit,如果发生异常就rollback。谁能获取到连接呢,事务连接池,所以在事务管理器的模板中注入连接池对象,因为事务管理模板是真正进行事务管理的类,连接池可以获得到连接对象: -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 但是直接进行事务管理的话代码比较繁琐,所以spring为了简化我们事务管理的代码,它给我们提供了事务管理的模板,叫:TransactionTemplate所以定义一个事务管理模板: -->
<!-- 配置事务管理的模板:Spring为了简化事务管理的代码而提供的类。就像它还给我们提供过jdbc的模板。 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<!-- 这个模板是简化事务管理的,真正进行事务管理的,是transactionManager,所以注入: -->
<property name="transactionManager" ref="transactionManager" />
</bean>

业务类AccountServiceImpl中注入,如果要注入的话,还有提供set方法:

//注入事务管理的模板
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
  this.transactionTemplate = transactionTemplate;
}

这里提供set方法了,配置文件中需要注入事务管理的模板,哪个类需要事务管理,就在哪个类中注入模板类:

<bean id="accountService" class="cn.it.spring.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
<!-- 注入事务管理的模板 -->
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>

AccountServiceImpl中的那两条转账语句就在一个事务里的,使用模板中的方法:

public void transfer(final String out, final String in, final Double money) {
// accountDao.outMoney(out, money);
// int i = 1/0;
// accountDao.inMoney(in, money);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {//调用这个方法时看到它的参数要传一个TransactionCallback的接口,可以自己写一个类实现它,也可以直接使用匿名内部类的形式,new完之后里面有个方法doInTransactionWithoutResult。
  @Override
  protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {//方法里面要传一个对象,就是事务的状态,匿名内部类中使用了外部类中的变量,所以要把外部变量变成final
    accountDao.outMoney(out, money);
    int i = 1/0;
    accountDao.inMoney(in, money);
  }
});
}

测试,发现事务回滚成功。 

声明式事务管理方式一:基于TransactionProxyFactoryBean的方式

编程式事务管理需要手动改service代码,开发中不好用。
 
声明式事务管理基于AOP的思想完成的,也就是在转账的两句代码之前做一些事,之后做一些事。这就是AOP思想。
建包cn.it.spring.demo2,把上面的代码复制并恢复。
新建配置文件applicationContext2.xml。配置也还原。
因为基于aop,所以这里要引入aop包(和spring整合)和aop联盟(aop是一个联盟提出的)包:com.springsource.org.aopalliance-1.0.0.jar, spring-aop-3.2.0.RELEASE.jar
applicationContext2.xml:
<!-- 配置业务层类 -->
<bean id="accountService" class="cn.it.spring.demo2.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
<bean id="accountDao" class="cn.it.spring.demo2.AccountDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- ==================================2.使用XML配置声明式的事务管理(原始方式)=============================================== -->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置业务层的代理 -->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目标对象 -->
<property name="target" ref="accountService" />
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager"></property>
<!-- 注入事务的属性 -->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事务的传播行为
* ISOTATION :事务的隔离级别
* readOnly :只读
* -EXCEPTION :发生哪些异常回滚事务
* +EXCEPTION :发生哪些异常不回滚事务
-->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
<!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
<!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
</props>
</property>
</bean>

 TransactionTest:

// @Resource(name = "accountService") // name就是applicationContext里配置的名字。
@Resource(name = "accountServiceProxy")//这里要用代理。
private AccountService accountService;
@Test
public void demo1() {
accountService.transfer("aaa", "bbb", 200d);
}

 AccountServiceImpl:

// 注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。
private AccountDao accountDao;
public void transfer(final String out, final String in, final Double money) {
accountDao.outMoney(out, money);
int i = 1/0;
accountDao.inMoney(in, money);
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
  声明式事务管理方式二:基于AspectJ的XML方式

上面的原始方式不经常使用,因为需要为每一个进行事务管理的类都需要配置一个transactionProxyFactoryBean。只能对一个目标进行增强。

所以要用另一种AspectJ的XML方式:
aspectJ是为是简化AOP编程,开源的第三方AOP开发框架。
aspectJ相应开发包:com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar(aspectJ包),spring-aspects-3.2.0.RELEASE.jar(spring整合aspectJ的包)。
建包cn.it.spring.demo3
复制applicationContext3.xml:
<!-- 配置业务层类 -->
<bean id="accountService" class="cn.it.spring.demo3.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
<bean id="accountDao" class="cn.it.spring.demo3.AccountDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- ==================================3.使用XML配置声明式的事务管理,基于tx/aop=============================================== -->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务的通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation :事务传播行为
isolation :事务的隔离级别
read-only :只读
rollback-for:发生哪些异常回滚
no-rollback-for :发生哪些异常不回滚
timeout :过期信息
-->
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice> 
<!-- 配置切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* cn.it.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>

不管用哪种方式,事务管理器是必须的。

这种代理方式是自动代理,就是在类的生成过程中,类的本身就是一个代理对象。而不需要注入代理对象。因为在产生的过程中就已经对这个类增强了。
 TransactionTest:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class TransactionTest {
  @Resource(name = "accountService") // name就是applicationContext里配置的名字。
  private AccountService accountService;
  @Test
  public void demo1() {
    accountService.transfer("aaa", "bbb", 200d);
  }
}
  声明式事务管理方式三:基于注解的方式
建包cn.it.spring.demo4
复制applicationContext4.xml:
也要配事务管理器,因为它才是真正事务管理的类。里面注入连接池。开启注解事务后就能使用注解了。注解驱动标签,标签里配置事务管理器对象。然后在类上添加注解。注解里也有属性,像隔离级别,传播行为,只读,等等。
<!-- 配置业务层类 -->
<bean id="accountService" class="cn.it.spring.demo4.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
<bean id="accountDao" class="cn.it.spring.demo4.AccountDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- ==================================4.使用注解配置声明式事务============================================ -->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

 AccountServiceImpl

/**
*@Transactional中的的属性
*propagation :事务的传播行为
*isolation :事务的隔离级别
*readOnly :只读
*rollbackFor :发生哪些异常回滚
*noRollbackFor :发生哪些异常不回滚
*rollbackForClassName 根据异常类名回滚
*/
//@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
@Transactional
public class AccountServiceImpl implements AccountService {
  // 注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。
  private AccountDao accountDao;
  /**
  * @param out
  *            :转出账号
  * @param in
  *            :转入账号
  * @param money
  *            :转账金额
  */
  public void transfer(final String out, final String in, final Double money) {
    accountDao.outMoney(out, money);
    // int i = 1/0;
    accountDao.inMoney(in, money);
  }
  public void setAccountDao(AccountDao accountDao) {
    this.accountDao = accountDao;
  }
}

 总结 

Spring将事务管理分成了两类:
  * 编程式事务管理
    * 手动编写代码进行事务管理.(很少使用)
  * 声明式事务管理:
    * 基于TransactionProxyFactoryBean的方式.(很少使用)
      * 需要为每个进行事务管理的类,配置一个TransactionProxyFactoryBean进行增强.
    * 基于AspectJ的XML方式.(经常使用)
      * 一旦配置好之后,类上不需要添加任何东西
    * 基于注解方式.(经常使用)
      * 配置简单,需要在业务层上添加一个@Transactional的注解.
 

转载于:https://www.cnblogs.com/yigechengxuyuan/p/8397008.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值