Spring中常用的事务管理有以下两种方式:
一种是基于@Transactional注解的声明式,另外一个是基于XML配置的事务管理。
下边以Spring 4.3.23.RELEASE这个版本为例来提供两种事务管理方式的常用代码。
先以添加员工基本信息和员工部门关联信息为例来说明纯注解的事务声明方式,下边是配置类的代码:
package com.huixin.tx;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* @Configuration 告诉Spring容器这是一个配置类.<br>
* @ComponentScan 包扫描, 相当于XML配置文件中的 <context:component-scan base-package="com.huixin.tx"/> 标签.
* 该注解会将指定包下的带有@Controller、@Service、@Repository、@Component注解的类加入到Spring容器中.<br>
* Spring事务相关配置<br>
* @EnableTransactionManagement 表示开启事务管理, 相当于XML配置文件中的 <tx:annotation-driven/> 标签<br>
* <b>使用声明式事务的步骤:</b><br>
* 1、将DAO、Service注册到Spring容器中.<br>
* 2、在需要添加事务的方法上加上 @Transactional 注解.<br>
* 3、把DataSource、TransactionManager对象注册到Spring容器中.<br>
* 4、在配置类上加上 @EnableTransactionManagement 注解开启事务管理.<br>
*/
@Configuration
@ComponentScan({"com.huixin.tx"})
@EnableTransactionManagement
public class TransactionConfig {
/**
* 注册DataSource
* @return
* @throws Exception
*/
@Bean
public DataSource dataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("oracle.jdbc.driver.OracleDriver");
dataSource.setJdbcUrl("jdbc:oracle:thin:@127.0.0.1:1521:Ora11g");
dataSource.setUser("username");
dataSource.setPassword("user_pass");
return dataSource;
}
/**
* 注册JdbcTemplate
* @param dataSource
* @return
*/
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
/**
* 注册TransactionManager
* @param dataSource
* @return
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
上边数据源的各项参数都是写死的,实际的开发中可以放在配置文件中,或者配在应用服务器的JNDI中。
下边是Service类:
package com.huixin.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
// ID默认为 empService
@Service
public class EmpService {
/*
* @Autowired 自动装配, 默认先按照类型进行查找装配. 如果按照类型查找到多个, 则按照ID查找装配(属性名作为默认的ID进行匹配). 自动装配推荐使用该注解.
*/
@Autowired
private EmpDAO empDAO;
/**
* @Transactional 表示为该方法添加事务.
* 该方法中插入了员工基本信息和员工部门关联信息
*/
@Transactional
public void insert() {
empDAO.insertEmpBaseInfo();
empDAO.insertEmpDeptInfo();
System.out.println(10/0);
}
}
在该事务方法中,后边的10/0会抛异常,那么前边插入的两条记录应该回滚。
下边是DAO类的代码:
package com.huixin.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
//ID默认为 empDAO
@Repository
public class EmpDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insertEmpBaseInfo() {
String sql = "INSERT INTO tab_emp_base_info (emp_id,emp_name,emp_sex) VALUES (?,?,?)";
jdbcTemplate.update(sql, 1, "张三", 'M');
}
public void insertEmpDeptInfo() {
String sql = "INSERT INTO tab_emp_dept_info (emp_id,dept_id) VALUES (?,?)";
jdbcTemplate.update(sql, 1, 1001);
}
}
至此纯注解的事务配置写完了。调用Service的insert方法,如果出险异常,事务会回滚。
下边以经典的转账为例来说明纯XML配置事务的方法。先写一个转账的DAO接口,代码如下:
package com.example.transaction.dao;
public interface AccountInfoDAO {
/**
* 根据账号查询余额
* @param accountNo 账号
* @return 余额
*/
public double findBalanceByAccountNo(String accountNo);
/**
* 根据账号减少金额
* @param accountNo 账号
* @param varyMoney 变化金额
* @return 影响记录数
*/
public int reduceBalanceByAccountNo(String accountNo, double varyMoney);
/**
* 根据账号增加金额
* @param accountNo 账号
* @param varyMoney 变化金额
* @return 影响记录数
*/
public int addBalanceByAccountNo(String accountNo, double varyMoney);
}
下边是转账DAO接口的实现类:
package com.example.transaction.dao.impl;
import org.springframework.jdbc.core.JdbcTemplate;
import com.example.transaction.dao.AccountInfoDAO;
public class AccountInfoDAOImpl implements AccountInfoDAO {
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public double findBalanceByAccountNo(String accountNo) {
String sql = "SELECT balance FROM tab_account_info WHERE account_no=?";
double balance = jdbcTemplate.queryForObject(sql, Double.class, accountNo);
return balance;
}
@Override
public int reduceBalanceByAccountNo(String accountNo, double varyMoney) {
String sql = "UPDATE tab_account_info SET balance = balance-? WHERE account_no = ?";
int rows = jdbcTemplate.update(sql, varyMoney, accountNo);
return rows;
}
@Override
public int addBalanceByAccountNo(String accountNo, double varyMoney) {
String sql = "UPDATE tab_account_info SET balance = balance+? WHERE account_no = ?";
int rows = jdbcTemplate.update(sql, varyMoney, accountNo);
return rows;
}
}
下边是转账的Service接口:
package com.example.transaction.service;
public interface AccountInfoService {
/**
* 转账
* @param srcAccountNo 源账号
* @param targetAccountNo 目标账号
* @param transferMoney 转账金额
* @return true表示成功, 否则表示失败
*/
public boolean transferAccount(String srcAccountNo, String targetAccountNo, double transferMoney);
}
下边写转账Service接口的实现类:
package com.example.transaction.service.impl;
import com.example.transaction.dao.AccountInfoDAO;
import com.example.transaction.service.AccountInfoService;
public class AccountInfoServiceImpl implements AccountInfoService {
private AccountInfoDAO accountInfoDAO;
public AccountInfoDAO getAccountInfoDAO() {
return accountInfoDAO;
}
public void setAccountInfoDAO(AccountInfoDAO accountInfoDAO) {
this.accountInfoDAO = accountInfoDAO;
}
@Override
public boolean transferAccount(String srcAccountNo, String targetAccountNo, double transferMoney) {
double balance = accountInfoDAO.findBalanceByAccountNo(srcAccountNo);
if (balance < transferMoney) {
return false;
}
int rows = accountInfoDAO.reduceBalanceByAccountNo(srcAccountNo, transferMoney);
if (rows <= 0) {
return false;
}
rows = accountInfoDAO.addBalanceByAccountNo(targetAccountNo, transferMoney);
if (rows <= 0) {
return false;
}
return true;
}
}
下边写一个配置文件datasource.properties,用于保存数据源的用户名密码等信息,并将该文件放在classpath下:
oracle.driverClass=oracle.jdbc.driver.OracleDriver
oracle.jdbcUrl=jdbc:oracle:thin:@localhost:1521:Ora11g
oracle.username=username
oracle.password=user_pass
下来写Spring事务的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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 包扫描配置 -->
<context:component-scan base-package="com.example.transaction"/>
<!-- 加载外部的属性配置文件, 在后边通过${}的方式获取properties配置文件中的值 -->
<context:property-placeholder location="datasource.properties"/>
<!-- 配置DataSource -->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${oracle.driverClass}" />
<property name="jdbcUrl" value="${oracle.jdbcUrl}" />
<property name="user" value="${oracle.username}" />
<property name="password" value="${oracle.password}" />
<!-- <property name="acquireIncrement" value="20" />
<property name="acquireRetryAttempts" value="5" />
<property name="acquireRetryDelay" value="2000" />
<property name="autoCommitOnClose" value="false" />
<property name="idleConnectionTestPeriod" value="5" /> -->
<property name="minPoolSize" value="5" />
<property name="initialPoolSize" value="20" />
<property name="maxPoolSize" value="200" />
<property name="maxIdleTime" value="0" />
<property name="preferredTestQuery" value="SELECT SYSDATE FROM dual" />
</bean>
<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="datasource"/>
</bean>
<bean id="accountInfoDAO" class="com.example.transaction.dao.impl.AccountInfoDAOImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="accountInfoService" class="com.example.transaction.service.impl.AccountInfoServiceImpl">
<property name="accountInfoDAO" ref="accountInfoDAO"/>
</bean>
<!-- 配置TransactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<!-- 配置事务通知的属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transferAccount" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 配置事务切入点 -->
<aop:pointcut expression="execution(public * com.example.transaction.service.impl.AccountInfoServiceImpl.transferAccount(String, String, double))" id="txPointcut"/>
<!-- 把事务切入点和事务通知属性关联起来 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
至此,XML配置事务的代码写完,调用Service里边的transferAccount方法进行转账,任何一部出错,都会回滚事务。
当然还有一些混合型的,比如XML配置文件中注册DataSource、TransactionManager等等信息:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 包扫描配置 -->
<context:component-scan base-package="com.example.transaction"/>
<!-- 加载外部的属性配置文件, 在后边通过${}的方式获取properties配置文件中的值 -->
<context:property-placeholder location="datasource.properties"/>
<!-- 配置DataSource -->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${oracle.driverClass}" />
<property name="jdbcUrl" value="${oracle.jdbcUrl}" />
<property name="user" value="${oracle.username}" />
<property name="password" value="${oracle.password}" />
<!-- <property name="acquireIncrement" value="20" />
<property name="acquireRetryAttempts" value="5" />
<property name="acquireRetryDelay" value="2000" />
<property name="autoCommitOnClose" value="false" />
<property name="idleConnectionTestPeriod" value="5" /> -->
<property name="minPoolSize" value="5" />
<property name="initialPoolSize" value="20" />
<property name="maxPoolSize" value="200" />
<property name="maxIdleTime" value="0" />
<property name="preferredTestQuery" value="SELECT SYSDATE FROM dual" />
</bean>
<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="datasource"/>
</bean>
<!-- 配置TransactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<!-- 启用事务的注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
在Service类上加@Service注解,事务方法上加@Transactional注解,DAO类上加@Repository注解。这些就不再赘述。