Java原理收藏-技术篇

Spring深入 3.事务底层原理分析

2020-01-04  本文已影响0人  香沙小熊

一、数据库的事务的基本特性

事务是区分文件存储系统与Nosql数据库重要特性之一,其存在的意义是为了保证即使在并发情况下也能正确的执行crud操作。怎样才算是正确的呢?这时提出了事务需要保证的四个特性即ACID:

隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read)
读未提交(Read uncommitted) 可能 可能 可能
读已提交(Read committed) 不可能 可能 可能
可重复读(Repeatable read) 不可能 不可能 可能
可串行化(SERIALIZABLE) 不可能 不可能 不可能
脏读 :

一个事务读取到另一事务未提交的更新数据

不可重复读 :

在同一事务中,多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据. 相反, “可重复读”在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据。

幻读 :

查询表中一条数据如果不存在就插入一条,并发的时候却发现,里面居然有两条相同的数据。这就幻读的问题。

二、Sring 对事务的支持与使用

知识点:
1.spring 事务相关API说明
2.声明式事务的使用
3.事务传播机制

  1. spring 事务相关API说明
    spring 事务是在数据库事务的基础上进行封装扩展 其主要特性如下:
    a.支持原有的数据事务的隔离级别
    b.加入了事务传播的概念 提供多个事务的和并或隔离的功能
    c.提供声明式事务,让业务代码与事务分离,事务变得更易用。

怎么样去使用Spring事务呢?spring 提供了三个接口供使用事务。分别是:

基于API实现事务
public class SpringTransactionExample {
    private static String url = "jdbc:mysql:///localhost:3306/test";
    private static String user = "root";
    private static String password = "123456";

    public static Connection openConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
        return conn;
    }

    public static void main(String[] args) {
        final DriverManagerDataSource ds = new DriverManagerDataSource(url, user, password);
        final TransactionTemplate template = new TransactionTemplate();
        template.setTransactionManager(new DataSourceTransactionManager(ds));
        template.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                Connection conn = DataSourceUtils.getConnection(ds);
                Object savePoint = null;
                try {
                    {
                        // 插入
                        PreparedStatement prepare = conn.
                                prepareStatement("insert INTO account (accountName,user,money) VALUES (?,?,?)");
                        prepare.setString(1, "111");
                        prepare.setString(2, "aaaa");
                        prepare.setInt(3, 10000);
                        prepare.executeUpdate();
                    }
                    // 设置保存点
                    savePoint = status.createSavepoint();
                    {
                        // 插入
                        PreparedStatement prepare = conn.
                                prepareStatement("insert INTO account (accountName,user,money) VALUES (?,?,?)");
                        prepare.setString(1, "222");
                        prepare.setString(2, "bbb");
                        prepare.setInt(3, 10000);
                        prepare.executeUpdate();
                    }
                    {
                        // 更新
                        PreparedStatement prepare = conn.
                                prepareStatement("UPDATE account SET money= money+1 where user=?");
                        prepare.setString(1, "asdflkjaf");
                        Assert.isTrue(prepare.executeUpdate() > 0, "");
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    System.out.println("更新失败");
                    if (savePoint != null) {
                        status.rollbackToSavepoint(savePoint);
                    } else {
                        status.setRollbackOnly();
                    }
                }
                return null;
            }
        });
    }
}

输出

更新失败
查询数据库

2、声明示事务

我们前面是通过调用API来实现对事务的控制,这非常的繁琐,与直接操作JDBC事务并没有太多的改善,所以Spring提出了声明示事务,使我们对事务的操作变得非常简单,甚至不需要关心它。

编写服务类

@Transactional

public void addAccount(String name, int initMenoy) {

String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMenoy);

// 人为报错

int i = 1 / 0;

}

l 演示添加 @Transactional 注解和不添加注解的情况。

3、事务传播机制

类别 事务传播类型 说明
支持当前事务 PROPAGATION_REQUIRED
(必须的)
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
支持当前事务 PROPAGATION_SUPPORTS
(支持)
支持当前事务,如果当前没有事务,就以非事务方式执行。
支持当前事务 PROPAGATION_MANDATORY
(强制)
使用当前的事务,如果当前没有事务,就抛出异常。
不支持当前事务 PROPAGATION_REQUIRES_NEW
(隔离)
新建事务,如果当前存在事务,把当前事务挂起。
不支持当前事务 PROPAGATION_NOT_SUPPORTED
(不支持)
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
不支持当前事务 PROPAGATION_NEVER
(强制非事务)
以非事务方式执行,如果当前存在事务,则抛出异常。
套事务 PROPAGATION_NESTED
(嵌套事务)
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

常用事务传播机制:

l 演示常用事务的传播机制

用例1:

创建用户时初始化一个帐户,表结构和服务类如下。

表结构 服务类 功能描述
user UserSerivce 创建用户,并添加帐户
account AccountService 添加帐户

UserSerivce.createUser(name) 实现代码

@Transactional
public void createUser(String name) {

    // 新增用户基本信息

    jdbcTemplate.update("INSERT INTO `user` (name) VALUES(?)", name);

    //调用accountService添加帐户

    accountService.addAccount(name, 10000);

 }

AccountService.addAccount(name,initMoney) 实现代码(方法的最后有一个异常)

@Transactional(propagation = Propagation.REQUIRED)
public void addAccount(String name, int initMoney) {

    String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

    jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMenoy);

    // 出现分母为零的异常

    int i = 1 / 0;

}

实验预测一:

createUser addAccount(异常) 预测结果
场景一 无事务 required createUser (成功) addAccount(不成功)
场景二 required 无事务 createUser (不成功) addAccount(不成功)
场景三 required not_supported createUser (不成功) addAccount(成功)
场景四 required required_new createUser (不成功) addAccount(不成功)
场景五 required(异常移至createUser方法未尾) required_new createUser(不成功)
addAccount(成功)
场景六 required(异常移至createUser方法未尾)(addAccount 方法移至createUser方法的同一个类里) required_new createUser (不成功) addAccount(不成功)

三、aop 事务底层实现原理

讲事务原理之前我们先来做一个实验,当场景五的环境改变,把addAccount 方法移至UserService 类下,其它配置和代码不变:

@Override
@Transactional
public void createUser(String name) {

    jdbcTemplate.update("INSERT INTO `user` (name) VALUES(?)", name);

    addAccount(name, 10000);

    // 人为报错
    int i = 1 / 0;

}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addAccount(String name, int initMoney) {

    String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

    jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMoney);

}

演示新场景

经过演示我们发现得出的结果与场景五并不 一至,required_new 没有起到其对应的作用。原因在于spring 声明示事务使用动态代理实现,而当调用同一个类的方法时,是会不会走代理逻辑的,自然事务的配置也会失效。

通过一个动态代理的实现来模拟这种场景

UserSerivce proxyUserSerivce = (UserSerivce) Proxy.newProxyInstance(LubanTransaction.class.getClassLoader(),
        new Class[]{UserSerivce.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                try {

                    System.out.println("开启事务:"+method.getName());

                    return method.invoke(userSerivce, args);

                } finally {

                    System.out.println("关闭事务:"+method.getName());

                }

            }

        });

proxyUserSerivce.createUser("kpioneer");

当我们调用createUser 方法时 仅打印了 createUser 的事务开启、关闭,并没有打印addAccount 方法的事务开启、关闭,由此可见addAccount 的事务配置是失效的。

如果业务当中上真有这种场景该如何实现呢?
在spring xml中配置 暴露proxy 对象,然后在代码中用AopContext.currentProxy() 就可以获当前代理对象

<!-- 配置暴露proxy -->
<aop:aspectj-autoproxy expose-proxy="true"/>

// 基于代理对象调用创建帐户,事务的配置又生效了

@Transactional
public void createUser(String name) {

    // 新增用户基本信息

    jdbcTemplate.update("INSERT INTO `user` (name) VALUES(?)", name);

    // 暴露proxy 对象 调用accountService添加帐户
    ((UserSerivce) AopContext.currentProxy()).addAccount(name, 10000);
    // 人为报错
    int i = 1 / 0;
 }
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addAccount(String name, int initMoney) {

    String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

    jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMoney);
}
但是并不推荐这样写事务,还是另写AccountService类 调用addAccount最好
上一篇 下一篇

猜你喜欢

热点阅读