事务控制的主要任务就是使我们需要做的数据库工作统一起来,满足:原子性,隔离性,永久性。实现一种看似并发实则串行的数据库操作,同时保护数据的合理性变化,避免脏读,不可重复读等现发生。在代码中我们也可以发现,事务控制的底层也是AOP面向切面编程。
事务的隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。
ISOLATION_DEFAULT
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE
事务的传播行为
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
是否只读:建议查询时设置为只读
编程式事务控制三大对象
PlatformTransactionManager
TransactionDefinition
TransactionStatus
基于xml配置文件的声明式事务控制方法
主要配置代码:
<!--目标对象内部的方法就是我们的切点-->
<bean class="com.hlc.service.UserServiceImpl" id="userService">
<property name="userDao" ref="userDao"/>
</bean>
<!-- 配置平台事务管理器,需要注入dataSource-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务的增强,需要引入tx命名空间-->
<tx:advice transaction-manager="transactionManager" id="txAdvice">
<tx:attributes>
<!--目标对象的一个方法(切点)就是一个事务,每个事务都可以单独设置自己的事务属性,isolation:事务的隔离级别,read-only:读锁,propagation:事务的传播行为的设置 -->
<tx:method name="transfer" isolation="REPEATABLE_READ" read-only="false" propagation="REQUIRED"/>
<tx:method name="account" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置aop织入-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.hlc.service.UserServiceImpl.*(..))"/>
</aop:config>
其中,<tx:method> 代表切点方法的事务参数的配置,例如:
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED"timeout="-1" read-only="false"/>
name
:切点方法名称
isolation:
事务的隔离级别
propogation
:事务的传播行为
timeout
:超时时间
read-only
:是否只读
在UserServiceImpl中的事务控制的切点方法代码:
public void transfer(String outMan, String inMan, double money) {
//这里分明是两个方法,所以在没有声明式事务控制之前就算在两个方法代码之间加上一个致命的错误,让in无法执行导致转账转出而转入却没有收到。
//增加了事务控制之后,二者就必须同时结束,如果in无法执行,那么out也无法执行。
userDao.out(outMan,money);
userDao.in(inMan,money);
在同一个方法中调用了两个方法分别是转入与转出,那么如果不设置事务控制,我们可以使转出者转出了,而转入者没有转入,看以下代码:
userDao.out(outMan,money); int i=1/0; userDao.in(inMan,money);
很明显如果不配置事务的控制,转入与转出它们就是两个事务,out事务提交之后去执行int语句发现出现了0异常,所以下一个in事务就无法正常执行,实现了我给你转账,我扣钱你没有收到的bug,所以事务控制就是把这个transfer方法通过aop织入切面的方式变成一个事务,只有其中所有的方法完成之后才能够正常提交。否则就会回滚。
基于注解的声明式事务控制方法
主要配置代码:
<!--组件扫描-->
<context:component-scan base-package="com.itheima"/>
<!--事务的注解驱动-->
<tx:annotation-driven/>
UserDaoImpl代码:
@Repository("userDao")
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<Account> account() {
List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(accountList);
return accountList;
}
@Override
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money=money-? where name=?",money,outMan);
}
@Override
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money=money+? where name=?",money,inMan);
}
}
UserServiceImpl代码:
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public List<Account> account() {
List<Account> accountList = userDao.account();
return accountList;
}
@Override
@Transactional
public void transfer(String outMan, String inMan, double money) {
//这里分明是两个方法,所以在没有声明式事务控制之前就算在两个方法代码之间加上一个致命的错误,让in无法执行导致转账转出而转入却没有收到。
//增加了事务控制之后,二者就必须同时结束,如果in无法执行,那么out也无法执行。
userDao.out(outMan,money);
userDao.in(inMan,money);
}
}
从3月1日开始,到如今的3月20日,在保证日常上课,运动的基础上我们终于结束了spring和springmvc的进度,花了19天,休息了五天,也就是花了14天,每天平均6小时图书馆,习惯养成,做自律的自学菜鸟。下周正式开始mybatis与Boot,加油!xdm。