使用spring框架整合DBUtils技术,实现用户转账功能
步骤
1. 创建java项目,导入坐标
2. 编写Account实体类
3. 编写AccountDao接口和实现类
4. 编写AccountService接口和实现类
5. 编写spring核心配置文件
6. 编写测试代码
1.导入依赖
2.编写Account实体类
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//Spring会完成注入到queryRunner中
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 {
@Autowired
private AccountDao accountDao;
/*
转账方法
*/
@Override
public void transfer(String outUser, String inUser, Double money) {
accountDao.out(outUser, money);
accountDao.in(inUser, money);
}
}
5.编写spring核心配置文件
使用了注解生成实例,所以不再需要用bean标签配置dao和service层的例题
但是其实这些注解还没有生效,想要生效必须在配置文件中开启注解扫描
<?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"/>
<!--引入properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置DataSource,把数据库连接池交给IOC容器-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
</beans>
base-package就是表明要对哪个包进行扫描
6.编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)//为了让测试在Spring容器环境下执行
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() throws Exception {
accountService.transfer("tom", "jerry", 100d);
}
}
@RunWith:用于指定junit运行环境,是junit提供给其他框架测试环境接口扩展,为了便于使用spring的依赖注入,spring提供了org.springframework.test.context.junit4.SpringJUnit4ClassRunner作为Junit测试环境
注意这里会有JDK版本问题!!!,改成11即可
7.问题分析
上面的代码事务在dao层,转出转入操作都是一个独立的事务,但实际开发,应该把业务逻辑控制在
一个事务中,所以应该将事务挪到service层
优化事务
应该让减钱和加钱处在同一事物中,一旦出错要进行回滚
我们应在Service层进行事务控制
步骤
- 编写线程绑定工具类
- 编写事务管理器
- 修改service层代码
- 修改dao层代码
1)编写线程绑定工具类
这个工具类是为了在dao层中用到的connection是同一个
/*
* 连接工具类,从数据源中获取一个连接,并将实现和线程的绑定\
* ThreadLocal:线程内部的存储类,可以在指定的县城内存储数据,可以理解为map中的key-value结构,
* key就是当前线程ThreadLocal value:任意类型的值
*/
@Component //生成该类的实例对象存到IOC容器中
public class ConnectionUtils {
@Autowired //让Spring根据类型把容器中的dataSource对象注入进来
private DataSource dataSource;
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();//这里value存的就是Connection
/*
获取当前线程上绑定的连接 如果获取到的连接为空 那么就要从数据源中获取连接并放到ThreadLocal中(绑定当前线程)
*/
public Connection getThreadConnection() {
//1.先从ThreadLocal上获取连接
Connection connection = threadLocal.get();
//2.判断当前线程中是否有Connection
if(null==connection){
// 3.从数据源中获取一个连接,并存入到ThreadLocal中
try {
//不为null
connection = (Connection) dataSource.getConnection();
threadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
/**
* 解除当前线程的连接绑定 */
public void removeThreadConnection() {
threadLocal.remove();
}
}
2)编写事务管理器
事务管理器工具类,包含:开启事务、提交事务、回滚事务、释放资源
//事务管理器工具类,包含:开启事务、提交事务、回滚事务、释放资源
@Component
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
/*
开启事务
*/
public void beginTransaction() {
//获取connection对象
Connection connection = connectionUtils.getThreadConnection();
try {
//开启了一个手动事务
connection.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();// 归还到连接池
connectionUtils.removeThreadConnection();// 解除线程绑定
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3修改service层代码
@Service("AccountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private TransactionManager transactionManager;
@Autowired
private AccountDao accountDao;
/*
转账方法
*/
@Override
public void transfer(String outUser, String inUser, Double money) {
//手动开启事务:调用事务管理器类中的开启事务方法
transactionManager.beginTransaction();
try {
accountDao.out(outUser, money);
accountDao.in(inUser, money);
//手动提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
transactionManager.rollback();
}finally {
//手动释放资源
transactionManager.release();
}
}
}
4.修改dao层代码
@Repository//生成该类实例存到IOC容器中
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;//g会完成注入到queryRunner中
@Autowired
private ConnectionUtils connectionUtils;
//转出操作
@Override
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();
}
}
//转入操作
@Override
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();
}
}
}
问题分析
上面代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了
一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了,违背了面向对象的开发思想
Proxy优化转账案例
我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强。这样
就不会对业务层产生影响,解决了耦合性的问题
常用动态代理技术
JDK 代理 : 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成
一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,从而实现方法增强
CGLIB代理:基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强
JDK和CGLIB的不同在于一个是基于接口的动态代理技术,另一个是基于父类的动态代理技术