spring (4)Proxy优化转账案例

使用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层进行事务控制

步骤

  1. 编写线程绑定工具类
  2. 编写事务管理器
  3. 修改service层代码
  4. 修改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的不同在于一个是基于接口的动态代理技术,另一个是基于父类的动态代理技术

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值