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

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: <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。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业务,无需过多担心数据库的管理和维护。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
6月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
6月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
6月前
|
搜索推荐 JavaScript Java
基于springboot的儿童家长教育能力提升学习系统
本系统聚焦儿童家长教育能力提升,针对家庭教育中理念混乱、时间不足、个性化服务缺失等问题,构建科学、系统、个性化的在线学习平台。融合Spring Boot、Vue等先进技术,整合优质教育资源,提供高效便捷的学习路径,助力家长掌握科学育儿方法,促进儿童全面健康发展,推动家庭和谐与社会进步。
|
7月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1239 5
|
7月前
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
7月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
7月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2448 0
|
7月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
371 0
探索Spring Boot的@Conditional注解的上下文配置
|
7月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
702 0
|
7月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南