首先
一般程序中对于持久层的操作一般都需要添加事务处理来保证数据一致性。而持久层大面积的业务代码则需要大面积的事务处理。所以Spring对于这些重复的事务代码采用AOP管理,在程序运行时动态的植入到业务代码中。
编程式事务与声明式事务控制
编程式事务控制:
编程式事务控制属于细粒度事务管理,可以指定到方法内部某些行代码。
例如:jdbc对于事务的控制
//获取连接
1.Connection con = DriverManager.getConnection()
//开启事务
2.con.setAutoCommit(true/false);
3.执行CRUD
//提交事务/回滚事务
4. con.commit() / con.rollback();
Hibernate对于事务的控制
sqlSession.beginTransaction()
声明式事务控制:
Spring提供了对于事务的声明式控制,用户想要使用事务只需要在配置文件内部配置即可,关闭事务只需要将实物配置代码注释掉。Spring事务管理核心思想为AOP
声明式事务为粗粒度事务管理,只能作用于方法上不能指定到某些行。
程序对于事务的控制
用户操作---->Action ------>Service ----->Dao
事物的处理一般都是放在Service层的,这是应为Service层的方法里面一般可能存在多个Dao层的调用,必须要采用事务管理来保证程序运行时数据的一致性。
准备:spring-tx-4.0.5.RELEASE.jar,mysql-connector-java-6.0.2.jar,c3p0-0.9.5.3.jar,spring-jdbc-4.0.5.RELEASE.jar
结合AOP为指定关注点添加事务管理
事务使用
XML配置使用
1.配置Spring事务管理器,指定数据源对象
2.配置事务管理增强(为哪些方法?按照什么要的约定来管理事务?)
3.通过Aop配置,来指定事务的切入点
<!--注入c3p0数据源对象 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--配置c3p0连接池 -->
<property name="driverClass" value="com.mysql.jdbc.Driver">
</property>
<!--数据库连接配置 -->
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/entertainment?charset=utf8&serverTimezone=UTC&useSSL=false"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<!--连接池初始化连接数量 -->
<property name="initialPoolSize" value="3"></property>
<!--连接最大数量 -->
<property name="maxPoolSize" value="10"></property>
<!--当连接不足时,创建连接的增量 -->
<property name="acquireIncrement" value="2"></property>
</bean>
<!--Spring整合jdbc的JDBCTemplate对象 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- Spring声明式事务XML配置 -->
<!--配置Spring事务管理器 核心基于AOP -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理增强(对那些方法?按照什么样的约定来管理事务?) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" read-only="false"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--开启AOP注解使用,声明为IOC容器中目标bean设置代理,织入切面 -->
<!--<aop:aspectj-autoproxy poxy-target-class="true"/>时, 表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,
如果目标类没有声明接口,则spring将自动使用CGLib动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
<!--通过AOP来 来指定事务的切入点 -->
<aop:config>
<aop:pointcut expression="execution(public * com.ncs.service.CelebrityService.*(..))" id="pt"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
注解方式
1.事务注解方式必须引入AOP相关的依赖包
2.配置Spring事务管理器,指定数据源
3.声明事务管理
4.在指定方或类上面使用@Transactional注解
<!--注入c3p0数据源对象 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--配置c3p0连接池 -->
<property name="driverClass" value="com.mysql.jdbc.Driver">
</property>
<!--数据库连接配置 -->
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/entertainment?charset=utf8&serverTimezone=UTC&useSSL=false"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<!--连接池初始化连接数量 -->
<property name="initialPoolSize" value="3"></property>
<!--连接最大数量 -->
<property name="maxPoolSize" value="10"></property>
<!--当连接不足时,创建连接的增量 -->
<property name="acquireIncrement" value="2"></property>
</bean>
<!--Spring整合jdbc的JDBCTemplate对象 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--Spring声明式事务 注解方式配置 -->
<!--事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启AOP注解使用 声明为IOC容器中目标bean设置代理,织入切面-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
<!--声明式事务管理 -->
<tx:annotation-driven transaction-manager="txManager"/>
注意:<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用cglib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用cglib动态代理
//在指定方法上添加事务注解
@Transactional(readOnly = false, timeout = -1)
public void view() {
Celebrity celebrity=new Celebrity();
celebrity.setID(9);
celebrity.setName("stephen");
celebrityDaoImpl.saveCelebrity(celebrity);
int i=1/0;
celebrityDaoImpl.getById(1);
}
事务的相关属性
readOnly 只读事务
timeout 超时事务 (当值为-1时,表示不超时,当最终由数据库底层决定)
noRollbackFor 不回滚事务
例如:noRollbackFor=ArithmeticException.class 表示:遇到数学异常事务不回滚
rollbackFor 回滚事务
ioslation 数据库事务的隔离级别
例子:isolation=Isolation.DEFAULT
propagation 事务的传播行为
例子:propagation=Propagation.REQUIRED
1.propagation=Propagation.REQUIRED
1.指定当前的方法必须在事务环境下执行
2.如果当前的方法已经存在事务,就会加入到当前的事务。
例子:
@Transactional(propagation=Propagation.REQUIRED)
public void saveCelebrity(){
}
@Transactional(propagation=Propagation.REQUIRED)
public void getOne(){
saveCelebrity();
}
在getOne方法里面调用了saveCelebrity()方法,由于saveCelebrity已经存在事务管理,而getOne又拥有自己的事务管理
所以saveCelebrity会加入到getOne的事务管理
2.propagation=Propagation.REQUIRES_NEW
1.指定当前的方法必须在事务环境下运行
2.如果当前方法已经存在事务,事务就会挂起,会开启一个新的事务,待到新的事务结束后,挂起的事务才会继续执行。
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void saveCelebrity(){
}
@Transactional(propagation=Propagation.REQUIRED)
public void getOne(){
saveCelebrity();
}
在getOne方法里面调用了saveCelebrity方法,由于由于saveCelebrity已经存在事务管理且为Propagation.REQUIRES_NEW,而getOne又拥有自己的事务管理
所以会先挂起saveCelebrity的事务管理,执行新的事务,待新的事务结束后,挂起的saveCelebrity事务会继续执行。
事务的传播行为
@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化
MYSQL:默认为REPEATABLE_READ级别
oracle,SQLSERVER: 默认为READ_COMMITTED
脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据
注意:
1、@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能. 这是由于动态代理只能作用于public方法
2、用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.
默认遇到运行期异常(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的异常时回滚;
而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked异常不回滚:@Transactional(notRollbackFor=RunTimeException.class)