三 后台环境搭建 - 8. 声明式事务

8. 声明式事务

支点: git checkout -b 3.8.0_transaction

8.1 目标

  • 在框架环境下通过一系列的配置由 Spring 来管理事务. 通过事务操作

    • 让我们写的代码能够享受框架提供的服务
try {
	// 开启事务 (关闭自动提交)
    // 代码中有一个错误 所有数据库操作 ( 增删改 ) 回滚
    connection.setAutoCommiit(false);
    
    // 核心操作
    adminService.saveAdmin(admin);
    
    // 提交事务
    connection.commit();
    
} catch (Exception e) {
	// 回滚事务
    connection.roolBack();
} finally {
	// 释放数据库连接
    connection.close();
}

8.2 思路

8.2.1 选择合适的事务管理器
  • 事务定位方法

    • 注解:@Transactional
    • 配置:切入点表达式 <推荐配置>
8.2.2 配置文件结构
图 8-1 事务管理的配置结构图

img

8.3 代码

8.3.1 配置事务管理器
8.3.2 配置 AOP
8.3.3 配置事务

spring-persist-tx.xml

img

<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
">
    <!-- 配置自动扫描的包: 只要是为了把 Service 扫描到 IOC 容器中 -->
    <context:component-scan base-package="com.atguigu.crowd.service"/>

    <!-- 配置事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 配置数据源  dataSource 会从 IOC 容器中找到-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置事务切面 -->
    <aop:config>
        <!-- 考虑到后面我们整合 SpringSecurity, 避免把 UserDetailsService 加入事务控制, 让切入点表达式定位到 ServiceImpl -->
        <aop:pointcut id="txPointcut" expression="execution(* *..*ServiceImpl.*(..))"/>

        <!-- 将切入点表达式和事务通知关联起来 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

    <!-- 配置事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- 配置事务属性 -->
        <tx:attributes>
            <!-- 查询方法: 配置只读属性, 让数据库知道这是个查询操作, 能够进行一定优化 -->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="count*" read-only="true"/>

            <!-- 增删改方法: 配置事务传播行为、回滚异常 -->
            <!--
                propagation 属性:
                    REQUIRED: 默认值, 表示当前方法必须工作在事务中,
                                如果当前线程上没有已经开启的事务, 则自己开新事务。
                                如果已经有了那么就使用已有的事务
                                顾虑: 用别人的事务有可能 "被" 回滚
                    REQUIRES_NEW: 建议使用的值, 表示不管当前线程上有没有事务, 都要自己开事务, 在自己的事务中运行
                                    好处: 不会受到其他事务回滚的影响
            -->
            <!--
                rollback-for 属性: 配置事务方法针对上面样的异常回滚
                    默认: RuntimeException 运行时异常回滚
                    建议: 编译时异常和运行时异常都回滚
            -->
            <tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="remove*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="batch*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>

</beans>

8.4 测试

8.4.1 创建服务接口和实现

atcrowdfunding03-admin-component

package com.atguigu.crowd.service.api;

import com.atguigu.crowd.entity.Admin;

public interface AdminService {

    void saveAdmin(Admin admin);

}
package com.atguigu.crowd.service.impl;

import com.atguigu.crowd.entity.Admin;
import com.atguigu.crowd.mapper.AdminMapper;
import com.atguigu.crowd.service.api.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AdminServiceImpl implements AdminService {

    @Autowired
    private AdminMapper adminMapper;

    @Override
    public void saveAdmin(Admin admin) {
        adminMapper.insert(admin);

        throw new RuntimeException();
    }

}
8.4.2 Junit 测试

测试代码 testTx 方法

package com.atguigu.crowd.test;

import com.atguigu.crowd.entity.Admin;
import com.atguigu.crowd.mapper.AdminMapper;
import com.atguigu.crowd.service.api.AdminService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/* 在类上标记必要的注解, Spring 整合 Junit */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml", "classpath:spring-persist-tx.xml"})
public class CrowdTest {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AdminMapper adminMapper;

    @Autowired
    private AdminService adminService;

    @Test
    public void testTx() {
        Admin admin = new Admin(null, "jerry", "123123", "杰瑞", "jerry@qq.com", null);
        adminService.saveAdmin(admin);
    }

    @Test
    public void testLog() {
        // 1. 获取 Logger(日志记录) 对象, 这里传入的 Class 对象就是当前打印的类
        Logger logger = LoggerFactory.getLogger(CrowdTest.class);

        // 2. 根据不同日志级别打印日志
        logger.debug("Hello I an Debug Level!!!");
        logger.debug("Hello I an Debug Level!!!");
        logger.debug("Hello I an Debug Level!!!");

        logger.info("Hello I an Debug Level!!!");
        logger.info("Hello I an Debug Level!!!");
        logger.info("Hello I an Debug Level!!!");

        logger.warn("Hello I an Debug Level!!!");
        logger.warn("Hello I an Debug Level!!!");
        logger.warn("Hello I an Debug Level!!!");

        logger.error("Hello I an Debug Level!!!");
        logger.error("Hello I an Debug Level!!!");
        logger.error("Hello I an Debug Level!!!");
    }

    @Test
    public void testInsertAdmin() {
        Admin admin = new Admin(null, "tom", "123123", "汤姆", "tom@qq.com", null);
        int insert = adminMapper.insert(admin);

        // 在实际开发中, 所有想查看数值的地方都所有 sysout 方式打印, 会给项目上线运行带来问题
        // sysout 本质上是一个 IO 操作, 通过 IO 的操作是比较消耗性能的, 如果项目中 sysout 多, 那么对性能的影响就比较大了
        // 即使上线前专门花时间删除代码中的 sysout, 也就可能有遗漏, 而且非常麻烦
        // 而如果使用日志系统, 那么通过日志级别就可以批量的控制信息的打印
        System.out.println("受影响的行数" + insert);
    }

    @Test
    public void testConnection() throws SQLException {
        // 1. 通过数据源对象获取数据源连接
        Connection connection = dataSource.getConnection();
        // 2. 打印数据库连接
        System.out.println(connection);
    }

}
  • 执行后保存操作会回滚
  • 事务配置成功
  1. 将 logback.xml 日志级别改为 DEBUG

img

  1. 执行 testTx 方法

    1. 可以看到 SQL 下面的回滚信息

img

8.4.3 注意
  • 在基于 XML 的声明式事务中, 事务属性的 tx:method 是必须配置的, 如果某个方法没有配置对应的 tx:method, 那么事务对这个方法不会生效

8.5 Push

img

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值