Spring——Spring中的事务、使用注解(@Transactional)控制事务、使用AspectJ框架控制事务 (下)

简介: Spring——Spring中的事务、使用注解(@Transactional)控制事务、使用AspectJ框架控制事务 (下)

文章目录:


2.5 编写dao接口和对应的mapper映射文件

2.5.1 商品实体类Goods对应的dao接口和mapper文件

2.5.2 销售记录实体类Sale对应的dao接口和mapper文件 

2.6 编写MyBatis主配置文件

2.7 定义异常类(运行时异常)

2.8 定义Service接口和对应的实现类

2.8.1 Spring使用注解(@Transactional)控制事务

2.9 定义Spring配置文件

2.9.1 引入外部属性配置文件(jdbc.properties

2.9.2 声明数据源、SqlSessionFactory对象、读取mapper文件

2.9.3 声明Service业务层对象

2.9.4 声明事务管理器、开启事务注解驱动 

2.10 定义测试类

2.10.1 测试方法1

2.10.2 测试方法2

2.10.3 测试方法3

4.使用AspectJ框架控制事务

5.两种方式各自的使用特点


2.5 编写dao接口和对应的mapper映射文件


2.5.1 商品实体类Goods对应的dao接口和mapper文件

package com.bjpowernode.dao;
import com.bjpowernode.entity.Goods;
/**
 *
 */
public interface GoodsDao {
    //查询某个id的商品信息,返回的是Goods对象(商品类)
    Goods selectById(Integer id);
    //参数goods表示本次购买的商品id和amount购买数量
    int updateGoods(Goods goods);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.GoodsDao">
    <!-- 使用insert、update、delete、select标签编写sql语句 -->
    <select id="selectById" resultType="com.bjpowernode.entity.Goods">
        select id,name,amount,price
        from goods
        where id = #{id}
    </select>
    <update id="updateGoods">
        update goods
        set amount = amount - #{amount}
        where id = #{id}
    </update>
</mapper>

2.5.2 销售记录实体类Sale对应的dao接口和mapper文件 

package com.bjpowernode.dao;
import com.bjpowernode.entity.Sale;
/**
 *
 */
public interface SaleDao {
    int insertSale(Sale sale);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.SaleDao">
    <!-- 使用insert、update、delete、select标签编写sql语句 -->
    <insert id="insertSale">
        insert into sale(gid,num) values(#{gid},#{num})
    </insert>
</mapper>

2.6 编写MyBatis主配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 设置日志 -->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <mappers>
<!--        <mapper resource=""/>-->
        <package name="com.bjpowernode.dao"/>
    </mappers>
</configuration>

2.7 定义异常类(运行时异常)

这个异常类主要用来处理购买商品时,库存、商品是否存在这些情况下,所发生的异常信息。 

package com.bjpowernode.excetion;
/**
 *  运行时异常
 */
public class NotEnoughException extends RuntimeException {
    public NotEnoughException() {
        super();
    }
    public NotEnoughException(String message) {
        super(message);
    }
}

2.8 定义Service接口和对应的实现类

package com.bjpowernode.service;
/**
 *
 */
public interface BuyGoodsService {
    /**
     * @param goodsId: 购买的商品id
     * @param num: 购买的商品数量
     */
    void buy(Integer goodsId,Integer num);
}

2.8.1 Spring使用注解(@Transactional)控制事务

@Transactional的所有可选属性如下所示:

1. propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED

2. isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT

3. readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false

4. timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。

5. rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

6. rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

7. noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

8.noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。 

需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非public 方法上的@Transaction 注解。若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

package com.bjpowernode.service.impl;
import com.bjpowernode.dao.GoodsDao;
import com.bjpowernode.dao.SaleDao;
import com.bjpowernode.entity.Goods;
import com.bjpowernode.entity.Sale;
import com.bjpowernode.excetion.NotEnoughException;
import com.bjpowernode.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
 *
 */
public class BuyGoodsServiceImpl implements BuyGoodsService {
    private SaleDao saleDao;
    private GoodsDao goodsDao;
    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }
    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }
    /**
     *  @Transactional 注解放在public业务方法的上面,表示方法有事务功能
     *  propagation = Propagation.REQUIRED: 设置事务的传播属性,默认值
     *  isolation = Isolation.DEFAULT: 设置事物的隔离级别,默认值
     *  readOnly = false: 对数据库的操作不是只读的
     *  timeout = 20: 超时时限设置为20s
     *  rollbackFor = {NullPointerException.class,NotEnoughException.class}): //两个异常发生任何一个,则执行回滚操作
     *  框架首先检查方法抛出的异常是不是在rollback的数组中,如果在,一定执行回滚。
     *  如果不在,框架会继续检查抛出的异常是不是RuntimeException,如果是,仍然执行回滚。
     */
    @Transactional(propagation = Propagation.REQUIRED,
                    isolation = Isolation.DEFAULT,
                    readOnly = false,
                    timeout = 20,
                    rollbackFor = {NullPointerException.class,NotEnoughException.class})
    @Override
    public void buy(Integer goodsId, Integer num) {
        System.out.println("=====buy方法的开始======");
        //生成销售记录
        Sale sale=new Sale();
        sale.setGid(goodsId);
        sale.setNum(num);
        saleDao.insertSale(sale);
        //查询商品
        Goods goods=goodsDao.selectById(goodsId);
        if (goods == null) {
            throw new NullPointerException(goodsId + "商品不存在。");
        }else if (goods.getAmount() < num) {
            throw new NotEnoughException(goodsId + "商品库存不足。");
        }
        //更新库存
        Goods buyGoods=new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(num);
        goodsDao.updateGoods(buyGoods);
        System.out.println("=====buy方法的完成======");
    }
}

2.9 定义Spring配置文件

2.9.1 引入外部属性配置文件(jdbc.properties

jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&amp;characterEncoding=utf-8
jdbc.username=root
jdbc.password=12345678
<context:property-placeholder location="classpath:jdbc.properties" />

2.9.2 声明数据源、SqlSessionFactory对象、读取mapper文件

<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="myDataSource" />
    <property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="factory" />
    <property name="basePackage" value="com.bjpowernode.dao" />
</bean>

2.9.3 声明Service业务层对象

<bean id="buyGoodsService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl">
    <property name="goodsDao" ref="goodsDao" />
    <property name="saleDao" ref="saleDao" />
</bean>

2.9.4 声明事务管理器、开启事务注解驱动 

<!-- 声明事务的控制 -->
<!-- 声明事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 指定数据源DataSource -->
    <property name="dataSource" ref="myDataSource" />
</bean>
<!--
    开启事务注解驱动,告诉框架使用注解管理事务
    transaction-manager: 指定事务管理器的id
-->
<tx:annotation-driven transaction-manager="transactionManager" />

2.10 定义测试类

2.10.1 测试方法1

service.buy(100110);表示我们此时要购买1001号商品10台。 

@Test
public void test01() {
    ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
    BuyGoodsService service= (BuyGoodsService) ctx.getBean("buyGoodsService");
    service.buy(1001,10);
}

这里的 id 等于5,是因为在这之前,我做了几次代码测试,sale表中的主键(销售记录编号)是自增的,所以这里是5

后面的 gid=1001num=10,表示编号为1001的商品,卖出了10台。

2.10.2 测试方法2

service.buy(1002200);表示我们此时要购买1002号商品200台。(而数据库中1002号商品总库存只有50台,所以这里会发生异常)

@Test
public void test02() {
    ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
    BuyGoodsService service= (BuyGoodsService) ctx.getBean("buyGoodsService");
    service.buy(1002,200);
}

这里产生的异常是我们自定义的NotEnoughException 异常。所以事务一定执行回滚。

2.10.3 测试方法3

service.buy(100520);表示我们此时要购买1005号商品20台。(而数据库中并不存在1005号商品,所以这里会发生异常)

@Test
public void test03() {
    ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
    BuyGoodsService service= (BuyGoodsService) ctx.getBean("buyGoodsService");
    service.buy(1005,20);
}

这里产生的异常是JavaException类下的NullPointerException 异常。所以事务一定执行回滚。

4.使用AspectJ框架控制事务


使用这种方法控制事务的步骤如下:

1.    pom.xml中加入 spring-aspects 依赖

2.    spring配置文件中声明事务的内容:声明事务管理器;声明业务方法需要的事务属性;声明切入点表达式。

与上面使用 注解(@Transactional)相比,大部分代码都是一样的。只有Spring的配置文件中有所改动。

将第一种方法中spring配置文件中的声明事务注解驱动删掉,换成下面的代码就可以了。

    <!--
        声明业务方法的事务属性(隔离级别、传播行为、超时时限)
        id: 给业务方法配置事务段代码起个名称,唯一值
        transaction-manager: 事务管理器的id
    -->
    <tx:advice id="serviceAdvice" transaction-manager="transactionManager">
        <!-- 给具体的业务方法增加事务的说明 -->
        <tx:attributes>
            <!-- name的值: 1)业务方法的名称 2)带有通配符* -->
            <tx:method name="buy"
                       propagation="REQUIRED"
                       isolation="DEFAULT"
                       timeout="20"
                       read-only="false"
                       rollback-for="java.lang.NullPointerException,com.bjpowernode.excetion.NotEnoughException"/>
        </tx:attributes>
    </tx:advice>
    <!-- 声明切入点表达式,表示哪些包中的类、方法需要添加事务 -->
    <aop:config>
        <!--
            id: 切入点表达式的名称,唯一值
            expression: 切入点表达式
        -->
        <aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))" />
        <aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePointcut" />
    </aop:config>


5.两种方式各自的使用特点


一、使用@Transactional注解

1.    Spring框架自己提供的事务控制。

2.    适合中小型项目。

3.    使用方便,效率高。

二、使用AspectJ框架

1.    缺点:理解难,配置复杂。

2.    优点:代码和事务配置是分开的。控制事务,其源代码不用修改。能快速的了解和掌控项目的全部事务,适合大型项目。

相关文章
|
17天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
150 73
|
12天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
45 21
|
6天前
|
SQL Java 关系型数据库
【SpringFramework】Spring事务
本文简述Spring中数据库及事务相关衍伸知识点。
35 9
|
13天前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
43 13
|
17天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
17天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
3月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
275 2
|
4天前
|
Java 测试技术 应用服务中间件
Spring Boot 如何测试打包部署
本文介绍了 Spring Boot 项目的开发、调试、打包及投产上线的全流程。主要内容包括: 1. **单元测试**:通过添加 `spring-boot-starter-test` 包,使用 `@RunWith(SpringRunner.class)` 和 `@SpringBootTest` 注解进行测试类开发。 2. **集成测试**:支持热部署,通过添加 `spring-boot-devtools` 实现代码修改后自动重启。 3. **投产上线**:提供两种部署方案,一是打包成 jar 包直接运行,二是打包成 war 包部署到 Tomcat 服务器。
25 10
|
18天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
4天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
32 8