1、基本概念
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
2、spring中配置c3p0连接池
(1)mysql
<!-- 配置c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///mydb_329"/>
<property name="user" value="root"/>
<property name="password" value=""/>
</bean>
(2)oracle
<!--c3p0连接池配置--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/> <property name="jdbcUrl" value="jdbc:oracle:thin:@127.0.0.1:1521:orclht"/> <property name="user" value="hscon"/> <property name="password" value="hscon"/> </bean>
3、spring核心配置文件中完成注入操作
<!--创建jdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="bankDao" class="com.spring.dao.BankDao"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <bean id="bankService" class="com.spring.service.BankServcie"> <property name="bankDao" ref="bankDao"></property> </bean>
4、编写代码模拟转账交易
Dao
public class BankDao { JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void addBankCount(long id,double money){ String sql="UPDATE hscon.USER_BALANCE SET balance = balance + ? WHERE id = ?"; jdbcTemplate.update(sql,money,id); } public void deductBankCount(long id,double money) throws Exception{ String sql="UPDATE hscon.USER_BALANCE SET balance = balance - ? WHERE id = ?"; jdbcTemplate.update(sql,money,id); } }
Servcice
public class BankServcie { private BankDao bankDao; public void setBankDao(BankDao bankDao) { this.bankDao = bankDao; } //银行转账为例解释事物 public void transfer(long fromID,long toID,double money) throws Exception{ bankDao.addBankCount(toID,money); bankDao.deductBankCount(fromID,money); } }
测试代码
public class TestDemo { //测试的方法 public static void main(String args[]){ ApplicationContext ap= new ClassPathXmlApplicationContext("applicationContext.xml"); BankServcie bankService = (BankServcie) ap.getBean("bankService"); try{ bankService.transfer(1,2,10); }catch (Exception e){ e.printStackTrace(); } } }
目录结构
5、spring声明式事务处理
(1)实现Spring的事务管理,需要使用到事务管理器(PlatformTransactionManager)。Spring为不同的持久化框架提供了不同的PlatformTransactionManager接口实现。如下:
事务 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用Spring JDBC或iBatis进行持久化数据时使用 |
org.springframework.orm.hibernate5.HibernateTransactionManager | 使用Hibernate5.0版本进行持久化数据时使用 |
org.springframework.orm.jpa.JpaTransactionManager | 使用JPA进行持久化时使用 |
org.springframework.jdo.JdoTransactionManager | 使用JDO进行持久化时使用 |
org.springframework.transaction.jta.JtaTransactionManager | 使用一个JTA实现来管理事务,在一个事务跨多个资源时必须使用 |
注:在本案例中采用的Spring JDBC操作数据库,即使用DataSourceTransactionManager
这一事务管理器实现类
(2)配置xml
在项目的Spring核心配置文件中进行配置,如下:
a、配置事务管理器,即配置DataSourceTransactionManager
,设置数据源;
b、配置事务增强;
c、配置切面,定义切入点,应用事务增强。
具体如下:
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 设置进行事务操作的方法匹配规则 -->
<!-- name: Service层中需要事务增强的方法名,其中这里表示名称以transfer开头的所有方法 -->
<!-- propagation="REQUIRED"代表支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择 -->
<tx:method name="transfer*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="bankPointcut" expression="execution(* com.wm103.tx.BankService.transfer*(..))"/>
<!-- 切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="bankPointcut"/>
</aop:config>
(3)事务配置说明
tx:method
的属性如下:
a. name
,该属性必须设置值,其值表示与事务关联的方法(名)。该属性值可以使用通配符*
,用来指定一批关联到相同的事务属性的方法,如get*
、insert*
等。应用事务的方法需要存在满足该属性值的tx:method
(事务属性配置),同时在定义切入点后该方法“受到”事务的增强,才能真正的实现事务;
b. propagation
,默认值为REQUIRED
,事务的传播行为(所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。),包括:REQUIRED
,REQUIRES_NEW
,SUPPORTS
,NOT_SUPPORTED
,NEVER
,MANDATORY
,NESTED
。这些属性值在TransactionDefinition中也对应定义了如下几个表示传播行为的常量:
常量名 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED |
c. isolation
,默认值为DEFAULT
,表示事务隔离级别(事务隔离级别指若干个并发的事务之间的隔离程度),该属性值默认为对应数据库设置的隔离级别。具体如下:
隔离级别 | 含义 |
---|---|
DEFAULT | 使用后端数据库默认的隔离级别(Spring中的选择项),对大部分数据库而言,通常这值就是READ_COMMITTED ;但是MySQL的默认隔离级别是REPEATABLE_READ |
READ_UNCOMMITTED | 一个事务可以读取另一个事务修改但还没有提交的数据,可能导致脏读、幻读、不可重复读,因此很少使用该隔离级别 |
READ_COMMITTED | 一个事务只能读取另一个事务已经提交的数据,可防止脏读,但幻读和不可重复读仍可发生,这也是大多数情况下的推荐值 |
REPEATABLE_READ | 一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同,除非数据被事务本身改变。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。可防止脏读、不可重复读,但幻读仍可能发生 |
SERIALIZABLE | 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,完全服从ACID的隔离级别,确保不发生脏读、幻读、不可重复读。这在所有的隔离级别中是最慢的,它是典型地通过完全锁定在事务中涉及的数据表来完成的,因此通常情况下也不会用到该级别 |
d. timeout
,默认值为-1(表示永不超时),事务超时的时间,单位为秒;
e. read-only
,默认值为false,即事务不是只读的,该属性表示事务是否只读。对于该属性值,针对事务内均为查询操作时才可以设置为true,对于增加删除修改的操作则不允许read-only设置为true。至于该字段的含义或者存在的意义,可以参考如下资料的说明:
百度知道 标题:spring事务管理属性为只读是什么意思?
您好
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持read-only=”true”表示该事务为只读事务,比如上面说的多条查询的这种情况可以使用只读事务,
由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,例如Oracle对于只读事务,不启动回滚段,不记录回滚log。
(1)在JDBC中,指定只读事务的办法为: connection.setReadOnly(true);
(2)在Hibernate中,指定只读事务的办法为: session.setFlushMode(FlushMode.NEVER);
此时,Hibernate也会为只读事务提供Session方面的一些优化手段;
(3)在Spring的Hibernate封装中,指定只读事务的办法为: bean配置文件中,prop属性增加“read-Only”或者用注解方式@Transactional(readOnly=true)。Spring中设置只读事务是利用上面两种方式(根据实际情况)。
在将事务设置成只读后,相当于将数据库设置成只读数据库,此时若要进行写的操作,会出现错误。
希望对你有帮助。
我们可以使用上述案例,在配置文件中进行如下修改(即加入read-only属性的设置,并设置true):
<tx:method name="transfer*" propagation="REQUIRED" read-only="true"/>
原因即设置read-only="true"
后,我们在事务中进行了写操作,结果抛出异常。
f. rollback-for
,可不设置,表示被触发进行回滚的异常,设置多个异常类则以逗号分隔,如:com.wm103.MyException,ServletException
;
g. no-rollback-for
,可不设置,表示不被触发进行回滚的异常,设置多个异常类则以逗号分隔,如:com.wm103.MyException,ServletException
。
涉及到这两个属性值,那么这里要对Spring声明式事务管理的默认回滚规则加以说明。在Spring事务管理中默认的回滚规则为:如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常(必检异常),则仍然提交事务。
为了验证上述的结论,我们将对上述案例进行修改,修改Service类中的transfer1方法,让其抛出一个必检异常,这里我抛出一个IOException,如下:
(实际上,在上述案例中模拟转账过程中出现的异常int err = 10 / 0
即为运行异常,测试后,我们可以发现确实事务发生了回滚。)
修改后,再运行测试类的测试方法,我们可以发现用户A扣钱后,发生异常,到事务并未回滚。为使得发生这样的异常时也触发回滚操作,需要设置rollback-for
属性,如下:
<tx:method name="transfer*" propagation="REQUIRED" rollback-for="Exception"/>
相反地,如果想让发生运行时异常时不触发回滚操作,那可以设置属性no-rollback-for="RuntimeException"
。
(4)完整的spring核心配置文件
<!--c3p0连接池配置--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/> <property name="jdbcUrl" value="jdbc:oracle:thin:@127.0.0.1:1521:orclht"/> <property name="user" value="hscon"/> <property name="password" value="hscon"/> </bean> <!--创建jdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="bankDao" class="com.spring.dao.BankDao"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <bean id="bankService" class="com.spring.service.BankServcie"> <property name="bankDao" ref="bankDao"></property> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务增强 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 设置进行事务操作的方法匹配规则 --> <!-- name: Service层中需要事务增强的方法名,其中这里表示名称以transfer开头的所有方法 --> <!-- propagation="REQUIRED"代表支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择 --> <tx:method name="transfer*" propagation="REQUIRED" no-rollback-for="Exception"/> </tx:attributes> </tx:advice> <!-- 配置切面 --> <aop:config> <!-- 切入点 --> <aop:pointcut id="bankPointcut" expression="execution(* com.spring.service.*.transfer*(..))"/> <!-- 切面 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="bankPointcut"/> </aop:config>
6、spring注解方式实现事务的管理
完整配置文件
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--c3p0连接池配置--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/> <property name="jdbcUrl" value="jdbc:oracle:thin:@127.0.0.1:1521:orclht"/> <property name="user" value="hscon"/> <property name="password" value="hscon"/> </bean> <!--创建jdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="bankDao" class="com.spring.dao.BankDao"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <bean id="bankService" class="com.spring.service.BankServcie"> <property name="bankDao" ref="bankDao"></property> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置开启事务注解 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
在类或者方法上应用@Transactional
注解,如:
import org.springframework.transaction.annotation.Transactional;
//@Transactional
@Transactional(rollbackFor = Exception.class)
public class BankService {
- 1
- 2
- 3
- 4
- 5
或者
@Transactional(rollbackFor = Exception.class)
public void transfer1(final long formId, final long toId, double amount) throws IOException {
因此为修改Spring默认事务的回滚规则,可以设置注解@Transactional
的rollbackFor
为Exception.class
,即@Transactional(rollbackFor = Exception.class)
。同样地,如果不希望运行时异常触发回滚操作,可以设置@Transactional(noRollbackFor = RuntimeException.class)
。
@Transactional注意事项
摘录自“Transactional注解中常用参数说明”一文,链接在“知识点扩展或参考”这一部分
1. @Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
2. @Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 元素的出现 开启 了事务行为。
3. Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。
遇到问题
warning no match for this type name: com.spring.service [Xlint:invalidAbsoluteTypeName]
配置事务时,一定注意expression="execution(* com.zrm.service.*(..))" 应该为
expression="execution(* com.zrm.service.*.*(..))" ,这样,切点才定位到方法上了