今天总结下spring对于事务的支持。我只会把每一步的步骤都写清楚,如果有些需要着重说的,我会以注解的方式写在代码里。
spring的事务支持主要分为两大类:
1. 编程式事务
通过编码的方式来实现事务的管理(很少用).
2.声明式事务
又分为三种。
1. 服务层代理类的方式(很少用)
2. aop的方式实现(最常用)
3. 注解方式实现(常用)
准备工作
我们的场景为转账,从AAA帐号转200元到BBB的帐号,
我们在AAA转出200元后,使代码发生异常(1/0),如果数据中没有发生AAA钱转出(因为发生了异常,事务回滚。)则事务生效。
[idea创建maven工程](http://blog.csdn.net/baibinboss/article/details/62897695)
在pom.xml文件中加入我们的依赖,清单如下:
pom.xml内容
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baibin</groupId>
<artifactId>transaction</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--aop支持,spring的aop支持是依赖aop联盟的-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!--阿里开源的数据库链接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.16</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--spring上下文模块-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<!--spring数据库操作-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<!--spring测试集成-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.6.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
配置数据库链接信息
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=password
数据库建表语句
CREATE TABLE `t_account` (
`id` varchar(32) NOT NULL DEFAULT '',
`name` varchar(32) DEFAULT NULL,/*帐号*/
`account` decimal(10,0) DEFAULT NULL,/*钱*/
PRIMARY KEY (`id`)
);
INSERT INTO `t_account` VALUES ('1', 'AAA', '1000');
INSERT INTO `t_account` VALUES ('2', 'BBB', '1000');
INSERT INTO `t_account` VALUES ('3', 'CCC', '1000');
编程式事务
第一步:配置spring,配置文件内容如下:
<?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"
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">
<!--扫描配置的注解-->
<context:component-scan base-package="com.baibin.demo1"/>
<!--将所有的类路径下面的配置文件交给spring管理-->
<context:property-placeholder location="classpath*:*.properties"/>
<!--数据库操作-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg>
<ref bean="dataSource"></ref>
</constructor-arg>
</bean>
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.mysql.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--事务管理器
这里因为我们是用最基础的方式来实现对数据库操作的,所以我们使用DataSourceTransactionManager
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务模版,我们通过这个对象来进行编程式事务操作-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
</beans>
第二步:服务层代码:
package com.baibin.demo1.service;
/**
* 接口代码
*/
public interface TransferService {
/**
* 转账代码
* @param up 转出帐号
* @param down 转入帐号
* @param account 额度
*/
void transferAccounts(final String up,final String down,final double account);
}
package com.baibin.demo1.service;
import com.baibin.demo1.dao.TransferDAO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Resource;
/**
* 实现类代码
*/
@Service
public class TransferServiceImpl implements TransferService {
@Resource
private TransferDAO transferDAO;
@Resource
private TransactionTemplate transactionTemplate;
/**
* 转账代码,因为需要在内部类中使用参数,所以需要写成final修饰的
*
* @param up 转出帐号
* @param down 转入帐号
* @param account 额度
*/
public void transferAccounts(final String up, final String down, final double account) {
//转出钱,转入钱,他们应该一起成功,或者一起失败,我们用 1/0 来模拟发生意外。
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//转出
transferDAO.expenditure(up, account);
//发生异常
int i = 1/0;
//转入
transferDAO.income(down, account);
}
});
}
}
第三步:持久层代码
package com.baibin.demo1.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
/**
* 持久层
*/
@Repository
public class TransferDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 进账
*
* @param down
* @param account
*/
public void income(String down, double account) {
String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT+? WHERE NAME=?";
jdbcTemplate.update(sql, account, down);
}
/**
* 转出
*
* @param up
* @param account
*/
public void expenditure(String up, double account) {
String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT-? WHERE NAME=?";
jdbcTemplate.update(sql, account, up);
}
}
第四步:单元测试
package com.baibin.demo1.test;
import com.baibin.demo1.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
/**
* 编程试事务,在使用所有注解前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
/**
* 加载spring配置文件
*/
@ContextConfiguration(locations = {"classpath:beans1.xml"})
public class TransferServiceImplTest {
@Autowired
private TransferService transferService;
@Test
public void transferAccounts() throws Exception {
/**
* AAA帐号转账200到BBB帐号
*/
transferService.transferAccounts("AAA","BBB",200D);
}
}
编程式事务需要我们自己来写代码,比较麻烦,一般很少用。
声明式事务之服务层代理类的方式
第一步:配置spring,配置文件内容如下:
<?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"
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">
<!--扫描配置的注解-->
<context:component-scan base-package="com.baibin.demo2"/>
<!--将所有的类路径下面的配置文件交给spring管理-->
<context:property-placeholder location="classpath*:*.properties"/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg>
<ref bean="dataSource"></ref>
</constructor-arg>
</bean>
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.mysql.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transferDAO" class="com.baibin.demo2.dao.TransferDAO">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="transferService" class="com.baibin.demo2.service.TransferServiceImpl">
<property name="transferDAO" ref="transferDAO"></property>
</bean>
<!--事务代理类,我们在使用服务层对象的时候不在直接注入transferService而是注入我们的代理类-->
<bean id="transferServiceProxy"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!--为代理类注入事务管理器-->
<property name="transactionManager" ref="transactionManager"></property>
<!--代理的对象-->
<property name="target" ref="transferService"></property>
<!--事务配置-->
<property name="transactionAttributes">
<props>
<!--任意方法名
PROPAGATION_REQUIRED:需要一个事务,如果没有则创建新的事务-->
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
第二步:服务层代码:
package com.baibin.demo2.service;
import com.baibin.demo1.service.TransferService;
import com.baibin.demo2.dao.TransferDAO;
/**
* 服务层代码
*/
public class TransferServiceImpl implements TransferService {
private TransferDAO transferDAO;
public TransferDAO getTransferDAO() {
return transferDAO;
}
/**
* 需要set方法注入bean
* @param transferDAO
*/
public void setTransferDAO(TransferDAO transferDAO) {
this.transferDAO = transferDAO;
}
public void transferAccounts(String up, String down, double account) {
transferDAO.expenditure(up, account);
/*手动触发异常*/
int i = 1/0;
transferDAO.income(down, account);
}
}
第三步:持久层代码
package com.baibin.demo1.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
/**
* 持久层
*/
@Repository
public class TransferDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 进账
*
* @param down
* @param account
*/
public void income(String down, double account) {
String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT+? WHERE NAME=?";
jdbcTemplate.update(sql, account, down);
}
/**
* 转出
*
* @param up
* @param account
*/
public void expenditure(String up, double account) {
String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT-? WHERE NAME=?";
jdbcTemplate.update(sql, account, up);
}
}
第四步:单元测试
package com.baibin.demo2.test;
import com.baibin.demo1.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
/**
* 在使用所有注解前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
/**
* 加载spring配置文件
*/
@ContextConfiguration(locations = {"classpath:beans2.xml"})
public class TransferServiceImplTest {
/**
* 这里需要注意,我们不在直接注入TransferService 而是注入transferServiceProxy
*/
@Resource(name = "transferServiceProxy")
private TransferService transferService;
@Test
public void transferAccounts() throws Exception {
transferService.transferAccounts("AAA","BBB",200);
}
}
代理类的方式呢,优点是对java代码没有了污染,缺点就是需要配置的工作太多了。
声明式事务之aop切面方式
第一步:配置spring,配置文件内容如下:
<?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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描配置的注解-->
<context:component-scan base-package="com.baibin.demo3"/>
<!--将所有的类路径下面的配置文件交给spring管理-->
<context:property-placeholder location="classpath*:*.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.mysql.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager" >
<tx:attributes>
<!--name里写事务管理的匹配模式,*是通配符,代表所有的方法 *Save代表Save名结束的方法-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置aop切入点-->
<aop:config>
<!--aop切入的规则-->
<aop:pointcut id="allMethod" expression="execution(* com.baibin.demo3..*.*(..))"></aop:pointcut>
<!--指向事务通知-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="allMethod"></aop:advisor>
</aop:config>
</beans>
第二步:服务层代码:
package com.baibin.demo3.service;
import com.baibin.demo1.service.TransferService;
import com.baibin.demo3.dao.TransferDAO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 服务层代码
*/
@Service
public class TransferServiceImpl implements TransferService {
@Resource
private TransferDAO transferDAO;
public void transferAccounts(String up, String down, double account) {
transferDAO.expenditure(up, account);
/*手动触发异常*/
int i = 1 / 0;
transferDAO.income(down, account);
}
}
第三步:持久层代码
package com.baibin.demo3.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* Created by ibm on 2017/3/22.
*/
@Repository
public class TransferDAO {
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 进账
*
* @param down
* @param account
*/
public void income(String down, double account) {
String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT+? WHERE NAME=?";
jdbcTemplate.update(sql, account, down);
}
/**
* 转出
*
* @param up
* @param account
*/
public void expenditure(String up, double account) {
String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT-? WHERE NAME=?";
jdbcTemplate.update(sql, account, up);
}
}
第四步:单元测试
package com.baibin.demo3.test;
import com.baibin.demo1.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
/**
* 在使用所有注解前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
/**
* 加载spring配置文件
*/
@ContextConfiguration(locations = "classpath:beans3.xml")
public class TransferServiceImplTest {
@Resource
private TransferService transferService;
@Test
public void transferAccounts() throws Exception {
transferService.transferAccounts("AAA","BBB",200D);
}
}
aop的方式我个人觉得是最佳的方式,一个项目配置一个就可以完成所有的事务管理了(强烈建议)。
声明式事务之注解方式
第一步:配置spring,配置文件内容如下:
<?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"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描配置的注解-->
<context:component-scan base-package="com.baibin.demo4"/>
<!--将所有的类路径下面的配置文件交给spring管理-->
<context:property-placeholder location="classpath*:*.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.mysql.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--这里需要启动注解解析,需要设置事务管理器,如果不写则会默认为transactionManager-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
第二步:服务层代码:
package com.baibin.demo4.service;
import com.baibin.demo1.service.TransferService;
import com.baibin.demo4.dao.TransferDAO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 服务层代码
*/
@Service
public class TransferServiceImpl implements TransferService {
@Resource
private TransferDAO transferDAO;
/**
* 这里需要使用 @Transactional注解,propagation是指传播方式,具体的自己可以查下,这次就不在博客里赘述了。
* @param up 转出帐号
* @param down 转入帐号
* @param account 额度
*/
@Transactional(propagation = Propagation.REQUIRED)
public void transferAccounts(String up, String down, double account) {
transferDAO.expenditure(up, account);
transferDAO.income(down, account);
}
}
第三步:持久层代码
package com.baibin.demo4.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* Created by ibm on 2017/3/22.
*/
@Repository
public class TransferDAO {
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 进账
*
* @param down
* @param account
*/
public void income(String down, double account) {
String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT+? WHERE NAME=?";
jdbcTemplate.update(sql, account, down);
}
/**
* 转出
*
* @param up
* @param account
*/
public void expenditure(String up, double account) {
String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT-? WHERE NAME=?";
jdbcTemplate.update(sql, account, up);
}
}
第四步:单元测试
package com.baibin.demo4.test;
import com.baibin.demo1.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
/**
* 在使用所有注解前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
/**
* 加载spring配置文件
*/
@ContextConfiguration(locations = "classpath:beans4.xml")
public class TransferServiceImplTest {
@Resource
private TransferService transferService;
@Test
public void transferAccounts() throws Exception {
transferService.transferAccounts("AAA", "BBB", 200D);
}
}
注解的方式也比较不错,不过需要在每个类上面都写,还是建议大家使用aop的方式。
总结:
今天写了下java的spring提供的事务解决方案,我写的代码比较缀余,为的是大家可以拷过去就能用,事务还有一些隔离级别,
传播方式相关的内容,下次和大家一起分享。