spring学习笔记(21)编程式事务配置,service层概念引入

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: <div class="markdown_views"><h1 id="访问数据库事务导入">访问数据库事务导入</h1><p>在我之前的文章<a href="http://blog.csdn.net/qwe6112071/article/details/50976354">《spring学习笔记(19)mysql读写分离后端AOP控制实例》</a>中模拟数据库读写分离的

访问数据库事务导入

在我之前的文章《spring学习笔记(19)mysql读写分离后端AOP控制实例》中模拟数据库读写分离的例子,在访问数据库时使用的方法是:

public <E> E add(Object object) {
    return (E) getSessionFactory().openSession().save(object);
}

通过直接开启session而后保存对象、查询数据等操作,是没有事务的。而如果我们的项目规模变大,业务逻辑日益复杂,我们在一个方法中进行大量的数据库操作,而没有事务管理的话,一旦中间哪一个操作环节出错,后果是严重的。比如,一个用户通过支付宝转100块到银行账户,于是用户的100块先转到了银行,但这时数据库异常中断,银行无法把100块转给用户账户,这时事务又没有回滚,那么可能用户的100块就白白损失掉了。

在spring中,有多种方式可以进行我们的事务配置,比如我们可以直接修改上面的方法,加上事务:

public <E> E add(Object object) {
    Session session = getSessionFactory().openSession();
    Transaction tx = session.beginTransaction();
    E id = (E) session.save(object);
    tx.commit();
    return id; 
}

这样,我们就能为我们的add方法加上简单的事务了。

多数据库操作事务配置——引入Service层

但这样的话我们针对的只是dao层add这个方法,但在实际中,我们可能需要同时控制大量DAO的方法在同一个事务中,为此,我们可以创建service层来统一进行我们的业务逻辑处理。比如我们根据需求,需要先获取用户id,并修改用户名称,这里设计两个数据库操作,但我们在同一个service类方法中完成。

编程式事务模板类:TransactionTemplate

概念

在下例中,我们依然使用编程式事务,spring为此专门提供了模板类TransactionTemplate来满足我们的需求。TransactionTemplate是线程安全的,也即是说,我们可以在多个业务类中共享同一个TransactionTemplate实例进行事务管理。

常用属性

TransactionTemplate有很多常用的属性如:
1. isolationLevel:设置事务隔离级别
2. propagationBehavior:设置我们的事务传播行为
3. readOnly:设置为只读事务,即数据写操作会失败
4. timeout:设置链接过期时间,-1和默认为无超时限制
5. transactionManager:它是我们的IOC容器配置时的必要属性,设置我们的事务管理对象。在本例中用到hibernate,为HibernateTransactionManager。

核心方法

TransactionTemplate类在调用时主要用到的方法为execute(TransactionCallback
action)。

有返回值的回调接口

其中TransactionCallback为我们的回调接口,它只有一个方法:
T doInTransaction(TransactionStatus status),这个方法内是有事务的。通常我们的数据库查询操作就在这个方法里完成。

方法入参TransactionStatus接口

doInTransaction方法的唯一入参是TransactionStatus,它常用于查看我们当前的事务状态,它有两个常用的方法:
1. createSavepoint():创建一个记录点。
2. rollbackToSavepoint(savepoint):将事务回滚到特定记录点,这样从回滚处到记录点范围内所有的数据库操作都会失效。

无返回值的接口TransactionCallback

另外,doInTransaction是有返回值的,如果我们不需要返回值,我们可以使用TransactionCallback接口的一个子类TransactionCallbackWithoutResult,它对应的抽象方法doInTransactionWithoutResult(TransactionStatus status)是没有返回值的。

实例演示

下面开始我们的实例演示

1. service层配置

public class MyaseServiceImpl implements MyBaseService{
    private MyBaseDao myBaseDao;
    private TransactionTemplate transactionTemplate;

    @Override//测试方法
    public void queryUpdateUser(final Integer id,final String newName) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                User user = myBaseDao.queryUnique(User.class, id);//根据id获取用户
                System.out.println(user);
                user.setName(newName);//修改名称
                myBaseDao.update(user);//更新数据库
                System.out.println(user);
            }
        });
        /*下面的方法是由返回值的,在这里我们假设为User。
        User user = transactionTemplate.execute(new TransactionCallback<User>() {

            @Override
            public User doInTransaction(TransactionStatus status) {
                User user = myBaseDao.queryUnique(User.class, id);
                System.out.println(user);
                user.setName(newName);
                myBaseDao.update(user);
                System.out.println(user);
                return user;
            }
        });
        */
    }

    public void setMyBaseDao(MyBaseDao myBaseDao) {//set属性注入
        this.myBaseDao = myBaseDao;
    }

    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
}

2. DAO层配置

对应的DAO层类和部分方法如下所示:

public class MyBaseDaoImpl implements MyBaseDao{
    private SessionFactory sessionFactory;

    private Session getCurrentSession (){//根据参数来选择创建一个新的session还是返回当前线程的已有session
        return sessionFactory.getCurrentSession();
    }

    @Override
    public <E> E queryUnique(Class<E> clazz, Integer entityId) {//查询唯一的对象
            return (E) getCurrentSession().get(clazz, entityId);
    }
    @Override
    public void update(Object object) {//更新对象
        getCurrentSession().update(object);
    }
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
}

3. Spring容器配置Bean依赖关系

如果对于Hibernate不太理解,可以先不管我们的方法实现原理,只需要知道对应的方法实现了什么功能即可。在这里。接下来我们要配置我们的spring容器,主要完成Bean之间的依赖配置:

<bean id="myBaseDao" class="com.yc.dao.MyBaseDaoImpl" >
    <property name="sessionFactory"  ref="sessionFactory" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="myBaseServiceImpl" class="com.yc.service.MyBaseServiceImpl" >
    <property name="myBaseDao" ref="myBaseDao" />
    <property name="transactionTemplate" ref="transactionTemplate" />
</bean>

关于数据源和sessionFactory的配置实例如下:

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
            destroy-method="close"><!-- 设置为close使Spring容器关闭同时数据源能够正常关闭,以免造成连接泄露 --> 
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/yc" />
        <property name="username" value="yc" />
        <property name="password" value="yc" />
        <property name="defaultReadOnly" value="false" /><!-- 设置为只读状态,配置读写分离时,读库可以设置为true
        在连接池创建后,会初始化并维护一定数量的数据库安连接,当请求过多时,数据库会动态增加连接数,
        当请求过少时,连接池会减少连接数至一个最小空闲值 -->
        <property name="initialSize" value="5" /><!-- 在启动连接池初始创建的数据库连接,默认为0 -->
        <property name="maxActive" value="15" /><!-- 设置数据库同一时间的最大活跃连接默认为8,负数表示不闲置 -->
        <property name="maxIdle" value="10"/><!-- 在连接池空闲时的最大连接数,超过的会被释放,默认为8,负数表示不闲置 -->
        <property name="minIdle" value="2" /><!-- 空闲时的最小连接数,低于这个数量会创建新连接,默认为0 -->
        <property name="maxWait" value="10000" /><!-- 连接被用完时等待归还的最大等待时间,单位毫秒,超出时间抛异常,默认为无限等待 -->
    </bean>

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource">
            <ref bean="dataSource" />
        </property>
        <property name="hibernateProperties">
            <props>
                <!-- MySQL的方言 -->
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="javax.persistence.validation.mode">none</prop>
                <!-- 必要时在数据库新建所有表格 -->
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="current_session_context_class">thread</prop>
                <!-- <prop key="hibernate.format_sql">true</prop> -->
            </props>
        </property>
        <property name="packagesToScan" value="com.yc.model" />
    </bean>

4. 测试方法和结果分析

配置完成后,就可以进行我们的测试了。在这里,我用到了Junit测试组件

public class Test1 {
    @Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/spring-datasource.xml");
        MyBaseServiceImpl myBaseServiceImpl = (MyBaseServiceImpl) ac.getBean("myBaseServiceImpl");
        myBaseServiceImpl.queryUpdateUser(1, "newName");//在这里调用我们的service层方法
    }
}

调用测试方法,我们会看到控制台输出如下相关信息:

DEBUG: org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtaining JDBC connection
DEBUG: org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtained JDBC connection
DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - begin————————这里我们的事务开始了
DEBUG: org.hibernate.loader.Loader - Loading entity: [com.yc.model.User#1]——————————读取id为1的用户
DEBUG: org.hibernate.loader.Loader - Done entity load//完成装载工作
User [id=1, name=zenghao]——————获得了我们的User信息
User [id=1, name=newName]——————完成了修改操作
DEBUG: org.springframework.orm.hibernate4.HibernateTransactionManager - Initiating transaction commit//初始化数据库提交
DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - committing————————这时候才完成了事务提交
观察打印信息,我们会发现我们像数据库发出了两次请求(分别为获取和更新)但事务才提交了一次。说明这两个请求在同一个事务中。这样我们就能确保多个数据库操作在同一个事务内完成,一旦中间出现异常,能立即回滚,取消前面的数据库操作。
很多人都知道我们的mvc模式将后端业务分成了三层(DAO,service,controller),从这里,我们也能略微看出DAO层和service层的功能职责了。DAO主要完成数据库查询的封装,而Service层则调用DAO层的数据库查询方法来完成我们的业务逻辑处理

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
10天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
136 73
|
7天前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
34 13
|
10天前
|
Java Spring
【Spring配置相关】启动类为Current File,如何更改
问题场景:当我们切换类的界面的时候,重新启动的按钮是灰色的,不能使用,并且只有一个Current File 项目,下面介绍两种方法来解决这个问题。
|
25天前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
一键注入 Spring 成员变量,顺序编程
|
10天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
10天前
|
Java Spring
【Spring配置】创建yml文件和properties或yml文件没有绿叶
本文主要针对,一个项目中怎么创建yml和properties两种不同文件,进行配置,和启动类没有绿叶标识进行解决。
|
18天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
68 14
|
1月前
|
缓存 安全 Java
Spring高手之路26——全方位掌握事务监听器
本文深入探讨了Spring事务监听器的设计与实现,包括通过TransactionSynchronization接口和@TransactionalEventListener注解实现事务监听器的方法,并通过实例详细展示了如何在事务生命周期的不同阶段执行自定义逻辑,提供了实际应用场景中的最佳实践。
45 2
Spring高手之路26——全方位掌握事务监听器
|
16天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
53 6
|
17天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
80 3