第十章 Spring对事务的支持

Spring对事务的支持
分两种方式: 注释语法 与 XML的配置

 注释方式
首先在头文件中加入事务的支持
<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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
</beans>
和AOP一样,注意红色部分

接着我们需要配置一个事务管理器对象(Spring提供了), 因为事务是需要使用到Connection对象的,所以需要告诉事务管理器对象Connection对象从哪里来(从我们配置的数据源中获取)
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
然后我们就可以使用Spring提供的事务功能了.
<tx:annotation-driven transaction-manager="txManager" />
这一句就是开启事务注释的功能,如果事务采用XML配置的方式的话,这一句是可以不要的.

使用示例,我们可以在Service实现类中加入事务的支持(@Transactional),如下:
@Transactional
public class UserServiceImpl implements UserService {
private UserDao userDao;

public void batchCreateUser(String[] names) throws Exception {
for(String name : names){
userDao.create(new User(name));
}
throw new Exception();
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
那么这个时候,Spring会自动的在我们服务层的方法开始时加入事务的begin,结束时加入事务的commit,异常时加入事务的rollback,

执行Test:
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService)ctx.getBean("userService");
try {
userService.batchCreateUser(new String[]{"李四9","李四10"});
} catch (Exception e) {
e.printStackTrace();
}
}
}
请观察结果,我们发现事务好像没有起作用,虽然发生了异常,但是记住还是加入到数据库中去了.

接下来,我们修改服务层实现类的代码,让其抛出一个RuntimeException时异常, 如下:
@Transactional
public class UserServiceImpl implements UserService {
private UserDao userDao;

public void batchCreateUser(String[] names) throws Exception {
for(String name : names){
userDao.create(new User(name));
}
throw new RuntimeException();
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
再次执行Test文件,这时,我们发现事务起作用了,数据并没有插入到数据库中,这时说明一个问题,Spring的事务管理器针对异常类型的不同,处理方式有些不一样.
运行时异常(可以不捕获): 回滚
非运行时异常(必须捕获): 不回滚
以上是默认行为,但是我们可以通过配置来更改这种行为了,我们可以让其 运行时异常不回滚或让非运行时异常回滚.

还有需要注意的地方就是: 事务的注释加入的位置
如果加在类头上
@Transactional
public class UserServiceImpl implements UserService {
}
这时事务会对类中所有的方法都起作用.

如果加在方法头上,那么此时,事务只会对指定方法起作用,如:
public class UserServiceImpl implements UserService {
private UserDao userDao;

@Transactional
public void batchCreateUser(String[] names) throws Exception {
for(String name : names){
userDao.create(new User(name));
}
throw new RuntimeException();
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

接下来,我们看一下,怎么更改事务对抛出不同类型异常的默认行为
让非运行时异常也回滚
public class UserServiceImpl implements UserService {
private UserDao userDao;

@Transactional(rollbackFor=Exception.class)
public void batchCreateUser(String[] names) throws Exception {
for(String name : names){
userDao.create(new User(name));
}
throw new Exception();
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

让运行时异常不回滚:
public class UserServiceImpl implements UserService {
private UserDao userDao;

@Transactional(noRollbackFor=RuntimeException.class)
public void batchCreateUser(String[] names) throws Exception {
for(String name : names){
userDao.create(new User(name));
}
throw new RuntimeException();
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
通常的做法是: @Transactional(rollbackFor=Exception.class) 让非运行时异常回滚,那么这时我们就可以保证只要发生异常就会回滚事务了.

事务的传播属性
JDBC模式下通常的查询方法,因为并没有涉及到数据库的更新操作,所以一般是不需要加入事务的,如下配置:
@Transactional(propagation=Propagation.NOT_SUPPORTED)
public List<User> findUserAll() throws Exception {
return userDao.findUserAll();
}
告诉spring事务管理器, 此方法不需要加入事务

事务的只读属性
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
public List<User> findUserAll() throws Exception {
return userDao.findUserAll();
}
对于查询数据库的操作,是没有更新操作的,那么我们可以在查询的方法上加上readOnly=true,指明方法不会执行数据库的更新操作,从而提高性能.如果方法上设置了readOnly=true,然而又执行了更新操作,Spring就会报错的.

 XML配置事务
首先删除掉Service类中的注释的地方,保证有一个干净的Service实现类
public class UserServiceImpl implements UserService {
private UserDao userDao;

public void batchCreateUser(String[] names) throws Exception {
for(String name : names){
userDao.create(new User(name));
}
throw new Exception();
}

public List<User> findUserAll() throws Exception {
return userDao.findUserAll();
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
XML配置:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="find*" read-only="true" />
<tx:method name="*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut"
expression="execution(* com.wdpc.springjdbc.service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>
定义一个事务通知(和AOP的通知有点相似): <tx:advice id="txAdvice" transaction-manager="txManager">
在通知中指明所有find开头的方法,不会涉及到数据库的更新操作,声明事务为只读属性,并不开启事务,注意的地方(propagation="NOT_SUPPORTED" read-only="true")不能同时配置在一起
<tx:method name="find*" read-only="true"/>

在通知中指明所有的方法,只要发生异常就回滚: <tx:method name="*" rollback-for="Exception"/>

利用AOP的特点来进行事务的拦截
定义一个切入点: <aop:pointcut id="txPointcut"
expression="execution(* com.wdpc.springjdbc.service..*.*(..))" />
绑定切入点和事务通知: <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />

这个时候只要匹配了切入点中的方法,就会进行事务的拦截.

再次执行Test:
batchCreateUser方法抛出了Exception非运行时异常,按默认值配置来说,数据应该会进入数据库,但是我们在配置文件中指明了rollback-for="Exception",只要发生异常就回滚,可以观察一下数据进入数据库没有.

记住,事务一般在服务层进行拦截.

 事务的传播属性的研究
Propagation类规定了事务的传播属性
Propagation.REQUIRED: 开始一个新事务(也是默认值,不需要我们配置的),即如果已经存在事务,则加入到事务中,如果没有事务,则为自己新开一个事务.
Propagation.NOT_SUPPORTED: 声明方法不需要事务
Propagation.REQUIRES_NEW:不管事务是否存在,总会开启一个新的事务,如果方法在调用之前已经开启了一个事务,那么这个时候会出现事务嵌套.
Propagation.MANDATORY: 指定方法的调用必须在一个已经打开的事务中调用.方法没有自己的事务,如果方法的调用在没有事务的环境下调用,这时会抛出异常.
Propagation.NEVER:它和Propagation.MANDATORY相返,方法的调用绝对不能在事务的范围内执行.
Propagation.SUPPORTS: 随意型, 如果在事务范围内被调用,则会纳入事务的管理, 如果在没有事务管理范围内调用,也可以调用,但是就没有事务的特性了.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值