Spring(四)事务控制前言,ThreadLocal,JDK动态代理,CGLIB动态代理

目录

1.模拟转账案例

2.事务控制

3.ThreadLocal的说明

4.ThreadLocal的使用

5.自定义事务管理器

6.JDK动态代理方式简化代码


1.模拟转账案例

        使用DbUtils、Junit整合Spring进行模拟。

 pom.xml

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
    </dependencies>

applicationContext.xml

<?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="com.study"/>

    <context:property-placeholder location="classpath:jdbc.properties"/>

    <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>

    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

</beans>

测试类

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestDemo {

    @Autowired
    AccountService accountService;

    @Test
    public void transfer(){
        accountService.transfer("tom","jerry",100D);
    }
}

service接口

public interface AccountService {

    void transfer(String inName,String outName,Double money);
}

service实现类

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String inName, String outName, Double money) {
        accountDao.outMoney(outName,money);

        int i= 1/0;//模拟异常

        accountDao.inMoney(inName,money);    
    }
}

dao接口

public interface AccountDao {

    void inMoney(String name,Double money);

    void outMoney(String name,Double money);
}

dao实现类

@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;


    @Override
    public void inMoney(String name, Double money) {
        try {
            String sql = "update account set money = money - ? where name = ?";
            queryRunner.update(sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void outMoney(String name, Double money) {
        try {
            String sql = "update account set money = money + ? where name = ?";
            queryRunner.update(sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

        上述service实现类当中,模拟了在转账过程当中出现了不可抗力因素的异常,导致数据库表中没有达到预期效果,转出用户少了钱但是转入用户没有增加,正常是不允许这种情况出现的。

2.事务控制

        MySQL中事务的提交方式默认是自动提交,并且一个数据库连接一旦提交,就不可以回滚。上述案例中,service中执行了outMoney后就直接将事务进行提交,如果想要控制这种情况不再发生,那么我们需要把转入和转出操作作为一个最小的逻辑单元,也就是使用同一个事务去进行控制。使用同一个事务进行控制就需要两次操作使用同一个连接。如下代码:

service实现类

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private DataSource dataSource;//在配置文件中,已经将数据源加载进IOC容器当中,在此直接注入即可

    @Override
    public void transfer(String inName, String outName, Double money) {
        Connection connection = null;
        try {
            //获取连接
            connection = dataSource.getConnection();
            //设置为手动提交
            connection.setAutoCommit(false);
            accountDao.outMoney(connection,outName,money);
            int i= 1/0;//模拟异常
            accountDao.inMoney(connection,inName,money);

            //提交事务
            connection.commit();
        } catch (Exception e) {
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            try {
                //将事务还原为自动提交
                connection.setAutoCommit(true);
                //将连接归还给连接池
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

dao接口

public interface AccountDao {

    void inMoney(String name,Double money);

    void outMoney(String name,Double money);

    void inMoney(Connection connection,String name, Double money);

    void outMoney(Connection connection,String name,Double money);
}

dao实现类

@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;



    @Override
    public void inMoney(String name, Double money) {
        try {
            String sql = "update account set money = money - ? where name = ?";
            queryRunner.update(sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void outMoney(String name, Double money) {
        try {
            String sql = "update account set money = money + ? where name = ?";
            queryRunner.update(sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void inMoney(Connection connection,String name, Double money) {
        try {
            String sql = "update account set money = money - ? where name = ?";
            queryRunner.update(connection,sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void outMoney(Connection connection,String name, Double money) {
        try {
            String sql = "update account set money = money + ? where name = ?";
            queryRunner.update(connection,sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

其余类未修改

        以上方式解决了转账案例的数据不一致问题,控制住了事务。

3.ThreadLocal的说明

        在2中,service调用dao时每次都需要传入一个connection连接,耦合性比较高。如果想要降低代码的耦合性,我们可以将同一个连接放入到一个容器当中,service中控制事务使用这个连接,dao中操作数据库也使用这个连接。这样就降低了代码的耦合性。如下图:

        在操作共享变量时,会出现线程安全问题,Java类库提供了ThreadLocal类来解决这个问题。

        防止正在使用的变量被篡改。

        在一个线程中,保证每个步骤使用的是同一个变量。

        ThreadLocal的key就是当前线程,value是个泛型,定义时传入的是什么就是什么。

4.ThreadLocal的使用

工具类

@Component
public class ConnectionUtil {

    private static final ThreadLocal<Connection> TL = new ThreadLocal<>();
    @Autowired
    private DataSource ds;

    /**
     * 既可以向ThreadLocal中添加连接,也可以从ThreadLocal中取出连接。
     * 先取出,如果ThreadLocal中没有连接,则从数据源中获取连接放入ThreadLocal
     */
    public Connection getConnFromThreadLocal(){
        Connection connection = null;
        try {
            connection = TL.get();
            if (connection == null){
                connection = ds.getConnection();
                TL.set(connection);
            }
            connection.setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }

    /**
     * 将与此线程绑定的连接从ThreadLocal中剔除.
     * 若一直不剔除会出现内存溢出的问题.
     */
    public void removeFromThreadLocal(){
        TL.remove();
    }
}

service实现类

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private DataSource dataSource;//在配置文件中,已经将数据源加载进IOC容器当中,在此直接注入即可

    @Autowired
    private ConnectionUtil connectionUtil;

    @Override
    public void transfer(String inName, String outName, Double money) {
        Connection connection = null;
        try {
            /*//获取连接
            connection = dataSource.getConnection();
            //设置为手动提交
            connection.setAutoCommit(false);*/
            //从ThreadLocal中获取连接
            connection= connectionUtil.getConnFromThreadLocal();
            accountDao.outMoney(outName,money);
            int i= 1/0;//模拟异常
            accountDao.inMoney(inName,money);

            //提交事务
            connection.commit();
        } catch (Exception e) {
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            try {
                //将线程绑定的连接从ThreadLocal中移除
                connectionUtil.removeFromThreadLocal();
                //将事务还原为自动提交
                connection.setAutoCommit(true);
                //将连接归还给连接池
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

dao实现类

@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Autowired
    private ConnectionUtil connectionUtil;


    @Override
    public void inMoney(String name, Double money) {
        try {
            String sql = "update account set money = money - ? where name = ?";
            queryRunner.update(connectionUtil.getConnFromThreadLocal(),sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void outMoney(String name, Double money) {
        try {
            String sql = "update account set money = money + ? where name = ?";
            queryRunner.update(connectionUtil.getConnFromThreadLocal(),sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

5.自定义事务管理器

        在4中,我们使用ThreadLocal解决了事务的问题,但是不难看出service实现类中的代码过于冗余。所以我们抽出一个工具类用于解决业务逻辑以外的操作。

自定义事务管理器

@Component
public class MyTrunsactionManager {

    @Autowired
    private ConnectionUtil connectionUtil;

    //开启事务
    public void begin(){
        connectionUtil.getConnFromThreadLocal();
    }
    //提交事务
    public void commit(){
        try {
            connectionUtil.getConnFromThreadLocal().commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //回滚事务
    public void rollback(){
        try {
            connectionUtil.getConnFromThreadLocal().rollback();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //释放资源
    public void release(){
        try {
            connectionUtil.getConnFromThreadLocal().setAutoCommit(true);
            connectionUtil.removeFromThreadLocal();
            connectionUtil.getConnFromThreadLocal().close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

service实现类

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private MyTrunsactionManager trunsactionManager;

    @Override
    public void transfer(String inName, String outName, Double money) {
        Connection connection = null;
        try {
            //开启事务
            trunsactionManager.begin();

            accountDao.outMoney(outName,money);
            int i= 1/0;//模拟异常
            accountDao.inMoney(inName,money);

            //提交事务
            trunsactionManager.commit();
        } catch (Exception e) {
            trunsactionManager.rollback();
            e.printStackTrace();
        } finally {
            trunsactionManager.release();
        }

    }
}

其余代码不变

6.JDK动态代理方式简化代码

        在service中只应该关心业务逻辑,其他操作比如开启事务、提交事务、回滚事务、释放资源本不应该是service层处理的。所以使用动态代理的方式对其增强。

        JDK动态代理有个前提,就是目标对象需要有实现接口。

定义一个JDK动态代理的工厂

@Component
public class JDKProxyFactory {

    @Autowired
    private MyTrunsactionManager trunsactionManager;

    public Object createProxyObject(Object target){
        return Proxy.newProxyInstance(JDKProxyFactory.class.getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object object = null;
                        trunsactionManager.begin();
                        try {
                            object = method.invoke(target,args);
                            trunsactionManager.commit();
                        } catch (Exception e) {
                            trunsactionManager.rollback();
                            e.printStackTrace();
                        } finally {
                            trunsactionManager.release();
                        }
                        return object;
                    }
                });
    }
}

service实现类

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private MyTrunsactionManager trunsactionManager;

    @Override
    public void transfer(String inName, String outName, Double money) {

        accountDao.outMoney(outName,money);
        int i= 1/0;//模拟异常
        accountDao.inMoney(inName,money);
    }
}

测试类

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestDemo {

    @Autowired
    private AccountService accountService;

    @Autowired
    private JDKProxyFactory jdkProxyFactory;


    @Test
    public void transfer(){
        AccountService accountServiceProxy = (AccountService) jdkProxyFactory.createProxyObject(accountService);
        accountServiceProxy.transfer("tom","jerry",100D);
    }
}

其余代码不变

7.CGLIB动态代理方式简化代码

        CGLIB动态代理比JDK动态代理要强大,因为JDK动态代理中目标对象必须要实现接口,而CGLIB动态代理对目标对象没有这种要求。但CGLIB动态代理的效率要低于JDK动态代理。

定义一个CGLIB动态代理的工厂

@Component
public class CGLIBProxyFactory {

    @Autowired
    private MyTrunsactionManager trunsactionManager;

    public Object createProxyObject(Object target){
        return Enhancer.create(target.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

                Object invoke = null;
                try {
                    trunsactionManager.begin();
                    invoke = method.invoke(target, objects);
                    trunsactionManager.commit();
                } catch (Exception e) {
                    trunsactionManager.rollback();
                    e.printStackTrace();
                } finally {
                    trunsactionManager.rollback();
                }

                return invoke;
            }
        });
    }
}

测试类

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestDemo {

    @Autowired
    private AccountService accountService;

    @Autowired
    private JDKProxyFactory jdkProxyFactory;

    @Autowired
    private CGLIBProxyFactory cglibProxyFactory;

    @Test
    public void transfer(){
//        AccountService accountServiceProxy = (AccountService) jdkProxyFactory.createProxyObject(accountService);
        AccountService accountServiceProxy = (AccountService) cglibProxyFactory.createProxyObject(accountService);
        accountServiceProxy.transfer("tom","jerry",100D);
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值