public interface AccountDao {
//转出
void outUser(String outUser, Double money);
//转入
void inUser(String inUser, Double money);
}
@Repository
public class AccountDaoImpl implements AccountDao {
//依赖queryRunner
@Autowired
private QueryRunner queryRunner;
@Override
public void outUser(String outUser, Double money) {
String sql = "update account set money=money - ? where name = ?";
//执行sql
try {
queryRunner.update(sql, money, outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void inUser(String inUser, Double money) {
try {
// 1.编写sql
String sql = "update account set money = money + ? where name = ?";
// 2.执行sql
queryRunner.update(sql, money, inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public interface AccountService {
//转账
void transfer(String outUser, String inUser, Double money);
}
@Service
public class AccountServiceImpl implements AccountService {
//依赖注入accountDao
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String outUser, String inUser, Double money) {
//业务代码
//转出
accountDao.outUser(outUser, money);
//转入
accountDao.inUser(inUser, money);
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountTest {
@Autowired
private AccountService accountService;
//测试转账案例
@Test
public void test01() throws Exception {
accountService.transfer("tom", "jerry", 100d);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="cn.itcast"></context:component-scan>
<!--加载第三方配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--druid连接交给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>
</beans>
上述方法存在问题的解决方案:
将service方法的多个dao层代码,看做一个事务,要么都成功,要么都失败!!!!
@Service
public class AccountServiceImpl implements AccountService {
//依赖注入accountDao
@Autowired
private AccountDao accountDao;
//依赖dataSource
@Autowired
private DataSource dataSource;
@Override
public void transfer(String outUser, String inUser, Double money) {
Connection connection = null;
//获取conn手动控制事务
try {
connection = dataSource.getConnection();
connection.setAutoCommit(false);
//业务代码
//转出
accountDao.outUser(connection,outUser, money);
//机器故障
int i = 1 / 0;
//转入
accountDao.inUser(connection,inUser, money);
//提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//进行事务回滚
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
// 释放资源
try {
// 再改为自动提交
connection.setAutoCommit(true);
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
@Repository
public class AccountDaoImpl implements AccountDao {
//依赖queryRunner
@Autowired
private QueryRunner queryRunner;
@Override
public void outUser(Connection connection, String outUser, Double money) {
try {
String sql = "update account set money=money - ? where name = ?";
//执行sql(使用service连接对象)
queryRunner.update(connection, sql, money, outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void inUser(Connection connection,String inUser, Double money) {
try {
// 1.编写sql
String sql = "update account set money = money + ? where name = ?";
// 2.执行sql(使用service连接对象)
queryRunner.update(connection,sql, money, inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
问题:我们不应该将service的conn对象传递到dao层,这种方式,就产生了dao层与service的耦合性问题
ThreadLocal是一个线程的局部变量
@Component//交给ioc容器
public class ConnectionUtils {
@Autowired
private DataSource dataSource;
//储物柜,线程隔离
private static final ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
//从当前线程内绑定并获取conn对象
public Connection getThreadConnection() {
Connection connection = tl.get();
if (connection == null) {
try {
connection = dataSource.getConnection();
tl.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
//移出当前线程的conn对象
public void removeThreadConnection() {
tl.remove();
}
}
@Service
public class AccountServiceImpl implements AccountService {
//依赖注入accountDao
@Autowired
private AccountDao accountDao;
依赖dataSource
//@Autowired
//private DataSource dataSource;
@Autowired
private ConnectionUtils connectionUtils;
@Override
public void transfer(String outUser, String inUser, Double money) {
Connection connection = null;
//获取conn手动控制事务
try {
//connection = dataSource.getConnection();
connection = connectionUtils.getThreadConnection();
connection.setAutoCommit(false);
//业务代码
//转出
accountDao.outUser(outUser, money);
//机器故障
int i = 1 / 0;
//转入
accountDao.inUser(inUser, money);
//提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//进行事务回滚
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
// 释放资源
try {
// 再改为自动提交
connection.setAutoCommit(true);
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
@Repository
public class AccountDaoImpl implements AccountDao {
//依赖queryRunner
@Autowired
private QueryRunner queryRunner;
@Autowired
private ConnectionUtils connectionUtils;
@Override
public void outUser(String outUser, Double money) {
try {
String sql = "update account set money=money - ? where name = ?";
//获取当前线程内的conn
final Connection threadConnection = connectionUtils.getThreadConnection();
//执行sql(使用service连接对象)
queryRunner.update(threadConnection, sql, money, outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void inUser( String inUser, Double money) {
try {
// 1.编写sql
String sql = "update account set money = money + ? where name = ?";
// 获取当前线程内的 conn
Connection threadConnection = connectionUtils.getThreadConnection();
// 2.执行sql(使用service连接对象)
queryRunner.update(threadConnection, sql, money, inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
问题:在企业开发时,我们基础每一个业务层方法都需要进行事务的控制,这部分代码属于公共业务且重复的,出现了大量的代码冗余
这时候我们可以把这部分代码抽取到工具类(事务管理器)
@Component
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
//开启事务
// 1.开启事务
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().setAutoCommit(true);
//归还到连接池
connectionUtils.getThreadConnection().close();
//将connection对象从当前线程移出
connectionUtils.removeThreadConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Service
public class AccountServiceImpl implements AccountService {
//依赖注入accountDao
@Autowired
private AccountDao accountDao;
依赖dataSource
//@Autowired
//private DataSource dataSource;
//@Autowired
//private ConnectionUtils connectionUtils;
@Autowired
private TransactionManager transactionManager;
@Override
public void transfer(String outUser, String inUser, Double money) {
// Connection connection = null;
//获取conn手动控制事务
try {
//connection = dataSource.getConnection();
//connection = connectionUtils.getThreadConnection();
//connection.setAutoCommit(false);
//开启事务
transactionManager.beginTransaction();
//业务代码
//转出
accountDao.outUser(outUser, money);
//机器故障
int i = 1 / 0;
//转入
accountDao.inUser(inUser, money);
//提交事务
//connection.commit();
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//进行事务回滚
//connection.rollback();
transactionManager.rollback();
} finally {
transactionManager.release();
}
// 释放资源
//try {
// // 再改为自动提交
// connection.setAutoCommit(true);
//
// connection.close();
//} catch (SQLException e) {
// e.printStackTrace();
//}
}
}
}
@Repository
public class AccountDaoImpl implements AccountDao {
//依赖queryRunner
@Autowired
private QueryRunner queryRunner;
@Autowired
private ConnectionUtils connectionUtils;
@Override
public void outUser(String outUser, Double money) {
try {
String sql = "update account set money=money - ? where name = ?";
//获取当前线程内的conn
final Connection threadConnection = connectionUtils.getThreadConnection();
//执行sql(使用service连接对象)
queryRunner.update(threadConnection, sql, money, outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void inUser( String inUser, Double money) {
try {
// 1.编写sql
String sql = "update account set money = money + ? where name = ?";
// 获取当前线程内的 conn
Connection threadConnection = connectionUtils.getThreadConnection();
// 2.执行sql(使用service连接对象)
queryRunner.update(threadConnection, sql, money, inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
问题:几乎所有的方法都需要使用事务管理器,进行事务控制,且事务管理器属于通用业务,与我们核心业务代码产生耦合
我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强。这样就不会对业务层产生影响,解决了耦合性的问题啦!
常用的动态代理技术
JDK 代理 : 基于接口的动态代理技术
CGLIB代理:基于父类的动态代理技术
在企业进行开发时,只有核心业务成代码,不会出现事务相关代码,开发者只需要编写核心业务即可
public void transfer(String outUser, String inUser, Double money) {
// 业务代码-------- start
// 转出
accountDao.outUser(outUser, money);
// 模拟故障
int i = 1/0;
// 转入
accountDao.inUser(inUser, money);
// 业务代码-------- end
}
目标对象:AccountService
编写JdkProxyFactory
//基于JDK,实现对目标对象的事务增强
@Component
public class JdkProxyFactory {
@Autowired
private TransactionManager transactionManager;
public Object createJdkProxyTx(final Object target) {
Object proxy = null;
//使用SUN公司提供的jdk代理工具类
//目标对象类加载器
final ClassLoader classLoader = target.getClass().getClassLoader();
//目标对象接口数组
final Class<?>[] interfaces = target.getClass().getInterfaces();
//实现增强的业务逻辑(匿名内部类、Lambda)
proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
//invoke方法是代理对象的入口
//proxy:jdk工具类生成的代理对象
//method:当前用户执行的某个具体方法
//args:当前用户执行的某个具体方法传递的实际参数列表
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
//开启事务
transactionManager.beginTransaction();
//执行目标对象原有的功能
result = method.invoke(target, args);
//提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
transactionManager.rollback();
} finally { //释放资源
transactionManager.release();
}
return result;
}
});
//返回代理对象(增强后的)
return proxy;
}
}
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountTest {
@Autowired
private AccountService accountService;//目标对象
@Autowired
private JdkProxyFactory jdkProxyFactory;// jdk生产代理对象工厂
//测试转账案例
@Test
public void test01() throws Exception {
accountService.transfer("tom", "jerry", 100d);
}
@Test
public void test02() throws Exception {
//accountService.transfer("tom", "jerry", 100d);
//使用jdk对目标对象进行事务增强
final AccountService jdkProxyTx = (AccountService) jdkProxyFactory.createJdkProxyTx(accountService);
jdkProxyTx.transfer("tom", "jerry", 100d);
}
}
存在问题:进入公司,可能有些代码没有接口,但又需要动态代理进行增强,这时候sun公司提供jdk工具就无法实现了…
基于CGLIB技术,就可以对普通的java类型实现代理增强,需要导入jar包(Spring框架内置包含了此架包)
目标对象:AccountService
编写CglibProxyFactory
//基于JDK,实现对目标对象的事务增强
@Component
public class JdkProxyFactory {
@Autowired
private TransactionManager transactionManager;
public Object createJdkProxyTx(final Object target) {
Object proxy = null;
//使用SUN公司提供的jdk代理工具类
//目标对象类加载器
final ClassLoader classLoader = target.getClass().getClassLoader();
//目标对象接口数组
final Class<?>[] interfaces = target.getClass().getInterfaces();
//实现增强的业务逻辑(匿名内部类、Lambda)
proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
//invoke方法是代理对象的入口
//proxy:jdk工具类生成的代理对象
//method:当前用户执行的某个具体方法
//args:当前用户执行的某个具体方法传递的实际参数列表
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
//开启事务
transactionManager.beginTransaction();
//执行目标对象原有的功能
result = method.invoke(target, args);
//提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
transactionManager.rollback();
} finally { //释放资源
transactionManager.release();
}
return result;
}
});
//返回代理对象(增强后的)
return proxy;
}
}
@Test
public void test02() throws Exception {
//accountService.transfer("tom", "jerry", 100d);
//使用jdk对目标对象进行事务增强
//final AccountService jdkProxyTx = (AccountService) jdkProxyFactory.createJdkProxyTx(accountService);
//jdkProxyTx.transfer("tom", "jerry", 100d);
// 使用cglib对目标对象事务增强
AccountService cglibProxy = (AccountService) cglibProxyFactory.createCglibProxyTx(accountService);
cglibProxy.transfer("tom", "jerry", 100d);
}
}
AOP( 面向切面编程 )是一种思想, 它的目的就是在不修改源代码的基础上,对原有功能进行增强。
Spring AOP是对AOP思想的一种实现, Spring底层同时支持jdk和cglib动态代理。
这样做的好处是:
Spring会根据被代理的类是否有接口自动选择代理方式:
Target:目标对象
service层的核心业务(AccountServiceImpl)
JoinPoint:连接点
目标对象中的所有方法
transfer()
findAll()
Pointcut:切点
目标对象需要增强的方法
transfer()
Advice:通知(增强)
实现增强的功能的(TransactionManager)
beginTransaction()
commit()
rollback()
release()
Weaving:织入
将通知和切点进行织入(动作)
Aspect:切面(spring术语)
通知 + 切点 = 切面
Proxy:代理对象(底层实现)
通知 + 切点 = 代理对象
public interface AccountService {
void transfer();
void save();
}
//通知类(增强)
public class MyAdvice {
//在对目标对象方法执行之前进行增强
public void before(){
System.out.println("前置通知");
}
}
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
@Autowired
private AccountService accountService; // 配置了aop之后,就成为了代理对象
@Test
public void test01() throws Exception {
accountService.save();//保存了
accountService.transfer();//前置通知,转账了
}
}