Spring Dao编程基础-编程式事务

从 0 开始深入学习 Spring 专栏目录总览

下面,我们来开始学习 SpringFramework 中的事务控制。乍一上来咱先不整那些声明式的控制,先从比较原始的开始学习,不然一上来就搞那些复杂的,小伙伴们容易看懵,也达不到最终的学习效果。

1. 代码准备

咱都知道,事务应该放在业务层控制,所以接下来的代码中会有 Service 和 Dao 两部分。

1.1 Dao

Dao 的部分咱就不重写了,还是复用前面的 UserDao 吧,不过这次咱就不搞接口 + 实现类了,怪费劲的:

@Repository
public class UserDao {
    
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    public void save(User user) {
        jdbcTemplate.update("insert into tbl_user (name, tel) values (?, ?)", user.getName(), user.getTel());
    }
    
    public User findById(Integer id) {
        List<User> userList = jdbcTemplate
                .query("select * from tbl_user where id = ?", new BeanPropertyRowMapper<>(User.class), id);
        return userList.size() > 0 ? userList.get(0) : null;
    }
    
    public List<User> findAll() {
        return jdbcTemplate.query("select * from tbl_user", new BeanPropertyRowMapper<>(User.class));
    }
}

这里注入了 JdbcTemplate ,过会咱在 xml 配置文件里配一个就得了。

1.2 Service

Service 中依赖 Dao ,这都很简单的事情了,这里咱还是跟上一章一样,在两个数据库操作中间加一个除零操作:

@Service
public class UserService {
    
    @Autowired
    UserDao userDao;
    
    public void saveAndQuery() {
        User user = new User();
        user.setName("阿巴阿巴");
        user.setTel("123654789");
    
        userDao.save(user);

        int i = 1 / 0;

        List<User> userList = userDao.findAll();
        System.out.println(userList);
    }
}

1.3 配置文件

配置文件的编写,还是前面的那老一套,数据源、JdbcTemplate 等等:

<?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 
                           https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <context:component-scan base-package="com.linkedbear.spring.transaction.b_programmatic"/>
    <context:annotation-config/>

</beans>

这些内容都是前面 IOC 部分的知识,很好理解吧,service 和 dao 咱就不在这里配置了,都用注解驱动得了。

1.4 测试运行

咱先来写一个测试启动类来试一下现在 UserService 中的逻辑:

public class ProgrammaticTransactionApplication {
    
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("tx/programmatic-transaction.xml");
        UserService userService = ctx.getBean(UserService.class);
        userService.saveAndQuery();
    }
}

运行 main 方法,控制台会抛出异常,但数据却被插入到数据库了:

Spring Dao编程基础-编程式事务

这很明显不行,需要引入事务来控制逻辑执行。

2. 编程式事务引入【掌握】

SpringFramework 中的编程式事务,使用起来非常的简单,咱先讲解使用,学会了咱再解释其中的组件。

2.1 配置事务相关组件

编程式事务控制的部分需要在配置文件中注册两个组件:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

咱先简单介绍一下他俩都是干嘛的:

  • DataSourceTransactionManager :事务管理器,它负责控制事务
  • TransactionTemplate :事务模板,使用它可以完成编程式事务

2.2 Service使用事务模板

配置事务模板之后,Service 要相应的注入进去:

@Service
public class UserService {
    
    @Autowired
    TransactionTemplate transactionTemplate;

之后,下面的 saveAndQuery 方法中要使用事务模板,它的使用方法是调用 execute 方法:

public void saveAndQuery() {
    User user = new User();
    user.setName("阿巴阿巴");
    user.setTel("123654789");

    transactionTemplate.execute(???);
}

这个 execute 方法中需要传入一个 TransactionCallback 类型的对象,而这个 TransactionCallback 本身是一个函数式接口:

@FunctionalInterface
public interface TransactionCallback<T> {
	T doInTransaction(TransactionStatus status);
}

那思路就有了,咱在这里面可以传 lambda 啊:

    transactionTemplate.execute(status -> {
        userDao.save(user);

        int i = 1 / 0;

        List<User> userList = userDao.findAll();
        System.out.println(userList);
        return null;
    });

上面的业务代码直接塞进去,最后它要一个返回值,咱也不知道返回啥,那就给个 null 呗。

2.3 测试运行

这样改完就算完事了,直接重新运行 main 方法,控制台依然会打印除零异常。但是数据库没有再插入新的数据:

Spring Dao编程基础-编程式事务

证明事务已经生效。

2.4 TransactionCallback的优化

上面的写法中,最后返回了一个 null ,它为啥会要这个 null 呢?咱可以点进 TransactionTemplate 的 execute 方法去看一眼:(只节选挂件部分)

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    // ......
        T result;
        try {
            // 此处是lambda表达式的返回值
            result = action.doInTransaction(status);
        } 
        // ......
        // 返回出去了
        return result;
    }
}

哦,合着这个地方的返回值,是相当于 TransactionTemplate 的 execute 方法的返回值啊,那我们这根本用不到呀,总不能每次都这么写吧,太费劲了。

SpringFramework 当然为我们想到了这一点,于是它针对 TransactionCallback 接口加了一个抽象类 TransactionCallbackWithoutResult :

public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> {

	@Override
	public final Object doInTransaction(TransactionStatus status) {
		doInTransactionWithoutResult(status);
		return null;
	}

	protected abstract void doInTransactionWithoutResult(TransactionStatus status);
}

从类名上就很容易理解了,它的这个设计更是直白,它就是帮我们返回了那个 null ,其余的内容可以说是一点没变。

那我们就可以换用它了呀:

    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            userDao.save(user);

            int i = 1 / 0;

            List<User> userList = userDao.findAll();
            System.out.println(userList);
        }
    });

注意这回没法用 lambda 表达式了,因为是抽象类的匿名内部类实现,所以没招,只能这样写

3. 编程式事务涉及到的组件【熟悉】

上面的配置文件中咱引入了两个组件,本节咱简单解释一下它俩的用途。

3.1 DataSourceTransactionManager

从类名也可以看出,它是基于数据源的事务管理器。它实现的根接口 PlatformTransactionManager 有定义 commit 和 rollback 方法:

public interface PlatformTransactionManager extends TransactionManager {
	TransactionStatus getTransaction(TransactionDefinition definition)
			throws TransactionException;
	void commit(TransactionStatus status) throws TransactionException;
	void rollback(TransactionStatus status) throws TransactionException;
}

只不过这个 commit 和 rollback 方法要传入一个 TransactionStatus 的参数,这跟之前咱操作 jdbc 的 Connection 不太一样,至于为啥要这么设计,咱放到后面第 60 章 Spring 事务控制模型章讲解。

注意 PlatformTransactionManager 还有一个根接口 TransactionManager ,它是 SpringFramework 5.2 才新加的接口,由于响应式 jdbc 在 SpringFramework 中引入,在 SpringFramework 5.2 之后引入了响应式事务,由此产生了新的根接口。不过这种新鲜东西本身还没有经过大范围的实践,小册就先不提它了,小伙伴们知道一下就 OK 了。

3.2 TransactionTemplate

事务模板,它与前面咱学的 JdbcTemplate 在设计上是类似的,都是提供一个简单的模板来完成平时比较复杂的工作( JdbcTemplate 解决的是不需要再写那些复杂的 Statement 、ResultSet 等等)。它的核心方法是来自 TransactionOperations 接口定义的 execute 方法:(注意下面 else 块中的注释)

@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
    }
    else {
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        try {
            result = action.doInTransaction(status);
        }
        catch (RuntimeException | Error ex) {
            // 业务代码出现异常,回滚事务
            rollbackOnException(status, ex);
            throw ex;
        }
        catch (Throwable ex) {
            // 业务代码出现异常,回滚事务
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
        // try块没有出现异常,业务代码执行成功,提交事务
        this.transactionManager.commit(status);
        return result;
    }
}

可以发现,它的事务控制,与咱自己写的,在思路上是没有任何区别的。而且它提交和回滚事务的动作,就是拿的 TransactionManager 执行的 commit 和 rollback 方法。

TransactionTemplate 本质上是一个 TransactionDefinition ,这个咱也到第 60 章一起讲解,这里小伙伴们有个印象即可。

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Spring Dao编程基础-编程式事务