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层的数据库查询方法来完成我们的业务逻辑处理

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
24天前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
46 0
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
52 4
|
2月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
41 0
|
2月前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
1月前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
46 1
Spring高手之路24——事务类型及传播行为实战指南
|
25天前
|
XML Java 数据库连接
Spring中的事务是如何实现的
Spring中的事务管理机制通过一系列强大的功能和灵活的配置选项,为开发者提供了高效且可靠的事务处理手段。无论是通过注解还是AOP配置,Spring都能轻松实现复杂的事务管理需求。掌握这些工具和最佳实践,能
37 3
|
1月前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
30 1
|
2月前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
153 1
|
2月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
47 0
|
3月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。