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

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: <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层的数据库查询方法来完成我们的业务逻辑处理

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
4天前
|
Java 开发者 Spring
Spring Framework 中的 @Autowired 注解:概念与使用方法
【4月更文挑战第20天】在Spring Framework中,@Autowired 注解是实现依赖注入(Dependency Injection, DI)的一种非常强大的工具。通过使用 @Autowired,开发者可以减少代码中的引用绑定,提高模块间的解耦能力
28 6
|
12天前
|
SQL Java 数据库连接
(自用)Spring常用配置
(自用)Spring常用配置
15 0
|
1月前
|
监控 Java 数据处理
【Spring云原生】Spring Batch:海量数据高并发任务处理!数据处理纵享新丝滑!事务管理机制+并行处理+实例应用讲解
【Spring云原生】Spring Batch:海量数据高并发任务处理!数据处理纵享新丝滑!事务管理机制+并行处理+实例应用讲解
|
1月前
|
SQL Java 数据库连接
挺详细的spring+springmvc+mybatis配置整合|含源代码
挺详细的spring+springmvc+mybatis配置整合|含源代码
41 1
|
5天前
|
存储 安全 Java
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(下)
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(下)
13 0
|
5天前
|
安全 Java 数据库
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(上)
第2章 Spring Security 的环境设置与基础配置(2024 最新版)
28 0
|
6天前
|
安全 Java Spring
Spring Security 5.7 最新配置细节(直接就能用),WebSecurityConfigurerAdapter 已废弃
Spring Security 5.7 最新配置细节(直接就能用),WebSecurityConfigurerAdapter 已废弃
18 0
|
6天前
|
安全 Java 应用服务中间件
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
24 0
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
|
12天前
|
JSON Java 数据库连接
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
21 1
|
12天前
|
Java 数据库 Spring
切面编程的艺术:Spring动态代理解析与实战
切面编程的艺术:Spring动态代理解析与实战
25 0
切面编程的艺术:Spring动态代理解析与实战