目录
3、Spring中事务环境搭建(使用Spring自带的事务管理机制做代码演示)
3.1 创建商品表RUN_Goods和销售表RUN_Sale
3.2.2 创建SSM的web项目需要的全局配置文件:web.xml
3.3 创建SpringMVC用的配置文件:springmvc.xml
3.4 创建MyBatis用的配置文件:mybatis.xml
3.4.1 编写数据库的连接配置文件:jdbc.properties
3.5 创建Spring用的配置文件(包含事务管理器配置):spring.xml
3.6 编写dao层接口:GoodsDao.java + SaleDao.java
3.7 编写dao层级对应的mapper文件:GoodsDao.xml + SaleDao.xml
3.8 编写Service层接口:buyGoodsService.java
3.9 编写Service层接口的实现类(使用Spring事务管理的注解):buyGoodsServiceImpl.java
3.10 编写一个运行时异常类:NotEnoughException.java
3.12 编写测试类:buyServiceTest.java
3.12.4 Spring中的事务管理器的确发生作用了, 看表中数据的主键ID
3.12.5 附录2个实体类:SaleEntity和GoodsEntity
3.12.7 使用 Spring 的事务注解管理事务(掌握@Transactional )
4、Spring中事务环境搭建(使用AspectJ的事务管理机制做代码演示)
4.1 在pom.xml文件中引入AspectJ管理事务的依赖:只需关注“第二种”即可
4.2 在spring.xml文件中配置AspectJ事务管理器和AOP切入点表达式
【参考B站动力节点视频:https://www.bilibili.com/video/BV1nz4y1d7uy?p=96】
1、关于事务, 先回答一些问题(重要)
Spring处理事务的流程:
2、Spring 的事务管理
事务,原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
在 Spring 中通常可以通过以下两种方式来实现对事务的管理:(1)使用 Spring 的事务注解管理事务。(2)使用 AspectJ 的 AOP 配置管理事务。
2.1 Spring 事务管理 API
Spring 的事务管理,主要用到两个事务相关的接口。
2.1.1 (1)事务管理器接口(重点)
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
【A】常用的两个实现类
PlatformTransactionManager 接口有两个常用的实现类:
(1)DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
(2)HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
【B】Spring 的回滚方式(理解)
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译时异常)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方 式。
【C】回顾错误与异常(理解)
Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类 (或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。
Error 是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError、NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出)的,JVM 一般会终止线程。
程序在编译和运行时出现的另一类错误,称之为异常,它是 JVM 通知程序员的一种方式。通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。异常分为运行时异常与受查异常。
(1)运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如,NullPointerException、ArrayIndexOutOfBoundsException、 IllegalArgumentException 等均属于运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码编写足够仔细,程序足够健壮,运行时异常是可以避免的。
(2)受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。如 SQLException, ClassNotFoundException,IOException 等都属于受查异常。
注意:RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception 的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的为 RuntimeException 的子类,那么定义的就是受查异常。
2.1.1 (2)事务定义接口
事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。
【A】定义了五个事务隔离级别常量(掌握)
这些常量均是以 ISOLATION_开头,即形如 ISOLATION_XXX:
(1)DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ。Oracle 默认为 READ_COMMITTED。
(2)READ_UNCOMMITTED:读未提交。未解决任何并发问题。
(3) READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
(4)REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读。
(5)SERIALIZABLE:串行化。不存在并发问题。
【B】定义了七个事务传播行为常量(掌握)
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome() 调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传 播行为是加在方法上的。
事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
(1)PROPAGATION_REQUIRED
(2)PROPAGATION_REQUIRES_NEW
(3)PROPAGATION_SUPPORTS
(4)PROPAGATION_MANDATORY
(5)PROPAGATION_NESTED
(6)PROPAGATION_NEVER
(7)PROPAGATION_NOT_SUPPORTED
a、 PROPAGATION_REQUIRED:指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中; 若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther() 方法时就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
b、 PROPAGATION_SUPPORTS:指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
c、 PROPAGATION_REQUIRES_NEW:总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执 行完毕。
【C】定义了默认事务超时时限
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。
3、Spring中事务环境搭建(使用Spring自带的事务管理机制做代码演示)
3.1 创建商品表RUN_Goods和销售表RUN_Sale
CREATE TABLE `RUN_Goods` (
`Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID(商品编号ID)',
`Name` varchar(256) NOT NULL COMMENT '商品名称',
`Account` int(11) NOT NULL DEFAULT '0' COMMENT '商品数量',
`Price` int(11) NOT NULL DEFAULT '0' COMMENT '商品价格',
`AddTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
`UpdateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
CREATE TABLE `RUN_Sale` (
`Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID(销售记录ID)',
`Gid` int(11) NOT NULL DEFAULT '0' COMMENT '商品编号ID',
`Nums` int(11) NOT NULL DEFAULT '0' COMMENT '商品数量',
`AddTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
`UpdateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售表';
3.2 添加依赖
3.2.1 添加依赖:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wind</groupId>
<artifactId>ssm-web-tx2-aspectj</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>ssm-web Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.3.2</spring.version>
<mybatis.version>3.4.6</mybatis.version>
<mybatis.spring.version>2.0.3</mybatis.spring.version>
<mysql.version>8.0.22</mysql.version>
<druid.version>1.2.4</druid.version>
</properties>
<dependencies>
<!--使用Spring管理事务需要的依赖如下,2选1即可。
(1)第一种:使用Spring自带的事务管理器需要的依赖。
(2)第二种:使用AspectJ管理事务需要的依赖。
-->
<!--第二种:使用AspectJ管理事务需要的依赖=begin-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<!--第二种:使用AspectJ管理事务需要的依赖=end-->
<!--第一种:使用Spring自带的事务管理器需要的依赖=begin-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--第一种:使用Spring自带的事务管理器需要的依赖=end-->
<!--SpringMVC依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--SpringMVC使用的Servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!--SpringMVC使用的jsp依赖-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--MyBatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!--MyBatis与SpringMVC整合时需要的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mysql数据库连接池依赖:使用的是德鲁伊数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!--springMVC序列化用的jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
<!--单元测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>ssm-web</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<!--
maven默认扫描src/main/java中的文件而不理会src/main/resources中的xml文件,
所以后来添加了resource节点,这样就将src/main/resources中的xml文件改变maven默认的扫描策略,
防止造成src/main/resources下的配置文件打包丢失。
编译之后的文件中少了mapper.xml,这个和maven有关,maven编译src/java代码的时候,
默认只会对java文件进行编译然后放在target/classes目录,需要在pom.xml中加入下面配置-->
<!--如果不添加此节点,mapper.xml文件、config.properties文件、config.spring文件等
都不会被加载到target的classes中去,也就不能被使用,也就会报错。-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
3.2.2 创建SSM的web项目需要的全局配置文件:web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--注册前端控制器,也即整个web项目中唯一的一个后端中央控制器,用于分发给不同的Controller处理器-->
<servlet>
<servlet-name>springmvc-servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc-servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--注册Spring的监听器-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--注册字符集过滤器,一般使用Spring框架自带的字符集过滤器即可-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--加载静态资源-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
</web-app>
3.3 创建SpringMVC用的配置文件:springmvc.xml
<?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:mvc="http://www.springframework.org/schema/mvc"
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/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--SpringMVC的配置文件,用来声名Controller和其他web相关的对象-->
<!--配置组件扫描器-->
<context:component-scan base-package="com.wind.controller"/>
<!--视图解析器:添加前缀和后缀。
SpringMVC框架为了避免对于请求资源路径与扩展名上的冗余,在视图解析器 InternalResouceViewResolver
中引入了请求的前辍与后辍。而 ModelAndView 中只需给出要跳转页面的文件名即可,对于具体的文件路径与文件扩展名,
视图解析器会自动完成拼接。-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--视图文件的路径-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--视图文件的扩展名-->
<property name="suffix" value=".jsp"/>
</bean>
<!--注册注解驱动。
(1)响应ajax请求,返回json字符串。
(2)解决静态资源访问问题。-->
<mvc:annotation-driven/>
<!--加载静态资源图片啊,jQuery文件啊等等-->
<mvc:resources location="js/" mapping="/js/**"/>
<mvc:resources location="images/" mapping="/images/**"/>
</beans>
3.4 创建MyBatis用的配置文件:mybatis.xml
<?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>-->
<!--<!– 打印SQL–>-->
<!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
<!--</settings>-->
<!--给实体类设置别名-->
<typeAliases>
<package name="com.wind.entity"/>
</typeAliases>
<!--SQL Mapper映射文件的位置-->
<mappers>
<!--name:是包名,这个包下的mapper文件能够一次性加载-->
<!--package:使用这个属性的前提是:
(1)mapper文件名称和dao接口名必须完全一样,包括大小写。
(2)mapper文件和dao接口必须在同一目录下。-->
<package name="com.wind.dao"/>
</mappers>
</configuration>
3.4.1 编写数据库的连接配置文件:jdbc.properties
##数据库驱动
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
##MySQL连接信息
jdbc.url=jdbc:mysql://127.0.0.1:3306/RUNOOB?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT
##用户名
jdbc.username=root
##密码
jdbc.password=root
3.5 创建Spring用的配置文件(包含事务管理器配置):spring.xml
<?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"
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">
<!--Spring的配置文件,用来声名service、dao、工具类等对象-->
<!--加载连接mysql时需要的配置文件-->
<context:property-placeholder location="classpath:config/jdbc.properties"/>
<!--声名数据源,连接数据库-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--配置数据库连接池的初始化大小、最小、最大-->
<property name="initialSize" value="5"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="20"/>
<!--配置获取连接等待超时的时间,单位是毫秒-->
<property name="maxWait" value="10000"/>
<!--配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒-->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!--配置一个连接在池中最小生存的时间,单位是毫秒-->
<property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>
<!--声名一个SqlSessionFactoryBean,用它来创建SqlSessionFactory-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:config/mybatis.xml"/>
</bean>
<!--声名MyBatis的扫描器,创建dao接口接口对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
<property name="basePackage" value="com.wind.dao"/>
</bean>
<!--声名service的注解@Service所在的包名-->
<!--<context:component-scan base-package="com.wind.service,com.wind.serviceImpl"/>-->
<context:component-scan base-package="com.wind.service*"/>
<!--事务的配置:使用Spring自带的事务管理器来管理事务-->
<!--1.声名一个事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--给事务管理器配置一个数据源,告诉Spring你需要管理的是这个数据库-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.开启事务注解驱动:告诉Spring使用注解管理事务,创建代理对象。
transaction-manager:事务管理器对象的ID。-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
3.6 编写dao层接口:GoodsDao.java + SaleDao.java
package com.wind.dao;
import com.wind.entity.GoodsEntity;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface GoodsDao {
/**
* 更新库存。goodsEntity 表示本次用户购买的商品信息
*/
int updateGoods(@Param("goodsEntity") GoodsEntity goodsEntity);
/**
* 根据商品ID查询商品信息
*/
GoodsEntity queryGoods(@Param("id") Integer id);
}
package com.wind.dao;
import com.wind.entity.SaleEntity;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface SaleDao {
//增加销售记录
int insertSale(@Param("saleEntity") SaleEntity saleEntity);
}
3.7 编写dao层级对应的mapper文件:GoodsDao.xml + SaleDao.xml
<?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.wind.dao.GoodsDao">
<sql id="sql_select">
select Id, Name, Account, Price, AddTime, UpdateTime from RUN_Goods
</sql>
<update id="updateGoods" parameterType="goodsEntity" useGeneratedKeys="true">
update RUN_Goods set
account = account - #{goodsEntity.account}
where id = #{goodsEntity.id}
</update>
<select id="queryGoods" resultType="com.wind.entity.GoodsEntity">
<include refid="sql_select"/>
where id = #{id}
</select>
</mapper>
<?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.wind.dao.SaleDao">
<sql id="sql_select">
select Id, Gid, Nums, AddTime, UpdateTime from RUN_Sale
</sql>
<insert id="insertSale" parameterType="saleEntity" keyProperty="saleEntity.id" useGeneratedKeys="true">
insert into RUN_Sale
(
Gid,
Nums
)
values
(
#{saleEntity.gid},
#{saleEntity.nums}
)
</insert>
</mapper>
3.8 编写Service层接口:buyGoodsService.java
package com.wind.service;
import org.springframework.stereotype.Service;
@Service
public interface buyGoodsService {
/**
* 购买商品的方法。其中:goodsId:购买的商品ID。nums:购买的商品数量。
*/
void buy(Integer goodsId, Integer nums);
}
3.9 编写Service层接口的实现类(使用Spring事务管理的注解):buyGoodsServiceImpl.java
package com.wind.serviceImpl;
import com.wind.dao.GoodsDao;
import com.wind.dao.SaleDao;
import com.wind.entity.GoodsEntity;
import com.wind.entity.SaleEntity;
import com.wind.exception.NotEnoughException;
import com.wind.service.buyGoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class buyGoodsServiceImpl implements buyGoodsService {
@Autowired
private SaleDao saleDao;
@Autowired
private GoodsDao goodsDao;
/***
* 另:在方法上直接使用注解 @Transactional 即可。
* 解释:使用的是事务控制的默认值:
* (1)@Transactional 只能加在public修改的方法上。
* (2)默认的事务传播行为是:Propagation.REQUIRED
* (3)默认是事务隔离级别是:Isolation.DEFAULT
* (4)默认是在抛出运行时异常时回滚事务
*/
/**
* (0)@Transactional 只能加在public修改的方法上。
* (1)事务的传播行为。
* (2)事务的隔离级别。
* (3)该事务是否是只读。
* (4)rollbackFor:表示该方法发生执行的异常时一定会回滚。
* <p>
* 另外:rollbackFor 表示该方法发生执行的异常时一定会回滚,它的处理逻辑是:
* (1)Spring框架会首先检查程序抛出的"异常"是否包含在你自己定义的rollbackFor的属性值中,
* 如果在属性值列表中,不管什么异常,一定回滚。
* (2)如果程序抛出的异常不在你自己定义的rollbackFor的属性值列表中,
* Spring框架会检查这个异常是否是运行时异常RuntimeException,如果是,则一定回滚。
*/
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {
NullPointerException.class, NotEnoughException.class
}
)
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("。。。buy方法开始执行了。。。");
//数据库操作1
//1.购买了商品后,首先需要记录购买记录
SaleEntity saleEntity = new SaleEntity();
saleEntity.setGid(goodsId);
saleEntity.setNums(nums);
int insertSale = saleDao.insertSale(saleEntity);
//2.然后需要更新库存了
GoodsEntity goodsEntity = goodsDao.queryGoods(goodsId);
if (goodsEntity == null) {
//商品不存在
throw new NullPointerException("编号是" + goodsId + "的商品不存在");
} else if (goodsEntity.getAccount() < nums) {
throw new NotEnoughException("编号是" + goodsId + "的商品库存不足");
}
//数据库操作2
//3.更新库存了
GoodsEntity entityNew = new GoodsEntity();
entityNew.setId(goodsId);
entityNew.setAccount(nums);
int updateGoods = goodsDao.updateGoods(entityNew);
System.out.println("。。。buy方法执行结束了。。。");
}
}
3.10 编写一个运行时异常类:NotEnoughException.java
package com.wind.exception;
import org.springframework.stereotype.Component;
/**
* 自定义的运行时异常
*/
@Component
public class NotEnoughException extends RuntimeException {
public NotEnoughException() {
super();
}
public NotEnoughException(String message) {
super(message);
}
}
3.11 编写测试基类:BaseTest.java
package test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/*.xml"})
public abstract class BaseTest {
}
3.12 编写测试类:buyServiceTest.java
package test;
import com.wind.service.buyGoodsService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class buyServiceTest extends BaseTest {
@Autowired
private buyGoodsService buyGoodsService;
@Test
public void buyTest() {
Integer goodsId = 2;
Integer nums = 1;
//service代理对象是=com.sun.proxy.$Proxy35:这是一个JDK动态代理对象
System.out.println("service代理对象是=" + buyGoodsService.getClass().getName());
buyGoodsService.buy(goodsId, nums);
}
}
3.12.1 程序正常运行不抛出异常时的测试结果
3.12.2 程序抛出“商品不存在”的运行时异常的测试结果
3.12.3 程序抛出“库存不足”的运行时异常的测试结果
3.12.4 Spring中的事务管理器的确发生作用了, 看表中数据的主键ID
3.12.5 附录2个实体类:SaleEntity和GoodsEntity
package com.wind.entity;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
public class GoodsEntity implements Serializable {
private static final long serialVersionUID = -959529746052167108L;
private Integer id;
private String name;
private Integer account;
private Integer price;
}
package com.wind.entity;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
public class SaleEntity implements Serializable {
private static final long serialVersionUID = -959529746052167108L;
private Integer id;
private Integer gid;
private Integer nums;
}
3.12.6 附录项目结构图
3.12.7 使用 Spring 的事务注解管理事务(掌握@Transactional )
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。@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 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
4、Spring中事务环境搭建(使用AspectJ的事务管理机制做代码演示)
4.1 在pom.xml文件中引入AspectJ管理事务的依赖:只需关注“第二种”即可
【注意:使用Spring自带的事务管理器只需要关注“第一种”即可。】
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wind</groupId>
<artifactId>ssm-web-tx2-aspectj</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>ssm-web Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.3.2</spring.version>
<mybatis.version>3.4.6</mybatis.version>
<mybatis.spring.version>2.0.3</mybatis.spring.version>
<mysql.version>8.0.22</mysql.version>
<druid.version>1.2.4</druid.version>
</properties>
<dependencies>
<!--使用Spring管理事务需要的依赖如下,2选1即可。
(1)第一种:使用Spring自带的事务管理器需要的依赖。
(2)第二种:使用AspectJ管理事务需要的依赖。
-->
<!--第二种:使用AspectJ管理事务需要的依赖=begin-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<!--第二种:使用AspectJ管理事务需要的依赖=end-->
<!--第一种:使用Spring自带的事务管理器需要的依赖=begin-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--第一种:使用Spring自带的事务管理器需要的依赖=end-->
<!--SpringMVC依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--SpringMVC使用的Servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!--SpringMVC使用的jsp依赖-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--MyBatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!--MyBatis与SpringMVC整合时需要的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mysql数据库连接池依赖:使用的是德鲁伊数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!--springMVC序列化用的jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
<!--单元测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>ssm-web</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<!--
maven默认扫描src/main/java中的文件而不理会src/main/resources中的xml文件,
所以后来添加了resource节点,这样就将src/main/resources中的xml文件改变maven默认的扫描策略,
防止造成src/main/resources下的配置文件打包丢失。
编译之后的文件中少了mapper.xml,这个和maven有关,maven编译src/java代码的时候,
默认只会对java文件进行编译然后放在target/classes目录,需要在pom.xml中加入下面配置-->
<!--如果不添加此节点,mapper.xml文件、config.properties文件、config.spring文件等
都不会被加载到target的classes中去,也就不能被使用,也就会报错。-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
4.2 在spring.xml文件中配置AspectJ事务管理器和AOP切入点表达式
只需要关注的是下面这三个:
<!--1.声名一个事务管理器对象-->
<!-- 2.声名业务方法它的事务属性(事务隔离级别、事务传播行为、事务超时时间)。 (1)id:标识一个 <tx:advice> 与 </tx:advice> 之间的配置的内容的。 (2)transaction-manager:事务管理器对象的ID。 -->
<!--3.上述的步骤2中配置好了哪些方法要使用AspectJ管理的事务, 但是这些仅仅只是方法名称而已,你并不指导这些方法是哪个包下的,确定不了, 因此还需要配置AOP,使用切面表达式来指定具体的哪个包下的哪个方法要使用事务。 -->
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--Spring的配置文件,用来声名service、dao、工具类等对象-->
<!--加载连接mysql时需要的配置文件-->
<context:property-placeholder location="classpath:config/jdbc.properties"/>
<!--声名数据源,连接数据库-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--配置数据库连接池的初始化大小、最小、最大-->
<property name="initialSize" value="5"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="20"/>
<!--配置获取连接等待超时的时间,单位是毫秒-->
<property name="maxWait" value="10000"/>
<!--配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒-->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!--配置一个连接在池中最小生存的时间,单位是毫秒-->
<property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>
<!--声名一个SqlSessionFactoryBean,用它来创建SqlSessionFactory-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:config/mybatis.xml"/>
</bean>
<!--声名MyBatis的扫描器,创建dao接口接口对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
<property name="basePackage" value="com.wind.dao"/>
</bean>
<!--声名service的注解@Service所在的包名-->
<!--<context:component-scan base-package="com.wind.service,com.wind.serviceImpl"/>-->
<context:component-scan base-package="com.wind.service*"/>
<!--【1】事务的配置:下面是使用Spring自带的事务管理器来管理事务-->
<!--<!–1.声名一个事务管理器对象–>-->
<!--<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">-->
<!--<!–给事务管理器配置一个数据源,告诉Spring你需要管理的是这个数据库–>-->
<!--<property name="dataSource" ref="dataSource"/>-->
<!--</bean>-->
<!--<!–2.开启事务注解驱动:告诉Spring使用注解管理事务,创建代理对象。-->
<!--transaction-manager:事务管理器对象的ID。–>-->
<!--<tx:annotation-driven transaction-manager="transactionManager"/>-->
<!--【2】事务的配置:下面是使用Spring集成的AspectJ的事务管理器来管理事务,完全是声名式事务,不需要侵入到代码中去-->
<!--1.声名一个事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--给事务管理器配置一个数据源,告诉Spring你需要管理的是这个数据库-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
2.声名业务方法它的事务属性(事务隔离级别、事务传播行为、事务超时时间)。
(1)id:标识一个 <tx:advice> 与 </tx:advice> 之间的配置的内容的。
(2)transaction-manager:事务管理器对象的ID。
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--
(1)<tx:attributes> :配置事务属性。
(2)<tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法配置事务属性。
(3)name:方法名称:
3.1)可以是完整的方法名称,不带有包名和类名的。
3.2)可以是通配符 * :标识任意字符。
(4)propagation:事务的传播行为,是个枚举值。
(5)isolation:事务的隔离级别。
(6)timeout:事务的超时时间。
(7)rollback-for:你指定的异常类的类名,全限定类名,发生异常了则一定回滚。
-->
<tx:attributes>
<!--1.本项目中的案例走这个配置-->
<tx:method name="buy"
propagation="REQUIRED"
isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.wind.exception.NotEnoughException"/>
<!--2.增加方法的事务配置:使用通配符 * -->
<tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT"/>
<!--3.删除方法的事务配置-->
<tx:method name="remove*" propagation="REQUIRED" isolation="DEFAULT"/>
<!--4.来个默认的全局的方法-->
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--3.上述的步骤2中配置好了哪些方法要使用AspectJ管理的事务,
但是这些仅仅只是方法名称而已,你并不指导这些方法是哪个包下的,确定不了,
因此还需要配置AOP,使用切面表达式来指定具体的哪个包下的哪个方法要使用事务。
-->
<aop:config>
<!--3.1 配置切入点表达式:
(1)id:切入点表达式的名称,唯一值。
(2)expression:切入点表达式,用来指定哪些类需要使用事务,AspectJ会为它创建代理对象。
-->
<aop:pointcut id="servicePT" expression="execution(* *..service..*.*(..))"/>
<!--3.2 配置增强器:用来关联advice和pointcut
(1)advice-ref:通知的ID,也就是 <tx:advice> 的ID值。
(2)pointcut-ref:切入点表达式的ID。
含义:当切入点表达式【servicePT】中切入的方法和通知【myAdvice】中配置的方法匹配时,即会对该方法进行事务管理。
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePT"/>
</aop:config>
</beans>