前言
本博文主要阐述手写的MyBatis和手写的Spring的一个集成服务的实现,并实现了声明式事务的处理。
Spring集成MyBatis服务
定义声明式事务子服务
我们首先来创建一个子模块来定义声明式事务的顶级接口以及顶级注解。
/**
* 事务管理器顶级接口
* Created by rubin on 2021/3/24.
*/
public interface TransactionManager {
/**
* 开始事务
*/
void start() throws SQLException;
/**
* 提交事务
*/
void commit() throws SQLException;
/**
* 回滚事务
*/
void rollback() throws SQLException;
}
/**
* 扫描该注解 将其包装为声明式事务处理
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
}
模块结构如下:

实现Spring和MyBatis的集成模块
看过前篇博文的小伙伴都知道,我们的集成逻辑的重点就在我们的手写Spring框架有一个容器初始化后置处理器,我们就可以利用这个后置处理器,来将我们定义的mapper接口的代理对象提前注册进容器中。以供后续的bean装配使用。
工程准备
和之前的实现步骤一致,我们先来定义一些基础类。
首先创建集成模块子工程,引入我们之前定义的两个框架的依赖,maven坐标定义如下:
<?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">
<parent>
<artifactId>my-spring-demo</artifactId>
<groupId>com.rubin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-spring-mybatis</artifactId>
<description>项目容器与orm框架整合模块</description>
<dependencies>
<dependency>
<groupId>com.rubin</groupId>
<artifactId>my-mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.rubin</groupId>
<artifactId>my-spring-context</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.rubin</groupId>
<artifactId>my-spring-tx</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
再来定义我们的工具类:
/**
* 注解工具类
*/
public class AnnotationUtil {
/**
* 检查该类是否有该注解
*
* @param clazz
* @param annotationClass
* @return
*/
public static boolean hasAnnotation(Class clazz, Class annotationClass) {
if (clazz.getAnnotation(annotationClass) != null) {
return true;
}
Class[] interfaces = clazz.getInterfaces();
if (interfaces == null || interfaces.length == 0) {
return false;
}
for (int i = 0; i < interfaces.length; i++) {
if (interfaces[i].getAnnotation(annotationClass) != null) {
return true;
}
}
return false;
}
/**
* 检查该方法是否有该注解
*
* @param method
* @param annotationClass
* @return
*/
public static boolean hasAnnotation(Method method, Class annotationClass) {
return method.getAnnotation(annotationClass) != null;
}
}
最后,定义我们的Mapper扫描注解:
/**
* 标识这是一个mapper接口
* 容器启动 会扫描该注解的接口 生成代理类注入容器
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Mapper {
}
开发自定义容器初始化后置处理器
我们需要开发自定义的容器初始化后置处理器,来扫描我们的mapper接口。把所有扫描到的接口生成代理对象注入到容器中。实现逻辑如下:
/**
* 自定义容器处理器
* Created by rubin on 4/3/21.
*/
public class MybatisBeanFactoryProcessor implements BeanFactoryPostProcessor {
/**
* 自动驻入之前的钩子函数,此处扫描mapper接口,并代理注入容器
*
* @param autowiredBeanFactory
*/
@Override
public void postProcessBeanFactory(AutowiredBeanFactory autowiredBeanFactory) {
MapperScanner mapperScanner = (MapperScanner) autowiredBeanFactory.getBean(MapperScanner.class);
if (mapperScanner == null) {
throw new RuntimeException("the MapperScanner must be defined");
}
Set<Class> mapperClassSet = ReflectUtil.getAllInterfaceWithAnnotation(mapperScanner.getMapperLocation(), Mapper.class);
if (CollectionUtil.isEmpty(mapperClassSet)) {
return;
}
mapperClassSet.stream().forEach(mapperClass -> injectMapperProxy2BeanFactory(mapperClass, autowiredBeanFactory));
}
/**
* 将mapper生成代理类并注入到容器中
*
* @param mapperClass
* @param autowiredBeanFactory
*/
private void injectMapperProxy2BeanFactory(Class mapperClass, AutowiredBeanFactory autowiredBeanFactory) {
String beanId = BeanUtil.getDefaultBeanName(mapperClass.getSimpleName());
Object beanValue = ProxyUtil.getInterfaceJdkProxy(mapperClass, new MapperInvocationHandler(autowiredBeanFactory, mapperClass));
autowiredBeanFactory.registerBean(beanId, beanValue);
}
/**
* 执行顺序 升序排列执行
*
* @return
*/
@Override
public Integer getOrder() {
return 0;
}
}
由以上代码我们可以看出,我们需要准备一个接口扫描器的实体。该接口会存储我们Mapper接口的扫描根路径:
/**
* mapper接口扫描器
* Created by rubin on 4/4/21.
*/
@Data
public class MapperScanner implements Serializable {
private String mapperLocation;
}
再来定义我们的代理逻辑实现类:
/**
* mapper代理逻辑实现
*/
@AllArgsConstructor
public class MapperInvocationHandler implements InvocationHandler, JdkProxyHandler {
private AutowiredBeanFactory autowiredBeanFactory;
private Class<?> targetClass;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) autowiredBeanFactory.getBean(SqlSessionFactory.class);
if (sqlSessionFactory == null) {
throw new RuntimeException("the SqlSessionFactory must be defined");
}
Object mapper = sqlSessionFactory.openSession().getMapper(method.getDeclaringClass());
Class[] paramTypeArr = new Class[args.length];
for (int i = 0; i < args.length; i++) {
paramTypeArr[i] = args[i].getClass();
}
Method mapperMethod = mapper.getClass().getMethod(method.getName(), paramTypeArr);
return mapperMethod.invoke(mapper, args);
}
/**
* 获取代理目标类类型
*
* @return
*/
@Override
public Class<?> getTargetClass() {
return targetClass;
}
}
AOP
为什么我们要在这里先来介绍一下AOP是什么呢?是因为我们接下来实现声明式事务的部分,而这部分正是一个AOP特别典型的实现案例。而且,至此也就把我们Spring的两大特性IoC和AOP的预留部分补充上了。
AOP( Aspect oriented Programming )概念上来将就是⾯向切⾯编程/⾯向⽅⾯编程。它是一种横切逻辑,即在不改变原有业务代码的前提下,在原来逻辑的某一个点去织入我们的横切逻辑,比如日志打印,请求耗时统计等等。
为什么叫面向切面编程,切面的概念是怎么来的呢?我们的“切”就是切入逻辑,影响的方法就叫做切点,我们不能只影响一个方法,所有的切点就构成了我们的切面。
AOP的实现方案有很多,我们本案例只讲解代理模式实现AOP,如果小伙伴有兴趣可以深入了解一下Spring的AOP实现方案,Spring的AOP实现方案比较综合和全面。
声明式事务开发
首先,我们要定义我们自己的事务管理器,来统一管理事务:
/**
* 事务控制器
*/
@NoArgsConstructor
@AllArgsConstructor
public class MybatisTransactionManager implements TransactionManager {
private ConnectionFactory connectionFactory;
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
/**
* 开始事务
*/
@Override
public void start() throws SQLException {
connectionFactory.getConnection().setAutoCommit(false);
}
/**
* 提交事务
*/
@Override
public void commit() throws SQLException {
connectionFactory.getConnection().commit();
}
/**
* 回滚事务
*/
@Override
public void rollback() throws SQLException {
connectionFactory.getConnection().rollback();
}
}
事务管理器里面封装的我们的连接池工厂对象,我们先看实现代码:
/**
* Connection工厂
* 保证了同一个线程使用同一个连接
*/
public class ThreadConnectionFactory implements ConnectionFactory {
private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
public DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 生产Collection,保证了同一个线程使用同一个连接
*
* @return
*/
@Override
public Connection getConnection() {
if (dataSource == null) {
throw new RuntimeException("the dataSource can not be null");
}
Connection connection = connectionThreadLocal.get();
if (connection == null) {
try {
connectionThreadLocal.set(dataSource.getConnection());
connection = connectionThreadLocal.get();
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
}
由上面的实现逻辑可以看出我们声明式事务的实现逻辑:就是我们将一个线程内所有的数据库操作使用一个数据库连接,来保证我们的事务一致性。
那么,事务管理器定义好了,我们怎么使用它来管理事务呢?其实很简单,就是利用我们上述的AOP思想,将我们使用@Transactional注解标识的类做一个代理,使用代理类来将我们方法执行前后加上线程共享数据库连接的创建和销毁,就实现了声明式事务的需求。
我们的实现逻辑如下:
/**
* 事务包装器
* Created by rubin on 4/4/21.
*/
public class TransactionalBeanPostProcessor implements BeanPostProcessor {
private AutowiredBeanFactory autowiredBeanFactory;
/**
* 将需要事务处理的bean封装
*
* @param beanFactory
* @param beanName
* @param beanObject
* @return
*/
@Override
public Object postProcessBeforeRegister(BeanFactory beanFactory, String beanName, Object beanObject) {
if (checkNeedTransaction(beanObject)) {
Class<?>[] interfaces = beanObject.getClass().getInterfaces();
TransactionManager transactionManager = (TransactionManager) beanFactory.getBean(TransactionManager.class);
if (interfaces != null && interfaces.length > 0) {
beanObject = ProxyUtil.getJdkProxy(beanObject, new TransactionInvocationHandler(transactionManager, beanObject.getClass(), beanObject));
} else {
beanObject = ProxyUtil.getCglibProxy(beanObject, new TransactionMethodInterceptor(transactionManager, beanObject.getClass(), beanObject));
}
}
return beanObject;
}
/**
* 校验该bean是否需要事务
*
* @param bean
* @return
*/
private boolean checkNeedTransaction(Object bean) {
Class clazz = bean.getClass();
// 此处会将继承的注解也扫描出来,所以不用区分是不是接口
if (AnnotationUtil.hasAnnotation(clazz, Transactional.class)) {
return true;
}
if (!clazz.isInterface()) {
if (checkMethodNeedTransaction(clazz.getDeclaredMethods())) {
return true;
}
}
return false;
}
/**
* 校验是否有方法需要事务处理
*
* @param declaredMethods
* @return
*/
private boolean checkMethodNeedTransaction(Method[] declaredMethods) {
for (int i = 0; i < declaredMethods.length; i++) {
if (AnnotationUtil.hasAnnotation(declaredMethods[i], Transactional.class)) {
return true;
}
}
return false;
}
/**
* 执行顺序 升序排列执行
*
* @return
*/
@Override
public Integer getOrder() {
return 0;
}
}
我们自定义了bean初始化的后置处理器,将我们定义的带有@Transactional注解的bean实体做了一个代理类替换掉原对象。这样,就使用面向切面的思想实现了声明式事务的处理。
我们的代理逻辑有以下两种,分别对应实现了接口的对象(JDK动态代理)和没实现接口的对象(CGLIB代理)的代理:
/**
* 事务支持代理逻辑类
* Created by rubin on 2021/3/25.
*/
@AllArgsConstructor
public class TransactionMethodInterceptor implements MethodInterceptor, CGlibProxyMethodInterceptor {
/**
* 事务管理器
*/
private TransactionManager transactionManager;
/**
* 代理类的类类型
*/
private Class<?> targetClass;
/**
* 代理目标类实体
*/
private Object target;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (needTransaction(method.getName())) {
Object result;
try {
transactionManager.start();
result = method.invoke(target, objects);
transactionManager.commit();
} catch (Exception e) {
transactionManager.rollback();
throw e;
}
return result;
}
return method.invoke(target, objects);
}
/**
* 校验是否需要事务处理
* 进入代理方法的 都是标记支持事务的
* 重点查看是不是方法和类以及接口均没有注解的情况
*
* @param methodName
* @return
*/
private boolean needTransaction(String methodName) {
Method[] declaredMethods = targetClass.getDeclaredMethods();
Method proxyMethod = null;
for (int i = 0; i < declaredMethods.length; i++) {
if (methodName.equalsIgnoreCase(declaredMethods[i].getName())) {
proxyMethod = declaredMethods[i];
break;
}
}
if (proxyMethod == null) {
throw new RuntimeException("the method" + methodName + " can not find in " + targetClass.getSimpleName());
}
boolean result = AnnotationUtil.hasAnnotation(targetClass, Transactional.class) || AnnotationUtil.hasAnnotation(proxyMethod, Transactional.class);
return result;
}
/**
* 获取代理目标类的类类型
*
* @return
*/
@Override
public Class getTargetClass() {
return targetClass;
}
}
/**
* 事务支持代理逻辑类
*/
@AllArgsConstructor
public class TransactionInvocationHandler implements InvocationHandler, JdkProxyHandler {
/**
* 事务管理器
*/
private TransactionManager transactionManager;
/**
* 代理类的类类型
*/
private Class<?> targetClass;
/**
* 代理目标类实体
*/
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (needTransaction(method.getName())) {
Object result;
try {
transactionManager.start();
result = method.invoke(target, args);
transactionManager.commit();
} catch (Exception e) {
transactionManager.rollback();
throw e;
}
return result;
}
return method.invoke(target, args);
}
/**
* 校验是否需要事务处理
* 进入代理方法的 都是标记支持事务的
* 重点查看是不是方法和类以及接口均没有注解的情况
*
* @param methodName
* @return
*/
private boolean needTransaction(String methodName) {
Method[] declaredMethods = targetClass.getDeclaredMethods();
Method proxyMethod = null;
for (int i = 0; i < declaredMethods.length; i++) {
if (methodName.equalsIgnoreCase(declaredMethods[i].getName())) {
proxyMethod = declaredMethods[i];
break;
}
}
if (proxyMethod == null) {
throw new RuntimeException("the method" + methodName + " can not find in " + targetClass.getSimpleName());
}
boolean result = AnnotationUtil.hasAnnotation(targetClass, Transactional.class) || AnnotationUtil.hasAnnotation(proxyMethod, Transactional.class);
return result;
}
/**
* 获取代理的目标类
*
* @return
*/
@Override
public Class<?> getTargetClass() {
return targetClass;
}
}
至此,我们的声明式事务就完成了。
测试
我们来测试一下我们的框架,首先创建我们的测试子模块,maven坐标如下:
<?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">
<parent>
<artifactId>my-spring-demo</artifactId>
<groupId>com.rubin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>my-spring和my-batis集成项目客户端模块</description>
<artifactId>my-spring-mybatis-client</artifactId>
<dependencies>
<dependency>
<groupId>com.rubin</groupId>
<artifactId>my-spring-mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
数据库初始化脚本如下:
create schema `transfer-demo` collate utf8_general_ci;
use `transfer-demo`;
create table account_info
(
id int auto_increment comment '主键'
primary key,
username varchar(255) default '' not null comment '用户名称',
card_no varchar(255) default '' not null comment '银行卡号',
money decimal(10,2) default 0.00 not null comment '余额'
)
comment '账户信息表';
insert into account_info (username, card_no, money) values ('张三', 'NO123456789', 10000.00), ('李四', 'NO987654321', 10000.00);
实体定义:
/**
* 帐户信息实体
*/
@Data
@Accessors(chain = true)
public class AccountInfo implements Serializable {
private static final long serialVersionUID = -4486359278073035660L;
/**
* 主键
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 卡号
*/
private String cardNo;
/**
* 账户余额
*/
private BigDecimal money;
}
dao接口和Mapper文件:
/**
* dao层接口
* 必须有mapper注解 否则解析不到
*/
@Mapper
public interface IAccountInfoDao {
/**
* 通过卡号查询帐户信息
*
* @param accountInfo
* @return
*/
AccountInfo selectByCardNo(AccountInfo accountInfo);
/**
* 更新帐户信息
*
* @param accountInfo
*/
void updateByCardNo(AccountInfo accountInfo);
}
<mapper namespace="com.rubin.client.dao.IAccountInfoDao">
<select id="selectByCardNo" resultType="com.rubin.client.pojo.AccountInfo" parameterType="com.rubin.client.pojo.AccountInfo">
SELECT
id,
username,
card_no AS cardNo,
money
FROM
account_info
WHERE
card_no = #{cardNo}
</select>
<update id="updateByCardNo" parameterType="com.rubin.client.pojo.AccountInfo">
UPDATE account_info SET money = #{money} WHERE card_no = #{cardNo}
</update>
</mapper>
IoC容器配置文件:
<beans>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///transfer-demo"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--配置数据库连接工厂-->
<bean id="connectionFactory" class="com.rubin.spring.mybatis.connection.ThreadConnectionFactory">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="com.rubin.spring.mybatis.manager.MybatisTransactionManager">
<property name="connectionFactory" ref="connectionFactory"></property>
</bean>
<!--配置Mybatis配置类-->
<bean id="configuration" class="com.rubin.mybatis.pojo.Configuration">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置sqlSessionFactory-->
<bean id="sqlSessionFactory" class="com.rubin.mybatis.sqlsession.DefaultSqlSessionFactory" init-method="init">
<property name="configuration" ref="configuration"></property>
<property name="connectionFactory" ref="connectionFactory"></property>
<property name="mapperBasePackage" value="mappers"></property>
</bean>
<bean id="mapperScanner" class="com.rubin.spring.mybatis.scanner.MapperScanner">
<property name="mapperLocation" value="com.rubin.client.dao"></property>
</bean>
<!--开启注解扫描 不配置注解注入将失效-->
<component-scan base-package="com.rubin.client"></component-scan>
</beans>
定义我们的service接口和实现类,这里类比较多,分别对应了将声明式事务加在接口上、类上和方法上的区别的测试实体:
接口:
/**
* 账户信息处理类接口
*/
public interface IAccountService {
/**
* 通过卡号获取账户详情
*
* @param cardNo
* @return
*/
AccountInfo getByCardNo(String cardNo);
/**
* 没有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
void transferWithoutTransaction(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException;
/**
* 有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
void transferWithTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException;
/**
* 有事务控制 正常执行
*
* @param fromCardNo
* @param toCardNo
* @param money
*/
void transferWithTransactionSuccess(String fromCardNo, String toCardNo, BigDecimal money);
}
/**
* 账户信息处理类接口
*/
@Transactional
public interface IAccountTransactionInterfaceService {
/**
* 有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
void transferWithTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException;
/**
* 有事务控制 正常执行
*
* @param fromCardNo
* @param toCardNo
* @param money
*/
void transferWithTransactionSuccess(String fromCardNo, String toCardNo, BigDecimal money);
}
/**
* 账户信息处理类接口
*/
public interface IAccountTransactionService {
/**
* 有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
void transferWithTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException;
/**
* 有事务控制 正常执行
*
* @param fromCardNo
* @param toCardNo
* @param money
*/
void transferWithTransactionSuccess(String fromCardNo, String toCardNo, BigDecimal money);
}
实现类:
/**
* cglib动态代理测试类
* 主要测试代理类方法级的事务控制
*/
@Service(value = "cglibAccountMethodService")
public class AccountMethodTransactionService {
@Autowired
private IAccountInfoDao iAccountInfoDao;
/**
* 无事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
public void transferWithoutTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
int i = 1 / 0;
iAccountInfoDao.updateByCardNo(to);
}
/**
* 有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
@Transactional
public void transferWithTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
int i = 1 / 0;
iAccountInfoDao.updateByCardNo(to);
}
/**
* 有事务控制 正常执行
*
* @param fromCardNo
* @param toCardNo
* @param money
*/
@Transactional
public void transferWithTransactionSuccess(String fromCardNo, String toCardNo, BigDecimal money) {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
iAccountInfoDao.updateByCardNo(to);
}
}
/**
* jdk动态代理测试类
* 主要测试方法级事务的控制
*/
@Service(value = "accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountInfoDao iAccountInfoDao;
/**
* 通过卡号获取账户详情
*
* @param cardNo
* @return
*/
@Override
public AccountInfo getByCardNo(String cardNo) {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(cardNo);
return iAccountInfoDao.selectByCardNo(accountInfo);
}
/**
* 没有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
@Override
public void transferWithoutTransaction(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
int i = 1 / 0;
iAccountInfoDao.updateByCardNo(to);
}
/**
* 有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
@Transactional
@Override
public void transferWithTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
int i = 1 / 0;
iAccountInfoDao.updateByCardNo(to);
}
/**
* 有事务控制 正常执行
*
* @param fromCardNo
* @param toCardNo
* @param money
*/
@Transactional
@Override
public void transferWithTransactionSuccess(String fromCardNo, String toCardNo, BigDecimal money) {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
iAccountInfoDao.updateByCardNo(to);
}
}
/**
* jdk动态代理测试类
* 主要测试接口级事务的控制情况
*/
@Service
public class AccountTransactionInterfaceServiceImpl implements IAccountTransactionInterfaceService {
@Autowired
private IAccountInfoDao iAccountInfoDao;
/**
* 有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
@Override
public void transferWithTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
int i = 1 / 0;
iAccountInfoDao.updateByCardNo(to);
}
/**
* 有事务控制 正常执行
*
* @param fromCardNo
* @param toCardNo
* @param money
*/
@Override
public void transferWithTransactionSuccess(String fromCardNo, String toCardNo, BigDecimal money) {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
iAccountInfoDao.updateByCardNo(to);
}
}
/**
* cglib动态代理测试类
* 主要测试类级事务的控制情况
*/
@Service(value = "cglibAccountService")
@Transactional
public class AccountTransactionService {
@Autowired
private IAccountInfoDao iAccountInfoDao;
/**
* 有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
public void transferWithTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
int i = 1 / 0;
iAccountInfoDao.updateByCardNo(to);
}
/**
* 有事务控制 正常执行
*
* @param fromCardNo
* @param toCardNo
* @param money
*/
public void transferWithTransactionSuccess(String fromCardNo, String toCardNo, BigDecimal money) {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
iAccountInfoDao.updateByCardNo(to);
}
}
/**
* jdk动态代理测试类
* 主要测试类级事务的控制情况
*/
@Transactional
@Service
public class AccountTransactionServiceImpl implements IAccountTransactionService {
@Autowired
private IAccountInfoDao iAccountInfoDao;
/**
* 有事务控制 并有异常抛出
*
* @param fromCardNo
* @param toCardNo
* @param money
* @throws UndeclaredThrowableException
*/
@Override
public void transferWithTransactionRollback(String fromCardNo, String toCardNo, BigDecimal money) throws UndeclaredThrowableException {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
int i = 1 / 0;
iAccountInfoDao.updateByCardNo(to);
}
/**
* 有事务控制 正常执行
*
* @param fromCardNo
* @param toCardNo
* @param money
*/
@Override
public void transferWithTransactionSuccess(String fromCardNo, String toCardNo, BigDecimal money) {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo(fromCardNo);
AccountInfo from = iAccountInfoDao.selectByCardNo(accountInfo);
accountInfo.setCardNo(toCardNo);
AccountInfo to = iAccountInfoDao.selectByCardNo(accountInfo);
from.setMoney(from.getMoney().subtract(money));
to.setMoney(to.getMoney().add(money));
iAccountInfoDao.updateByCardNo(from);
iAccountInfoDao.updateByCardNo(to);
}
}
最后,定义我们的测试类:
/**
* 客户端测试类
*/
public class MyBatisSpringTest {
ApplicationContext applicationContext;
@Before
public void before() {
applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
}
/**
* 测试容器初始化成功
*/
@Test
public void applicationContextInitSuccessTest() {
Assert.assertNotNull(applicationContext);
Assert.assertNotNull(applicationContext.getBean("dataSource"));
Assert.assertNotNull(applicationContext.getBean("connectionFactory"));
}
/**
* 测试注入mapper成功 并可以正常调用其方法
*/
@Test
public void daoSelectTest() {
IAccountInfoDao iAccountInfoDao = (IAccountInfoDao) applicationContext.getBean(IAccountInfoDao.class);
AccountInfo accountInfo = new AccountInfo();
accountInfo.setCardNo("NO123456789");
Assert.assertNotNull(iAccountInfoDao.selectByCardNo(accountInfo));
}
/**
* 测试service注入成功 并可以正常调用其方法
*/
@Test
public void serviceSelectTest() {
IAccountService iAccountService = (IAccountService) applicationContext.getBean("accountService");
Assert.assertNotNull(iAccountService.getByCardNo("NO123456789"));
iAccountService = (IAccountService) applicationContext.getBean(IAccountService.class);
Assert.assertNotNull(iAccountService.getByCardNo("NO987654321"));
}
/**
* 测试未添加事务的方法导致数据错乱成功
*/
@Test(expected = UndeclaredThrowableException.class)
public void serviceMethodTransferWithoutTransactionTest() {
IAccountService iAccountService = (IAccountService) applicationContext.getBean("accountService");
iAccountService.transferWithoutTransaction("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试添加事务的方法事务回滚成功
*/
@Test(expected = UndeclaredThrowableException.class)
public void serviceMethodTransferWithTransactionRollbackTest() {
IAccountService iAccountService = (IAccountService) applicationContext.getBean("accountService");
iAccountService.transferWithTransactionRollback("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试添加事务的方法事务正常提交成功
*/
@Test
public void serviceMethodTransferWithTransactionSuccessTest() {
IAccountService iAccountService = (IAccountService) applicationContext.getBean("accountService");
iAccountService.transferWithTransactionSuccess("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试添加事务的类事务回滚成功
*/
@Test(expected = UndeclaredThrowableException.class)
public void serviceClassTransferWithTransactionRollbackTest() {
IAccountTransactionService iAccountTransactionService = (IAccountTransactionService) applicationContext.getBean(IAccountTransactionService.class);
iAccountTransactionService.transferWithTransactionRollback("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试添加事务的类事务正常提交成功
*/
@Test
public void serviceClassTransferWithTransactionSuccessTest() {
IAccountTransactionService iAccountTransactionService = (IAccountTransactionService) applicationContext.getBean(IAccountTransactionService.class);
iAccountTransactionService.transferWithTransactionSuccess("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试添加事务的接口事务回滚成功
*/
@Test(expected = UndeclaredThrowableException.class)
public void serviceInterfaceTransferWithTransactionRollbackTest() {
IAccountTransactionInterfaceService iAccountTransactionInterfaceService = (IAccountTransactionInterfaceService) applicationContext.getBean(IAccountTransactionInterfaceService.class);
iAccountTransactionInterfaceService.transferWithTransactionRollback("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试添加事务的接口事务正常提交成功
*/
@Test
public void serviceInterfaceTransferWithTransactionSuccessTest() {
IAccountTransactionInterfaceService iAccountTransactionInterfaceService = (IAccountTransactionInterfaceService) applicationContext.getBean(IAccountTransactionInterfaceService.class);
iAccountTransactionInterfaceService.transferWithTransactionSuccess("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试CGLIB代理成功
*/
@Test
public void cglibProxySuccessTest() {
AccountTransactionService accountTransactionService = (AccountTransactionService) applicationContext.getBean(AccountTransactionService.class);
Assert.assertNotNull(accountTransactionService);
}
/**
* 测试CGLIB代理类事务回滚成功
*/
@Test(expected = InvocationTargetException.class)
public void serviceCglibTransferWithTransactionRollbackTest() {
AccountTransactionService accountTransactionService = (AccountTransactionService) applicationContext.getBean(AccountTransactionService.class);
accountTransactionService.transferWithTransactionRollback("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试CGLIB代理类事务正常提交成功
*/
@Test
public void serviceCglibTransferWithTransactionSuccessTest() {
AccountTransactionService accountTransactionService = (AccountTransactionService) applicationContext.getBean(AccountTransactionService.class);
accountTransactionService.transferWithTransactionSuccess("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试CGLIB代理类未添加事务方法导致数据错乱
*/
@Test(expected = InvocationTargetException.class)
public void serviceCglibTransferMethodWithoutTransactionRollbackTest() {
AccountMethodTransactionService accountMethodTransactionService = (AccountMethodTransactionService) applicationContext.getBean(AccountMethodTransactionService.class);
accountMethodTransactionService.transferWithoutTransactionRollback("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试CGLIB代理类添加事务方法回滚成功
*/
@Test(expected = InvocationTargetException.class)
public void serviceCglibTransferMethodWithTransactionRollbackTest() {
AccountMethodTransactionService accountMethodTransactionService = (AccountMethodTransactionService) applicationContext.getBean(AccountMethodTransactionService.class);
accountMethodTransactionService.transferWithTransactionRollback("NO123456789", "NO987654321", new BigDecimal(100.00));
}
/**
* 测试CGLIB代理类添加事务方法正常提交成功
*/
@Test
public void serviceCglibTransferMethodWithTransactionSuccessTest() {
AccountMethodTransactionService accountMethodTransactionService = (AccountMethodTransactionService) applicationContext.getBean(AccountMethodTransactionService.class);
accountMethodTransactionService.transferWithTransactionSuccess("NO123456789", "NO987654321", new BigDecimal(100.00));
}
}
测试全部通过,说明我们的框架定义的没有问题。
优化项
本框架写到这里基本的功能是可以实现了,但是笔者总结了一些优化项,感兴趣的小伙伴可以试着自己实现一下。
优化项:
- 我们可以扩展一个FactoryBean的接口,来允许用户自定义bean的装配逻辑。
附录
源代码下载链接:my-spring-demo
文章评论