Spring框架(JavaEE开发进阶Ⅲ)—声明式事务

一、主要内容

1、定义事务属性
2、在XML中定义事务
3、定义注解驱动的事务

二、导言

1、Spring为POJO提供了声明式事务的支持
2、Spring对声明式事务的支持是通过Spring AOP框架来实现的
3、Spring提供了3种方式来声明事务边界
1)以前Spring只能使用Spring AOP和TransactionProxyFactoryBean的代理Bean来实现声明式事务(现在实际已经被淘汰了)
2)从Spring2以后,更好的方式是使用Spring的tx命名空间和@Transactional注解

三、定义事务属性

1、在Spring中,声明式事务是通过事务属性来定义的,事务属性包括5个方面,描述了事务策略如何应用到方法上

Isolation:事务的隔离性
Rollback rules:事务的回滚规则
Timeout:超时时间
Read-only:是不是只读
Propagation:传播性
2、传播行为(propagation behavior)定义了客户端与被调用方法之间的事务边界
3、传播规则回答这样的问题:新的事务应该被启动还是被挂起,或者方法是否要在事务环境中执行

PROPAGATION_MANDATORY:表示该方法必须在事务中运行,如果事务不存在的话会抛出一个异常
PROPAGATION_NESTED:表示如果当前已经存在事务的话,那么该方法将在嵌套事务中运行。嵌套事务可以独立于当前事务,进行单独的提交或者回滚。如果当前事务不存在,那么它的行为就和REQUIRED一样
PROPAGATION_REQUIRED:表示当前方法必须定义在事务中,如果事务存在就在该事务中运行,否则的话会启动一个新事务(最常用)
PROPAGATION_NEVER:表示当前方法不应该运行在事务上下文里面,如果当前有一个事务在运行的话会抛异常
PROPAGATION_NOT_SUPPORTED:表示当前方法不应该定义在事务中,如果当前存在一个事务,那么这个方法在运行期间,事务会被挂起
PROPAGATION_REQUIRES_NEW:当前方法必须运行在它自己的事务里面。如果程序中原来有事务,这个事务会被挂起,一个新的事务会被启动
PROPAGATION_SUPPORTS:表示当前方法不需要事务上下文,但是如果存在一个当前事务,方法会在事务中运行
4、隔离级别,定义了一个事务可能受其他并发事务影响的程度
5、多事务并发运行会导致以下问题:
1)脏读(Dirty read)---一个事务读取了另一个事务改写但尚未提交的数据
2)不可重复读(Nonrepeatable read)---一个事务执行相同的查询两次或以上,每次都得到不同的数据
3)幻读(Phantom read)---一个事务读取了几行数据,接着另一个并发事务插入了一些数据,随后的查询中,第一个事务发现多了一些原本不存在的记录
6、理想情况下,事务之间完全隔离,从而防止这些问题发生
7、但完全的隔离会导致性能问题,因为涉及到锁定数据库中的记录。侵占性的锁定会阻碍并发性,要求事务互相等待以完成各自的工作
8、隔离级别

ISOLATION_DEFAULT:使用数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED:允许读取尚未提交的数据变更
ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据
ISOLATION_REPEATABLE_READ:可重复读,对同一个字段多次读取结果是一致的,除非被本事务自己修改
ISOLATION_SERIALIZABLE:串行化,完全服从了ACID的隔离级别,通过锁定相关的表来实现,性能受到最大的影响

序号 隔离级别 可避免情况
1 串行化 脏读、不可重复读、幻读
2 可重复读 脏读、不可重复读
3 读已提交 脏读
4 读未提交 无

oracle默认的隔离级别是READ COMMITTED,一个事务,不允许脏读、允许不可重复读、允许幻读。oracle保证读一致性(数据读相对于某个时间点有一致的结果)

9、只读
如果事务只对后端的数据库进行读操作,数据库可以利用事务的只读特性来进行一些特定的优化,通过将事务设置为只读,可以让数据库应用适当的优化措施
1)只读优化是在事务启动的时候由数据库实施,只有对那些具备启动一个新事务的传播行为的方法来说才有效
如果是原来有事务存在,加入原有的事务,只读优化是没有用的
2)如果采用Hibernate作为持久化机制,将事务声明为只读会导致Hibernate的flush模式被设置为FLUSH_NEVER,Hibernate避免和数据库进行不必要的同步,并将所有的更新延迟到事务结束
3)测试程序:加只读事务,从数据库读取一行数据,程序睡眠,手动更新数据,程序再读
和不加事务,读取一行数据,程序睡眠,手动更新数据,程序再读
10、事务超时(timeout)
长时间的事务会不必要地占用数据库资源。可以声明一个事务,在特定的时间后自动回滚,而不是等待结束
1)超时时钟会在事务开始时启动,所以只有对那些具备可能启动一个新事务的传播行为的方法来说,声明事务超时才有意义
11、回滚规则
定义哪些异常会导致事务回滚
1)默认情况下,事务只在遇到运行时异常才会回滚
2)可以声明事务在遇到特定的受查异常时像遇到运行时异常一样回滚
3)也可以声明事务遇到特定的运行时异常不回滚
12、Spring提供一个tx配置命名空间,极大简化声明式事务,将其添加到Spring XML配置文件中

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

13、tx命名空间提供一些新的XML配置元素,最重要的是

<tx:advice>

声明式事务用到了aop:

<tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="add*" propagation="REQUIRED"/>
        <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

使用tx:advice来声明一些通知
14、对于<tx:advice>来说,事务属性定义在<tx:attributes>元素中,该元素包含若干个<tx:method>元素。该元素为某个name属性(使用通配符)指定的方法定义事务参数
15、<tx:method>有多个属性来定义方法的事务策略

isolation:指定事务的隔离级别
propagation:定义事务的传播行为
read-only:指定事务是不是只读
Rollback rules:回滚规则
    rollback-for  指定事务对哪些检查性异常回滚,不提交
    no-rollback-for  指定事务对哪些异常应答继续运行,而不回滚
timeout:定义超时时间
16、当使用<tx:advice>来声明事务时,还需要一个事务管理器,默认被声明为id是transactionManager的Bean,可以指定:

<tx:advice id="txAdvice"
  transaction-manager="txManager">
...
</tx:advice>

17、<tx:advice>只是定义了AOP通知,用于把事务边界通知给方法,还需要一个切入点声明哪些Bean应该被通知。为了完整定义事务性切面,需要定义一个通知器(advisor)
18、以下XML定义通知器,使用txAdvice通知所有实现MessageService接口的Bean:

<aop:config>
    <aop:advisor
        pointcut="execution(* *...MessageService.*(...))"
        advice-ref="txAdvice"/>
</aop:config>

19、这里的pointcut属性使用AspectJ切入点表达式表明通知器适用于MessageService接口的所有方法,事务通知由advice-ref属性指定,引用到前面定义的txAdvice通知
20、配置文件&例子程序
例子程序:https://pan.baidu.com/s/1NuvD__f-RO3K7DgVQYCS1w

扫描二维码关注公众号,回复: 3568407 查看本文章
<?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:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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-3.2.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <!-- Bean declarations go here -->
	
	<!-- 数据源 -->
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url"
            value="jdbc:mysql://127.0.0.1:3306/spring?useUnicode=true&amp;characterEncoding=UTF-8" />
		<property name="username" value="root" />
		<property name="password" value="565656" />
		<!-- 连接池启动时的初始值 -->
		<property name="initialSize" value="1" />
		<!-- 连接池的最大值 -->
		<property name="maxTotal" value="300" />
		<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
		<property name="maxIdle" value="2" />
		<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
		<property name="minIdle" value="1" />
	</bean>
	
	<!-- jdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<constructor-arg ref="dataSource"/>
	</bean>
	
	<!-- 事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<!-- messageDao -->
	<bean id="messageDao" class="com.iotek.myTwitter.presistence.JdbcMessageDao">
		<property name="jdbcTemplate" ref="jdbcTemplate"/>
	</bean>
	
	<!-- messageService 服务对象-->
	<bean id="messageService2" class="com.iotek.myTwitter.service.MessageService2">
		<!-- 只需要注入messageDao -->
		<property name="messageDao" ref="messageDao"/>
	</bean>
	
	<!-- 定义通知 -->
	<tx:advice id="txAdvice">
		<tx:attributes>
			<!-- 传播方式是REQUIRED, 需要在事务中 -->
			<tx:method name="add*" propagation="REQUIRED"/>
			<!-- 其他的方法 -->
			<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
		</tx:attributes>
	</tx:advice>
	<!-- 定义通知器, 切入点 -->
	<aop:config>
		<aop:advisor 
		pointcut="execution(* *..MessageService2.*(..))"
		advice-ref="txAdvice"/>
	</aop:config>
</beans>

四、定义注解驱动的事务

1、除了<tx:advice>元素,tx命名空间还提供了<tx:annotation-driven>元素,可以通过transaction-manager属性指定特定的事务管理器

<tx:annotation-driven transaction-manager="txManager"/>

2、这一行XML配置具有强大功能,允许在最有意义的位置声明事务规则:在事务性方法上
3、<tx:annotation-driven>元素告诉Spring检查上下文中所有的Bean并查找使用@Transactional注解的Bean,不管这个注解是用在类级别上还是方法级别上
4、对于每一个使用@Transactional注解的Bean,<tx:annotation-driven>会自动为它添加事务通知,通知的事务属性通过@Transactional注解的参数来定义
这样就不用在xml文件里面通过tx:advice,tx:advisor来定义

@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
public calss MessageServiceImpl implements MessageService {
    ...
    @Transactional(propagation=Propagation.REQUIRED, readOnly=false)
    public void saveMessage(Message message) {
        ...
    }
    ...
}

这个类事务的传播方式是SUPPORTS,只读。然后对类里的某个方法也可以加事务,可以改写事务属性,会覆盖掉类上的属性
5、在类的级别上,MessageServiceImpl使用了@Transactional注解,表示所有的方法都支持事务并且是只读的
6、在方法级别上,saveMessage()方法通过注解标识这个方法所需的事务上下文
7、事务要加在业务逻辑方法的地方开启,不要到DAO里面数据库方法开启,因为DAO里是细粒度的,应该在service里面类方法上
8、配置文件&例子程序
例子程序:https://pan.baidu.com/s/1ZBmV0PLY4havfIpwC4PbwQ

<?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:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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-3.2.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <!-- Bean declarations go here -->
	
	<!-- 打开事务注解驱动 -->
	<tx:annotation-driven/>
	
	<!-- 数据源 -->
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url"
            value="jdbc:mysql://127.0.0.1:3306/spring?useUnicode=true&amp;characterEncoding=UTF-8" />
		<property name="username" value="root" />
		<property name="password" value="565656" />
		<!-- 连接池启动时的初始值 -->
		<property name="initialSize" value="1" />
		<!-- 连接池的最大值 -->
		<property name="maxTotal" value="300" />
		<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
		<property name="maxIdle" value="2" />
		<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
		<property name="minIdle" value="1" />
	</bean>
	
	<!-- jdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<constructor-arg ref="dataSource"/>
	</bean>
	
	<!-- 事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<!-- messageDao -->
	<bean id="messageDao" class="com.iotek.myTwitter.presistence.JdbcMessageDao">
		<property name="jdbcTemplate" ref="jdbcTemplate"/>
	</bean>
	
	<!-- messageService 服务对象-->
	<bean id="messageService3" class="com.iotek.myTwitter.service.MessageService3">
		<!-- 只需要注入messageDao -->
		<property name="messageDao" ref="messageDao"/>
	</bean>
	
</beans>

猜你喜欢

转载自blog.csdn.net/csj50/article/details/82795370
今日推荐