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

相关文章
|
4天前
|
Java 开发者 Spring
深入理解Spring Boot的@ComponentScan注解
【4月更文挑战第22天】在构建 Spring Boot 应用时,@ComponentScan 是一个不可或缺的工具,它使得组件发现变得自动化和高效。这篇博客将详细介绍 @ComponentScan 的基本概念、关键属性及其在实际开发中的应用。
21 4
|
6天前
|
Java 开发者 Spring
Spring Framework 中的 @Autowired 注解:概念与使用方法
【4月更文挑战第20天】在Spring Framework中,@Autowired 注解是实现依赖注入(Dependency Injection, DI)的一种非常强大的工具。通过使用 @Autowired,开发者可以减少代码中的引用绑定,提高模块间的解耦能力
29 6
|
15天前
|
存储 安全 Java
事件的力量:探索Spring框架中的事件处理机制
事件的力量:探索Spring框架中的事件处理机制
28 0
|
2天前
|
安全 Java 数据库连接
[AIGC] Spring框架的基本概念和优势
[AIGC] Spring框架的基本概念和优势
|
2天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
2天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
14天前
|
XML Java 数据格式
进阶注解探秘:深入Spring高级注解的精髓与实际运用
进阶注解探秘:深入Spring高级注解的精髓与实际运用
26 2
|
14天前
|
XML Java 数据格式
从入门到精通:Spring基础注解的全面解析
从入门到精通:Spring基础注解的全面解析
30 2
从入门到精通:Spring基础注解的全面解析
|
6月前
|
Java Spring
【Spring事务的实现原理】
【Spring事务的实现原理】
|
2月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
35 1