Spring 使用事务

>事务的开发是放在service业务层中的,因为事务涉及到多个表,多个dao,不能写在某一个dao中;

>Spring事务(了解:面向接口式编程,与底层固定的事务资源没有绑定在一起)

Spring事务策略是通过PlatformTransactionManager接口体现的,该接口是Spring事务策略的核心。该接口的源代码如下:

PlatformTransactionManager是一个与任何事务策略分离的接口,随着底层不同事务策略的切换,应用必须采用不同的实现类。PlatformTransactionManager接口没有与任何事务性资源捆绑在一起,它可以适用于任何的事务策略,结合Spring的IoC容器,可以向PlatformTransactionManager注入相关的平台的特性。

PlatformTransactionManager接口有许多不同的实现类,应用程序面向与平台无关的接口编程,当底层采用不同的持久层技术时,系统只需要使用不同的PlatformTransactionManager的实现类即可——而这种切换通常由Spring容器负责管理,应用程序既无须与具体的事务API耦合,也无须与特定实现类耦合,从而将应用和持久化技术、事务API彻底分离开来。

在PlatformTransactionManager接口内,包含一个getTransaction(TransactionDefinition definition)方法,该方法根据TransactionDefinition参数返回一个TransactionStatus对象。TransactionStatus对象表示一个事务,该对象被关联在当前执行的线程上。

getTransaction(TransactionDefinition definition)返回的TransactionStatus对象,可能是一个新的事务,也可能是一个已经存在的事务对象。如果当前执行的线程已经处于事务管理下,则返回当前线程的事务对象,否则创建一个新的事务对象返回。

TransactionDefinition接口定义了一个事务规则,该接口必须指定如下几个属性值。

  • 事务隔离:当前事务和其他事务的隔离程度。例如,这个事务能否看到其他事务未提交的数据等。
  • 事务传播:通常在事务中执行的代码都会在当前事务中运行。但是,如果一个事务上下文已经存在,有几个选项可以指定该事务性方法的执行行为。
  • 事务超时:事务在超时前能运行多久,也就是事务的最长持续时间。如果事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚。
  • 只读状态:只读事务不修改任何数据。只读事务是非常有用的优化。

TransactionStatus代表事务本身,它提供了简单的控制事务执行和查询事务状态的方法,这些方法在所有的事务API中都是相同的。TransactionStatus接口的源代码如下:

Spring具体的事务管理由PlatformTransactionManager的不同实现类来完成。在Spring容器中配置管理类时,必须针对不同的环境提供不同的实现类。

>声明式事务管理

1、使用XML配置的方式

Spring的事务管理,提供了一个DataSourceTransactionManager类,用于进行声明式事务管理类,该类在org.springframework.jdbc.datasource包下,该类中包含一个dataSource属性用于配置一个数据源。配置如下:

事务管理器在配置时引用了dataSource对象,此对象为数据库连接池对象,表示事务管理器对事务的管理是通过连接池对象中的连接进行管理。事务管理器是通过消息通知的方式起作用,所以需要为事务管理来配置一个事务通知消息,即事务管理器在接收到一个消息通知时才会开启事务的操作。配置如下:

配置通知消息需要使用<tx:advice>标签,此标签包含id、transaction-manager两个重要属性。

  • id:消息通知的唯一标识ID
  • transaction-manager:消息通知发出后交由哪个事务管理器去处理。

在<tx:advice>标签下含有<tx:attributes>标签,用于配置在执行什么方法时才会触发消息发送(通知给事务管理类txManager)。配置方法使用<tx:method>方法,如下所示:

在配置方法时可以使用统配符的方式,即*,所以在开发时建议如果是查询数据的方法以get开头,那么上面配置就可以改为如下格式:

以上为消息通知的配置,表示在调用XXX方法时,如果在消息通知的配置管理内,则会触发消息推送,advice会将消息告知给相应的事务管理器,去执行事务管理。消息通知配置仅仅是定义了在遇到XXX方法执行时会触发消息通知,但是在整个应用中要执行的方法太多,不可能让Spring去监控所有的方法,这样会大大降低应用的响应效率。所以需要有这样一种机制,即在合理的情况下来启动消息通知:

在软件开发过程中,持久层的操作是dao层,而事务管理是对dao层的事务管理,而dao层是对service层提供服务的,而service层在业务处理时有可能会因为业务稍微复杂,需要调用几个dao来完成整体的业务流程,这时就涉及到事务,即一个dao失败就需要全部失败,全部成功才能成功。所以,启动消息通知的操作就需要在servcie层来完成。但是service层是纯粹的业务层,而事务的触发相当于公共业务,所以建议使用aop来完成此操作。配置如下:

使用Spring进行事务管理时,大多数都是采用声明式事务管理,此种方式对应用的影响最小,也是最符合非侵入式的轻量级容器的概念。

使用jdbc进行事务管理时,在dao层的代码需要进行相应的改动,改为使用Spring框架提供的JdbcTemplage类,此类在org.springframework.jdbc.core.JdbcTemplate下,该类的成员变量dataSource为指定的数据库连接池对象,在xml文件中配置如下:

>XML配置的方式事务举例

spring配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:c="http://www.springframework.org/schema/c"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
           http://www.springframework.org/schema/util
           http://www.springframework.org/schema/util/spring-util-4.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-4.0.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" default-autowire="byName" >
     <!-- 自动扫描,配合注解来确定加载哪些类 -->     
     <context:component-scan base-package="com.langsin.dao.impl
                                           com.langsin.service.impl"></context:component-scan>         
     <!-- 配置数据库连接池  -->      
     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
     <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
     <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=Hongkong"/>
     <property name="user" value="root"/>
     <property name="password" value="123456"/>
     <property name="maxPoolSize" value="20"/>
     <property name="minPoolSize" value="10"/>
     <property name="initialPoolSize" value="10"/>
     <property name="maxIdleTime" value="200"/>
     </bean> 

         
     <!-- 声明式事务配置 -->    
     <!-- 声明式事务管理类:由它来管理(执行,提交,回滚)事务 -->  
     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
     </bean>
     <!-- 通过切面aop的方式引入进来 -->
     <!-- 配置事务的消息通知 -->
     <tx:advice id="txAdvice" transaction-manager="txManager">
      <tx:attributes>
       <!-- 查询开头:只读  -->
       <tx:method name="query*" read-only="true"/>
       <tx:method name="get*" read-only="true"/>
       <!-- 其他:开启事务  -->
       <tx:method name="*" propagation="REQUIRED"/>
      </tx:attributes>
     </tx:advice>

     
     <!-- 在哪用事务? Dao:No Service:YES
                           在Service里调用Dao,Dao里的方法就是以insert、query...开头
                           在执行Dao的时候事务的代码(但是事务的代码实在事务管理器中)也要执行,就要使用切面的方式配置进去                 
       -->
     <!-- Spring中的事务是通过切面的方式植入到代码中 -->
     <aop:config>
      <!-- 配置一个切入点 -->
      <!-- execution执行表达式	第一个*:service方法有无返回值都+空格+包名+任何类+任何方法+可以传或不传任意个参数-->
      <aop:pointcut id="pointcut" expression="execution(* com.langsin.service.impl.*.*(..))"/>
      <!-- 配置消息调用,当切点的方法执行,消息就起作用检测方法名,并发给声明事务管理类 -->
      <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
     </aop:config>
     


     <!-- 配置Spring的JDBC事务支持类 -->
     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
     <property name="dataSource" ref="dataSource"/>
     </bean>   
</beans>

service层(接口代码省略):

//Service注解,传入的为该类的id,如果不写默认该类的id为类名首字母小写。
@Service("roleService")
public class RoleServiceImpl implements RoleService {
    @Autowired
	private RoleDao dao=null;
	@Override
	public void insertRoleList() throws Exception {
		dao.insertRole();		
	}
}

dao层(接口代码省略):

//这个注解通常不用,后期dao使用mapper
@Repository("dao")
public class RoleDaoImpl implements RoleDao {
	
	@Autowired
	private JdbcTemplate jdbcTemplate=null;

	//读取一个文件,数据,插入到数据库
	public void insertRole() throws Exception {
		BufferedReader reader=new BufferedReader(new FileReader("./data.txt"));
		String line=null;
		while ((line=reader.readLine())!=null) {
			Object[] datas=line.split(",");
			this.jdbcTemplate.update("insert into role(role_code,role_name) values(?,?)", datas);
		}
	}
}

txt文件(其中第三行的数据会引起SQL异常,Data too long for column):

executive,主管
chairman,董事长
superman,超级赛亚人打小怪兽
test,测试

当测试方法执行时,触发sql异常,jdbc的事务就会进行回滚,所有的四条数据都不会插入。

>2、使用注解 @Transaction的方式(推荐)

使用注解的方式,配置更为简洁,可以去掉事务的消息通知配置,只需要在Spring的配置文件中,加入事务驱动即可,然后在需要使用事务的方法或Service层类中使用@Transaction注解即可,如下所示:

Service层类或方法使用注解。

>注解方式举例

接上面的例子,将切点配置注掉,上面的方式就不再起作用。

Spring配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:c="http://www.springframework.org/schema/c"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
           http://www.springframework.org/schema/util
           http://www.springframework.org/schema/util/spring-util-4.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-4.0.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" default-autowire="byName" >
     <!-- 自动扫描,配合注解来确定加载哪些类 -->     
     <context:component-scan base-package="com.langsin.dao.impl
                                           com.langsin.service.impl"></context:component-scan>         
     <!-- 配置数据库连接池  -->      
     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
     <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
     <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=Hongkong"/>
     <property name="user" value="root"/>
     <property name="password" value="123456"/>
     <property name="maxPoolSize" value="20"/>
     <property name="minPoolSize" value="10"/>
     <property name="initialPoolSize" value="10"/>
     <property name="maxIdleTime" value="200"/>
     </bean> 
     <!-- 配置事务注解的驱动(类) -->
     <tx:annotation-driven transaction-manager="txManager"/>
         
     <!-- 声明式事务配置 -->    
     <!-- 声明式事务管理类:由它来管理(执行,提交,回滚)事务 -->  
     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
     </bean>
     
     <!-- 配置Spring的JDBC事务支持类 -->
     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
     <property name="dataSource" ref="dataSource"/>
     </bean>  
</beans>

Service层注解:

//Service注解,传入的为该类的id,如果不写默认该类的id为类名首字母小写。
@Service("roleService")
public class RoleServiceImpl implements RoleService {
	@Transactional
	@Override
	public void insertRoleList() throws Exception {
		dao.insertRole();		
	}
}

猜你喜欢

转载自blog.csdn.net/AhaQianxun/article/details/93930431