先来回顾一下事务
事务这个概念一开始是在数据库中被提起的
事务的特性:
ACID
原子性:指事务是一个不可分割的工作单位,事务的操作要么都发生,要么都不发生
一致性:指事务前后数据的完整性必须保证一致
隔离性:指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离
持久性:指一个事务一旦被提交,他对数据库中数据的改变就是永久性的,即时数据库发生故障也不应该对其有任何影响
隔离性的解释有点绕,所以百度下找个容易理解的说法:
比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务A和B,在事务A看来,B要么在A开始之前就已经结束,要么在A结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
所以说事务的隔离很重要,如果没有隔离,会发生什么呢
脏读:一个事务读取了另一个事务改写但是还没有提交的数据,如果这些数据被回滚,则读到的数据是无效的
不可重复读:在同一个事务中,多次读取同一个数据但是返回的结果有所不同
幻读:一个事务读取了几行记录后,另一个事务插入了一些记录,幻读由此产生。然后再次查询,第一个事务就会发现有些原来没有的记录
幻读和不可重复读区别:
不可重复读:例如在读取数据时,别的事务对其所读数据提交的修改,所以在此读取发现不一样
幻读:例如读取时,别的事务又提交了新增数据
如何解决这些问题(隔离机制)
READ_UNCOMMITED(读未提交) 允许读取还未提交的改变了的数据。 会导致:脏读,幻读,不可重复读
READ_COMMITTED:(读已提交) 允许在并发事务已经提交后读取。可防止脏读,但幻读,不可重复读仍可发生
REPEATABLE_READ:(可重复读) 对相同字段多次读取是一样的,除非数据被事务本身改变,可防止脏,不可重复读,但幻读认可发生
SERIALIZABLE:(串行化) 完全服从acid隔离级别,确保不发生 脏 幻 不可重复读,但是效率是最慢的
复习了事务后,开始Spring 事务的学习
先了解Spring为什么要有事务?
因为在不同平台,操作事务的代码各不相同.spring提供了一个接口PlaformTransactionManager
Spring事务操作对象
PlaformTransactionManager声明了事务中有哪些操作,并且针对不同平台给出不同的实现类
例:DataSourceTransactionManager HibernateTransactionManager
注意:在Spring中使用事务管理,最为核心的对象是TransactionManager对象
Spring管理事务的属性
事务隔离级别:
是否只读
事务传播行为(决定业务方法相互调用,事务该如何处理)
PROPAGION_XXX :事务的传播行为
* 保证同一个事务中
PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认)
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
* 保证没有在同一个事务中
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异厂
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
下面放一个案例(后面加事务要用):
模拟转账程序
dao接口
public interface AccountDao {
//加钱
void addMoney ( Integer id , Double money ) ;
//减钱
void decreaseMoney ( Integer id , Double money ) ;
}
dao实现
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao
{
//加钱
public void addMoney(Integer id, Double money)
{
String sql = "update MoneyDemo set money = money + ? where id = ?" ;
super.getJdbcTemplate().update( sql , money , id ) ;
}
//减钱
public void decreaseMoney(Integer id, Double money)
{
String sql = "update MoneyDemo set money = money - ? where id = ?" ;
super.getJdbcTemplate().update( sql , money , id ) ;
}
}
service接口
public interface AccountService {
//转账
void transfer ( Integer from , Integer to , Double money ) ;
}
service接口实现
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao ;
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(Integer from, Integer to, Double money) {
//减钱
accountDao.decreaseMoney(from, money);
//int i = 1 / 0 ;
//加钱
accountDao.addMoney(to, money);
}
}
bean.xml
<?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:p="http://www.springframework.org/schema/p"
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">
<context:component-scan base-package="cn.itcast"></context:component-scan>
<!-- 指定Spring读取DB.properties配置 -->
<context:property-placeholder location="classpath:DB.properties"/>
<!-- 将数据库连接池交由Spring管理 -->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- dao -->
<bean name="accountDao" class="cn.itcast.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean name="accountService" class="cn.itcast.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
什么叫声明式事务管理?
Spring提供了对事务的管理, 这个就叫声明式事务管理。
Spring声明式事务管理,核心实现就是基于Aop。
Spring 管理事务的方式(3种):
编码式
xml配置(aop)
注解(aop)
注意Spring管理事务是用aop来实现的 所以当你的类实现接口的话,接收时也要是用接口类型
xml方式配置事务
<?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:p="http://www.springframework.org/schema/p"
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">
<context:component-scan base-package="cn.itcast"></context:component-scan>
<context:component-scan base-package="com.study.spring"></context:component-scan>
<!-- 指定Spring读取DB.properties配置 -->
<context:property-placeholder location="classpath:DB.properties"/>
<!-- 将数据库连接池交由Spring管理 -->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- dao -->
<bean name="accountDao" class="cn.itcast.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean name="accountService" class="cn.itcast.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 事务核心管理器,封装了所有事务操作,依赖于连接池 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知 -->
<tx:advice transaction-manager="txManager" id="tx">
<tx:attributes>
<!-- 配置针对特定方法应用特定事务属性(*是通配符) isolation 隔离级别 propagation 传播行为 -->
<tx:method name="transfer*" isolation="DEFAULT" propagation="REQUIRED" />
<tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" />
<tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!--
将通知织入 -->
<aop:config>
<!-- 使用切入点表达式来定位切入点 -->
<aop:pointcut expression="execution(* cn.itcast.service.*ServiceImpl.*(..))" id="pt"/>
<!-- 配置切面 ( 切面是由 通知+切入点 构成的 ) -->
<aop:advisor advice-ref="tx" pointcut-ref="pt"/>
</aop:config>
</beans>
测试案例
@RunWith(SpringJUnit4ClassRunner.class)//使用帮我们自动创建容器,就不用自己手动创建Spring容器
@ContextConfiguration("classpath:bean.xml")//指定配置文件路径
public class MoneyDemo {
@Resource(name="accountService")
private AccountService asi ;
@Test
public void fun1 ()
{
asi.transfer(1, 2, (double) 200);
}
}
注解方式
<?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:p="http://www.springframework.org/schema/p"
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">
<context:component-scan base-package="cn.itcast"></context:component-scan>
<context:component-scan base-package="com.study.spring"></context:component-scan>
<!-- 指定Spring读取DB.properties配置 -->
<context:property-placeholder location="classpath:DB.properties"/>
<!-- 事务核心管理器,封装了所有事务操作,依赖于连接池 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 将数据库连接池交由Spring管理 -->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- dao -->
<bean name="accountDao" class="cn.itcast.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean name="accountService" class="cn.itcast.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 注解方式实现事务: 指定注解方式实现事务 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
service
注解定义到方法上: 当前方法应用spring的声明式事务
定义到类上: 当前类的所有的方法都应用Spring声明式事务管理;
定义到父类上: 当执行父类的方法时候应用事务。
@Transactional
public void transfer(Integer from, Integer to, Double money) {
//减钱
accountDao.decreaseMoney(from, money);
// int i = 1 / 0 ;
//加钱
accountDao.addMoney(to, money);
}