一 转账案例
需求
使用spring框架整合DBUtils技术,实现用户转账功能
1.1 基础功能
步骤分析
- 创建java项目,导入坐标
- 编写Account实体类
- 编写AccountDao接口和实现类
- 编写AccountService接口和实现类
- 编写spring核心配置文件
- 编写测试代码
转账案例
1 )创建java项目,导入坐标
<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>
</dependencies>
2 ) 编写Account实体类
public class Account {
private Integer id;
private String name;
private Double money;
3 )编写AccountDao接口和实现类
public interface AccountDao {
// 转出操作
public void out(String outUser, Double money);
// 转入操作
public void in(String inUser, Double money);
}
@Repository("accountDao") //生成该类实例存到ioc容器
public class AccountDaoImpl implements AccountDao {
@Autowired //注入queryRunner对象到ioc容器
private QueryRunner queryRunner;
public void out(String outUser, Double money) {
String sql = "update account set money = money - ? where name = ?";
try {
queryRunner.update(sql,money,outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void in(String inUser, Double money) {
String sql = "update account set money = money + ? where name = ?";
try {
queryRunner.update(sql,money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4 )编写AccountService接口和实现类
public interface AccountService {
//转账方法
public void transfer(String outUser,String inUser,Double money);
@Service("accountService")
public class AccountServiceImpl implements AccountService {
//需要用到dao层的对象,所以就需要注入dao对象
@Autowired
private AccountDao accountDao;
/*转账方法*/
public void transfer(String outUser, String inUser, Double money) {
accountDao.in(inUser,money);
accountDao.out(outUser,money);
}
}
5 )编写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.lagou"/>
<!--加载jdbc配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置dataSource-->
<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>
</beans>
6 )编写测试代码
package com.lagou.test;
import com.lagou.service.AccountService;
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;
/*需要加载配置文件*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("tom","jerry",100.0);
}
}
7 )问题分析
上面的代码事务在dao层,转出转入操作都是一个独立的事务,但实际开发,应该把业务逻辑控制在一个事务中,所以应该将事务挪到service层。
3.2 传统事务
步骤分析
- 编写线程绑定工具类
- 编写事务管理器
- 修改service层代码
- 修改dao层代码
1 )编写线程绑定工具类
/**
* 线程绑定工具类,从dataSource中获取一个connection,并把connection与线程进行绑定
* */
@Component //创建ConnectionUtils的实例存入Ioc容器
public class ConnectionUtils {
@Autowired
private DataSource dataSource;
/*ThreadLocal: 线程内部的存储类,可以在指定的线程内部存储数据 以key-value形式存储
* key---> ThreadLocal(就是当前线程)
* value---> 任意类型的值*/
private ThreadLocal<Connection> threadLocal;
/*获取当前线程的连接,如果获取到的为空,那么就要从数据源中获取,放到ThreadLocal中,即当前线程*/
public Connection getThreadConnection(){
// 1.先从线程上ThreadLocal获取
Connection connection = threadLocal.get();
// 2. 判断con连接是否为空,为空就从数据源获取
if (connection == null){
//从数据源获取
try {
connection = dataSource.getConnection();
// 把con连接放入ThreadLocal中
threadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
/*解除当前线程的连接绑定*/
public void removeThreadConnection(){
threadLocal.remove();
}
}
2 )编写事务管理器
/**
* 事务管理器工具类: 开启事务,提交事务,回滚事务,释放资源
* */
@Component //存入Ioc容器方便使用其实例调用相应方法
public class TransactionManager {
//调用ConnectionUtils的方法,所以需要用到ConnectionUtils的实例
@Autowired
private ConnectionUtils connectionUtils;
/*开启事务*/
public void beginTransaction(){
Connection threadConnection = connectionUtils.getThreadConnection();
try {
threadConnection.setAutoCommit(false);//改成手动提交事务
} catch (SQLException e) {
e.printStackTrace();
}
}
/*提交事务*/
public void commitTransaction(){
Connection threadConnection = connectionUtils.getThreadConnection();
try {
threadConnection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/*回滚事务*/
public void rollbackTransaction(){
Connection threadConnection = connectionUtils.getThreadConnection();
try {
threadConnection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/*释放资源*/
public void closeTransaction(){
Connection threadConnection = connectionUtils.getThreadConnection();
try {
//改成自动提交
threadConnection.setAutoCommit(true);
//关闭连接
threadConnection.close();
//解除线程绑定
connectionUtils.removeThreadConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3 )修改service层代码
@Service("accountService")
public class AccountServiceImpl implements AccountService {
//需要用到dao层的对象,所以就需要注入dao对象
@Autowired
private AccountDao accountDao;
// 需要用到TransactionManager对象
@Autowired
private TransactionManager transactionManager;
/*转账方法*/
public void transfer(String outUser, String inUser, Double money) {
try {
// 1.开启事务
transactionManager.beginTransaction();
// 2.操作事务
accountDao.in(inUser,money);
accountDao.out(outUser,money);
// 3.提交事务
transactionManager.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
// 4.回滚事务
transactionManager.rollbackTransaction();
} finally {
// 5.释放资源
transactionManager.closeTransaction();
}
}
}
4 )修改dao层代码
@Repository("accountDao") //生成该类实例存到ioc容器
public class AccountDaoImpl implements AccountDao {
@Autowired //注入queryRunner对象到ioc容器
private QueryRunner queryRunner;
@Autowired
private ConnectionUtils connectionUtils;
public void out(String outUser, Double money) {
String sql = "update account set money = money - ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(),sql,money,outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void in(String inUser, Double money) {
String sql = "update account set money = money + ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(),sql,money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5 )问题分析
上面代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了
一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了,违背了面向对象的开发思想。
二 、Proxy优化转账案例
我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强。这样就不会对业务层产生影响,解决了耦合性的问题啦!
常用的动态代理技术
JDK 代理 : 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,从而实现方法增强
CGLIB代理:基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强
2.1 JDK动态代理方式
Jdk工厂类
采用jdk动态代理技术生成目标类的代理对象
* newProxyInstance()方法的参数:
**ClassLoader loader** 类加载器,借助被代理对象获取类加载器
* **Class<?> interface** 被代理类所需要实现的全部接口
* **InvocationHandler h** 当代理对象调用接口的任意方法时,都会执行InvocationHandler的invoke()方法
***实现事务与业务代码的解耦,把事务代码放到代理对象的invoke()方法中,接口方法中只保留业务代码***
@Component
public class JdkProxyFactory {
@Autowired
private AccountService accountService;//被代理对象
@Autowired
private TransactionManager transactionManager;//管理事务对象
/*采用jdk动态代理技术生成目标类的代理对象
* newProxyInstance()方法的参数:ClassLoader loader 类加载器,借助被代理对象获取类加载器
* Class<?> interface 被代理类所需要实现的全部接口
* InvocationHandler h 当代理对象调用接口的任意方法时,都会执行InvocationHandler的invoke()方法 */
public AccountService createAccountServiceJdkProxy(){
AccountService accountServiceProxy =(AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
//第三个参数,需要InvocationHandler的实现类,这里使用了匿名内部类,并重写了invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*参数:proxy : 当前的代理对象引用
* method:被调用的目标方法的引用
* args: 被调用目标方法的参数*/
//会执行被代理类也就是accountService里面的方法
/*实现类事务与业务的分离,解耦*/
Object result = null;
try {
//**在执行接口的方法之前**,开启事务
transactionManager.beginTransaction();
//**执行事务操作**
result = method.invoke(accountService, args);
//**在执行接口的方法之后,**提交事务
transactionManager.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
transactionManager.rollbackTransaction();
} finally {
//释放资源
transactionManager.closeTransaction();
}
return result;
}
});
return accountServiceProxy;
}
}
接口代码
@Service("accountService")
public class AccountServiceImpl implements AccountService {
//需要用到dao层的对象,所以就需要注入dao对象
@Autowired
private AccountDao accountDao;
/* // 需要用到TransactionManager对象
@Autowired
private TransactionManager transactionManager;*/
/*转账方法*/
public void transfer(String outUser, String inUser, Double money) {
/* // 1.开启事务
transactionManager.beginTransaction();*/
// 2.操作事务
accountDao.in(inUser,money);
//int i = 10/0;
accountDao.out(outUser,money);
/*// 3.提交事务
transactionManager.commitTransaction();
// 4.回滚事务
transactionManager.rollbackTransaction();
// 5.释放资源
transactionManager.closeTransaction();*/
}
}
测试代码
@Test
public void testTransferProxy(){
//当前返回的时AccountService的代理对象Proxy
AccountService accountServiceJdkProxy = jdkProxyFactory.createAccountServiceJdkProxy();
//当代理对象调用被代理对象实现的接口的任意方法时,都会执行invoke()方法
accountServiceJdkProxy.transfer("tom","jerry",200.0);
}
2.2 CGLIB动态代理方式
Cglib工厂类
/**
* 该类使用cglib动态代理的方式,对目标类AccountServiceImpl进行方法的transfer的动态增强
*/
@Component
public class CglibProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceCglibProxy(){
//create()方法的参数
//参数1:目标类的字节码对象
//参数2:动作类,当代理对象调用目标对象的原方法时,都会调用代理对象的intercept()方法
AccountService accountServiceProxy =(AccountService) Enhancer.create(accountService.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//o 代表生成的代理对象,method 代表目标方法的引用, objects 方法入参 methodProxy 方法代理
try {
//开启事务
transactionManager.beginTransaction();
//执行事务操作
method.invoke(accountService,objects);
//提交事务
transactionManager.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
//回滚
transactionManager.rollbackTransaction();
} finally {
//释放资源
transactionManager.closeTransaction();
}
return null;
}
});
return accountServiceProxy;
}
}
测试代码
@Test
public void testTransferProxyCglib(){
//当前返回的时AccountService的代理对象Proxy
AccountService accountServiceCglibProxy = cglibProxyFactory.createAccountServiceCglibProxy();
//当代理对象调用被代理对象实现的接口的任意方法时,都会执行intercept方法
accountServiceCglibProxy.transfer("tom","jerry",100.0);
}