1. 转账案例
1.1 数据库准备
CREATE TABLE `account` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`money` double(255,0) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `account` VALUES (1, 'zhangsan', 1000);
INSERT INTO `account` VALUES (2, 'lisi', 1000);
复制代码
1.2 项目搭建
1.2.1 pom.xml
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.15</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
复制代码
1.2.2 applicationContext.xml
<!--开启组件扫描-->
<context:component-scan base-package="com.lagou"/>
<!--加载jdbc配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--把数据库连接池交给IOC容器-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--把QueryRunner交给IOC容器-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
复制代码
1.2.3 实体类
@Data
public class Account {
private Integer id;
private String name;
private Double money;
}
复制代码
1.2.4 dao层
(1)接口
/**
* @author 振帅
* Created on 2021/06/22 15:17
*/
public interface AccountDao {
//转出
public void out(String outUser, Double money);
//转入
public void in(String inUser, Double money);
}
复制代码
(2)实现
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
@Override
public void out(String outUser, Double money) {
try {
queryRunner.update("update account set money = money - ? where name = ?",money,outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void in(String inUser, Double money) {
try {
queryRunner.update("update account set money = money + ? where name = ?",money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
复制代码
1.2.5 service层
(1)接口
/**
* @author 振帅
* Created on 2021/06/22 17:27
*/
public interface AccountService {
public void transfer(String outUsername, String inUsername, Double money);
}
复制代码
(2)实现
/**
* @author 振帅
* Created on 2021/06/22 17:29
*/
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String outUsername, String inUsername, Double money) {
//转出
accountDao.out(outUsername,money);
//int i = 1 / 0;
//转入
accountDao.in(inUsername,money);
}
}
复制代码
1.2.6 测试
/**
* @author 振帅
* Created on 2021/06/22 17:26
*/
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("zhangsan", "lisi", 100d);
}
}
复制代码
1.2.7 问题分析
上面的代码事务在dao层,转出转入操作都是一个独立的事务,但实际开发,应该把业务逻辑控制在一个事务中,所以应该将事务挪到service层。
1.2.8 传统事务
(1)线程绑定工具类
业务逻辑控制在一个事务就需要两次操作都是同一个Connection
ThreadLocal
threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据
ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象
package com.lagou.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @Author 振帅
* @Date 2021/06/22 21:08
* 连接工具类,从数据源中获取一个连接,并将实现和线程的绑定
*/
@Component
public class ConnectionUtils {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
@Autowired
private DataSource dataSource;
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
// 先从ThreadLocal上获取
Connection connection = threadLocal.get();
if (connection == null) {
try {
//从数据源中获取一个连接,并存入到ThreadLocal中
connection = dataSource.getConnection();
threadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
/**
* 解除当前线程的连接绑定
*/
public void removeThreadConnection() {
threadLocal.remove();
}
}
复制代码
(2)事务管理器
事务管理器工具类,包含:开启事务、提交事务、回滚事务、释放资源
package com.lagou.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
/**
* @Author 振帅
* @Date 2021/06/22 21:30
* 事务管理器工具类,包含:开启事务、提交事务、回滚事务、释放资源
*/
@Component
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
/**
* 开启事务
*/
public void beginTransaction() {
try {
//关闭事务自动提交
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit() {
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback() {
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 释放资源
*/
public void release() {
try {
//改回自 动提交事务
connectionUtils.getThreadConnection().setAutoCommit(true);
//归还到连接池
connectionUtils.getThreadConnection().close();
//解除线程绑定
connectionUtils.removeThreadConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
复制代码
(3)修改dao层代码
注入事务管理器
/**
* @author 振帅
* Created on 2021/06/22 16:25
*/
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
@Autowired
ConnectionUtils connectionUtils;
@Override
public void out(String outUser, Double money) {
try {
queryRunner.update(connectionUtils.getThreadConnection(),"update account set money = money - ? where name = ?",money,outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void in(String inUser, Double money) {
try {
queryRunner.update(connectionUtils.getThreadConnection(),"update account set money = money + ? where name = ?",money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
复制代码
(4)修改service层代码
注入线程绑定工具类
/**
* @author 振帅
* Created on 2021/06/22 17:29
*/
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionManager transactionManager;
@Override
public void transfer(String outUsername, String inUsername, Double money) {
try {
// 1.开启事务
transactionManager.beginTransaction();
//2.业务操作
accountDao.out(outUsername,money);
//int i = 1 / 0;
accountDao.in(inUsername,money);
//3.提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//4.回滚事务
transactionManager.rollback();
} finally {
// 5.释放资源
transactionManager.release();
}
}
}
复制代码
1.2.9 问题分析
通过对业务层改造,已经可以实现事务控制,但是由于我们添加了事务控制,也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了,违背了面向对象的开发思想。
2. Proxy优化转账案例
2.1 什么是动态代理
- 动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
- 代理类在程序运行期间,创建的代理对象称之为动态代理对象。这种情况下,创建的代理对象,并不是事先在Java代码中定义好的。而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的。也就是说,你想获取哪个对象的代理,动态代理就会为你动态的生成这个对象的代理对象。动态代理可以对被代理对象的方法进行功能增强。有了动态代理的技术,那么就可以在不修改方法源码的情况下,增强被代理对象的方法的功能,在方法执行前后做任何你想做的事情。
我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强。这样
就不会对业务层产生影响,解决了耦合性的问题。
2.2 常用的动态代理技术
- JDK 代理 : 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成
一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,从而实现方法增强。
- CGLIB代理:基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是
final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行
增强。
2.3 JDK动态代理方式
- 提供者:JDK
- 基于接口的动态代理
- 使用JDK官方的Proxy类创建代理对象
- 代理的目标对象必须实现接口
Proxy.newProxyInstance(类加载器,对象的接口,处理器);
package com.lagou.utils;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Author 振帅
* @Date 2021/06/22 22:02
*/
@Component
public class JdkProxyFactory {
@Autowired
AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceJdkProxy(){
/**
* 创建对象的代理对象
* 参数一:类加载器
* 参数二:对象的接口
* 参数三:调用处理器,代理对象中的方法被调用,都会在执行方法。对所有被代理对象的方法进行拦截
*/
AccountService instance = (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 1.开启事务
transactionManager.beginTransaction();
System.out.println("before");
//2.业务操作
result = method.invoke(accountService, args);
System.out.println("after");
//3.提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//4.回滚事务
transactionManager.rollback();
} finally {
// 5.释放资源
transactionManager.release();
}
return result;
}
});
return instance;
}
}
复制代码
2.4 CGLIB动态代理方式
- 提供者:第三方 CGLib
- 基于父类的动态代理
- 使用CGLib的Enhancer类创建代理对象
- 被代理对象不能用final修饰
Enhancer.create(对象的字节码文件,方法的拦截器);
package com.lagou.utils;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @Author 振帅
* @Date 2021/06/22 22:20
*/
@Component
public class CGLBProxyFactory {
@Autowired
AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceCGLBProxy() {
/**
* 使用CGLib的Enhancer创建代理对象
* 参数一:对象的字节码文件
* 参数二:方法的拦截器
*/
AccountService o = (AccountService) Enhancer.create(accountService.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try {
// 1.开启事务
transactionManager.beginTransaction();
System.out.println("before");
//2.业务操作
result = method.invoke(accountService, objects);
System.out.println("after");
//3.提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//4.回滚事务
transactionManager.rollback();
} finally {
// 5.释放资源
transactionManager.release();
}
return result;
}
});
return o;
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END