一、事务控制

事务我们在java web中有涉及。Spring中 我们使用了 connection 对象的 setAutoCommit(true)此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条 sql语句,这种方式就无法实现功能了。在这里插入图片描述

解决办法:
让业务层来控制事务的提交和回滚。(这个我们之前已经在 web 阶段讲过了)
改造后的业务层实现类:
用 注:此处没有使用 spring 的 的IOC.

/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {

private IAccountDao accountDao = new AccountDaoImpl();

@Override
public List<Account> findAllAccount() {
List<Account> accounts = null;
try {
TransactionManager.beginTransaction();
accounts = accountDao.findAll();
TransactionManager.commit();
return accounts;
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
}finally {
TransactionManager.release();
}
return null;
}
......
}

下面来看一下这两个工具类 ConnectionUtils 和 TransactionManager

1.ConnectionUtils

连接的工具类,它用于从数据源中获取一个连接,并且实现和线程绑定,所以之后我们获取当前线程的连接便可以直接connectionUtils.getThreadConnection()

/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程绑定
*/
public class ConnectionUtils {

private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

private DataSource dataSource;

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {

try {
//1.先从ThreadLocal上获取
Connection connection = tl.get();
//2.判断当前线程上是否有连接
if (connection == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
connection = dataSource.getConnection();
tl.set(connection);
}
//4.返回当前线程上的连接
return connection;
}catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 把链接和线程解绑
*/
public void removeConnection() {
tl.remove();
}
}

2.TransactionManager

和事务管理相关的工具类,它包含了:开启事务,提交事务,回滚事务和释放连接(总管理)

例如:connectionUtils.getThreadConnection().setAutoCommit(false); 就等价于connection.setAutoCommit(false);

**
* 和事务管理相关的工具类,它包含了:开启事务,提交事务,回滚事务和释放连接
* @author Mango
*/
public class TransactionManager {

private ConnectionUtils connectionUtils;

public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}

/**
* 开启事务
*/
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 提交事务
*/
public void commit() {
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 回滚事务
*/
public void rollback() {
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 释放连接
*/
public void release() {
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();//连接与线程解绑
} catch (SQLException e) {
e.printStackTrace();
}
}
}

3.新的问题

上一小节的代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一
个新的问题:

@Override
public List<Account> findAllAccount() {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
List<Account> accounts = accountDao.findAllAccount();
//3.提交事务
txManager.commit();
//4.返回结果
return accounts;
}catch (Exception e){
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
txManager.release();
}

}

@Override
public Account findAccountById(Integer accountId) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
Account account = accountDao.findAccountById(accountId);
//3.提交事务
txManager.commit();
//4.返回结果
return account;
}catch (Exception e){
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
txManager.release();
}
}

业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了

试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。

二、动态代理回顾

在这里插入图片描述
1、 动态代理的特点

(1)字节码随用随创建,随用随加载。
(2)它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
(3)装饰者模式就是静态代理的一种体现。

2.动态代理的作用

**不修改源码的基础上对方法增强**

3.动态代理的分类:

(1)基于接口的动态代理

(1)基于接口的动态代理:
涉及的类:Proxy
提供者:JDK官方

如何创建代理对象:
使用proxy类中的newProxyInstance方法(要求:被代理的类至少实现其一个接口(例如IProducer,而不能用Producer来实现),如果没有就不能使用)

newProxyInstance方法
参数:1.ClassLoader:类加载器
   它是用于加载 代理对象字节码的。被代理对象的类加载器(固定写法)。

   2Interfaces:字节码数组
   它是用于让代理对象和被代理对象有相同的方法,实现相同的接口。(固定写法)

   3.InvocationHandler:用于提供增强的代码
   它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须
   此接口的实现类都是谁用谁写。
/**
* 模拟一个service
*/
public class Client {
public static void main(String[] args) {

final Producer producer = new Producer();//必须是最终的

IProducer proxyProducer =(IProducer) Proxy.newProxyInstance(
参数(1)producer.getClass().getClassLoader(),
参数(2)producer.getClass().getInterfaces(),
参数(3new InvocationHandler() {

/**
* 作用:执行被代理对象的任何接口的方法都会经过该方法
* 方法参数的含义:
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强方法的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float) args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;//方法执行一遍便有相同返回值
}
});

proxyProducer.saleProduct(10000f);
}
}

(2)基于子类的动态代理

基于子类的动态代理

提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。需要导入jar包
要求:被代理类不能用 final 修饰的类(最终类)。

使用 CGLib 的 的 Enhancer 类创建代理对象

(2)基于子类的动态代理:
涉及的类:Enhancer
提供者:第三方cglib库

如何创建代理对象:
   使用Enhancer中的create方法(要求:被代理的类不能是最终类)

create中的参数:
   1.class:字节码
       用于指定被代理对象的字节码(这里Producer被代理)
   2.callback:用于提供增强的代码
       一般写的都是该接口的子接口实现类:MenthInterceptor
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {

final Producer producer = new Producer();

Producer cglibProducer =(Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
*
* @param o
* @param method
* @param objects
以上三个参数和基于接口的动态代理中的invoke方法的参数时一样的

* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//提供增强方法的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float) objects[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;//方法执行一遍便有相同返回值
}
});
cglibProducer.saleProduct(10000f);
}
}

三、动态代理实现事务控制

1.用于创建service代理对象的factory

解决案例中的问题:
创建客户业务层对象工厂(当然也可以创建其他业务层对象,只不过我们此处不做那么繁琐)

/**
* 用于创建service代理对象的factory
* @author Mango
*/
public class BeanFactory {

private IAccountService accountService;

public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}

private TransactionManager transactionManager;

public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}

public IAccountService getAccountService() {
return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
try {
//1.开启事务
transactionManager.beginTransaction();
//2.执行操作
returnValue = method.invoke(accountService, args);
//3.提交事务
transactionManager.commit();
//4.返回结果
return returnValue;
} catch (Exception e) {
//5.回滚
transactionManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
transactionManager.release();
}
}
});
}
}

2.配置依赖注入

<!--配置代理的service对象-->
<bean id="proxyAccountService" factory-bean="beanfactory" factory-method="getAccountService"></bean>

<!--配置beanfactory-->
<bean id="beanfactory" class="com.itheima.factory.BeanFactory">
<!--注入service-->
<property name="accountService" ref="accountService"></property>
<!--注入事务管理器-->
<property name="transactionManager" ref="transactionManger"></property>
</bean>

3.service层与测试类

当我们改造完成之后,业务层用于控制事务的重复代码就都可以删掉了。

/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService{

private IAccountDao accountDao;


public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public void transfer(String souceName, String targetName, Float money) {
System.out.println("transfer......");
//2.执行操作
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(souceName);
//2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney() - money);
//4.转入账户加钱
target.setMoney(target.getMoney() + money);
//5.更新转出账户
accountDao.updateAccount(source);
//6.更新转入账户
accountDao.updateAccount(target);
}
}

所以测试类中:

/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

@Autowired
@Qualifier("proxyAccountService")//使用BeanFactory中的代理AccountService来执行
private IAccountService as;

@Test
public void testTransfer() {
as.transfer("aaa","bbb", 100f);
}
}