自学内容网 自学内容网

第二十六章 Spring之假如让你来写事务——初稿篇

Spring源码阅读目录

第一部分——IOC篇

第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇

第二部分——AOP篇

第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇

第三部分——事务篇

第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇



前言

    对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
在这里插入图片描述

    所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


    书接上回,在上篇 第二十五章 Spring之曾经的老朋友——事务 中,A君 大体了解了 事务 的相关概念。接下来看看 A君 会有什么骚操作吧

尝试动手写IOC容器

    出场人物:A君(苦逼的开发)、老大(项目经理)

    背景:老大要求A君在一周内开发个简单的 IOC容器

    前情提要: A君 大体了解了 事务 的相关概念 。。。

第二十四版 事务初稿

    老大 跟催命似的问 A君 要进度,翻来覆去就那么一句话:这玩意很简单。整的 A君 直翻白眼。没办法,来不及在去了解更细致的概念了,先整出一版来,也好有个交代。至于设计啥的?不好意思,不懂。嘿嘿

    A君 仔细琢磨了一下,如果单纯的考虑实现,事务 确实也没什么东西。只需要定义一个方法拦截器,环绕增强即可,A君 三下五除二就写完了。毕竟刚做完 AOP,这部分知识还热乎着呢。代码如下:

import com.hqd.ch03.v24.aopalliance.intercept.MethodInterceptor;
import com.hqd.ch03.v24.aopalliance.intercept.MethodInvocation;

import javax.sql.DataSource;
import java.sql.Connection;

public class TransactionInterceptor implements MethodInterceptor {
    private DataSource dataSource;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Connection connection = dataSource.getConnection();
        connection.setAutoCommit(false);
        Object retVal = null;
        try {

        } catch (RuntimeException e) {
            throw e;
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }
}

咦?不太对头。A君 细看了下代码,发现了一个很严重的问题:Connection 如何传递?要知道 事务 本身是和 Connection 绑定的,如果 Connection 不是同一个,事务 自然也不是同一个了,而且从数据库连接池中获取连接,不可能每次都是同一个连接的。那通过参数传递?不成不成,正搁这 AOP 呢,难道要求各个连接点必须有个 Connection 参数,这不扯淡吗?俗话说:遇事不决,量子力学。嘿嘿,计算机世界同样适用,不过呢?量子力学得换成 ThreadLocal,使用 ThreadLocal 既能保证 Connection 在不同方法之间传递,又能保证每个线程都持有自己独立的 Connection,不同线程之间不会相互干扰。何乐而不为呢?于是 A君 定义 TransactionSynchronizationManager 管理资源。代码如下:

注:一个 Connection 在不同的业务层或服务之间通过参数传递,这种方式叫做:“连接传递”(connection-passing)

import com.hqd.ch03.v24.thread.NamedThreadLocal;

import java.util.HashMap;
import java.util.Map;

/**
 * 事务资源管理
 */
public abstract class TransactionSynchronizationManager {
    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");

    public static boolean hasResource(Object key) {
        Map<Object, Object> map = resources.get();
        return map != null && map.containsKey(key);
    }

    public static void bindResource(Object key, Object value) throws IllegalStateException {
        Map<Object, Object> map = resources.get();
        if (map == null) {
            map = new HashMap<>();
            resources.set(map);
        }
        map.put(key, value);
    }

    public static Object getResource(Object key) {
        Map<Object, Object> map = resources.get();
        if (map != null) {
            return map.get(key);
        }
        return null;
    }
}

TransactionInterceptor 也得稍作修改,改动如下:

在这里插入图片描述

Connection 绑定到线程中就完事了吗?还是不够,得保证用户从线程中取 Connection 才行。A君 又定义了一个 DataSourceUtils,用以获取绑定在线程中的 Connection。代码如下:

import com.hqd.ch03.v24.tx.transaction.support.TransactionSynchronizationManager;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public abstract class DataSourceUtils {
    public static Connection getConnection(DataSource dataSource) throws SQLException {
        return doGetConnection(dataSource);
    }

    private static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Object resource = TransactionSynchronizationManager.getResource(dataSource);
        if (resource instanceof Connection) {
            return (Connection) resource;
        }

        Connection con = dataSource.getConnection();
        TransactionSynchronizationManager.bindResource(dataSource, con);
        return con;
    }
}

如果有铁头娃不用这个工具类获取 Connection,那神仙来了都没办法,A君 就更没办法处理了

注:正常来说,此处应该还有一层封装,不过操作数据库方面的东西可有可无,不是这系列的重点,故而跳过。真实情况下,想要和Spring整合,无论是JdbcTemplate,还是Hibernate,Mybatis都得从DataSourceUtils中获取连接

    好了,接下来就是测试了。A君 先定义个简单的JDBC操作类,代码如下:

import com.hqd.ch03.v24.utils.DataSourceUtils;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.beanutils.BeanUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
public class UserServiceImpl {

    private DataSource dataSource;

    public void saveData(User user) throws SQLException {
        PreparedStatement preparedStatement = null;
        try {
            Connection connection = DataSourceUtils.getConnection(dataSource);
            preparedStatement = connection.prepareStatement("insert into user(id,name,age,sex) values (?,?,?,?)");
            preparedStatement.setInt(1, user.getId());
            preparedStatement.setString(2, user.getName());
            preparedStatement.setInt(3, user.getAge());
            preparedStatement.setString(4, user.getSex());
            preparedStatement.executeUpdate();
            int a = 1 / 0;
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
        }
    }

    public List<User> getUsers() throws Exception {
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            Connection connection = DataSourceUtils.getConnection(dataSource);
            preparedStatement = connection.prepareStatement("select * from user");
            resultSet = preparedStatement.executeQuery();
            List<User> list = new ArrayList<>();
            while (resultSet.next()) {
                User user = new User();
                BeanUtils.setProperty(user, "id", resultSet.getInt("id"));
                BeanUtils.setProperty(user, "name", resultSet.getString("name"));
                BeanUtils.setProperty(user, "age", resultSet.getInt("age"));
                BeanUtils.setProperty(user, "sex", resultSet.getString("sex"));
                list.add(user);
            }
            return list;
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (resultSet != null) {
                resultSet.close();
            }
        }
    }

    public void updateData(User user) throws SQLException {
        String sql = "UPDATE user SET name = ?,age=?,sex=? WHERE id = ?";
        PreparedStatement preparedStatement = null;
        try {
            Connection connection = DataSourceUtils.getConnection(dataSource);
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, user.getName());
            preparedStatement.setInt(2, user.getAge());
            preparedStatement.setString(3, user.getSex());
            preparedStatement.setInt(4, user.getId());
            preparedStatement.executeUpdate();
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
        }
    }

    public void deleteData(int id) throws SQLException {
        String sql = "DELETE FROM user WHERE id =? ";
        PreparedStatement preparedStatement = null;
        try {
            Connection connection = DataSourceUtils.getConnection(dataSource);
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, id);
            preparedStatement.executeUpdate();
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
        }
    }
}

接着,配置一下xml文件,如下:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring?useSSL=false&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="initialSize" value="5"/>
        <property name="minIdle" value="5"/>
        <property name="maxActive" value="20"/>
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="true"/>
        <property name="testOnReturn" value="false"/>
    </bean>
    <bean id="txAdvice" class="com.hqd.ch03.v24.tx.transaction.interceptor.TransactionInterceptor">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="userService" class="com.hqd.ch03.bean.jdbc.UserServiceImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <aop:config>
        <aop:pointcut id="txPoint" expression="execution(* com.hqd.ch03.bean.jdbc.UserServiceImpl.*Data(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
    </aop:config>
</beans>

现在所有工作都已经准备好了,接下来 A君 开始编写测试代码,一个是正常删除操作,一个是异常插入。如下:

@Test
    public void v24() throws Throwable {
        System.out.println("############# 第二十四版: 初稿篇 #############");
        SpringImitationV24 beanFactory = new SpringImitationV24Xml("classpath:v24/bean.xml");
        UserServiceImpl userService = (UserServiceImpl) beanFactory.getBean("userService");

        System.out.println("################## 删除操作前 ##################");
        List<com.hqd.ch03.bean.jdbc.User> users = userService.getUsers();
        users.forEach(System.out::println);
        userService.deleteData(11);
        System.out.println("################## 删除操作后 ##################");
        users = userService.getUsers();
        users.forEach(System.out::println);
        System.out.println();
        System.out.println();
        System.out.println("################## 插入操作前 ##################");
        users = userService.getUsers();
        users.forEach(System.out::println);
        com.hqd.ch03.bean.jdbc.User user = new com.hqd.ch03.bean.jdbc.User();
        user.setAge(15);
        user.setName("ww");
        user.setSex("nv");
        user.setId(11);
        try {
            userService.saveData(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("################## 插入操作后 ##################");
        users = userService.getUsers();
        users.forEach(System.out::println);
    }

测试结果如下:

在这里插入图片描述
可以看到,没发生异常的情况下,事务 是正常提交的,正确的删除了数据。发生异常时候,插入数据并没有生效,说明 事务 回滚了

    “难怪 老大 一直说有了 AOP事务 就是顺手的事。” A君 感叹道,“如果只考虑实现,确实就是顺手的事!” 好啦,A君 也准备下班了,等明天开会,再看看 老大 有什么说法吧

在这里插入图片描述


总结

    正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)


原文地址:https://blog.csdn.net/weixin_42789334/article/details/144115224

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!