系列文章目录
IDEA项目实践——创建Java项目以及创建Maven项目案例、使用数据库连接池创建项目简介
文章目录
1.1 MySQL 创建数据库 ssm,新建表 Student
(4) 定义 Mapper 扫描配置器 MapperScannerConfigurer
2. 开启注解驱动 transaction-manager:
2.5 使用 AspectJ 的 AOP 配置管理事务(掌握)
前言
本文主要介绍Spring集成mybatis以及spring当中的事务,文章当中的案例仅供参考。
一 Spring集成mybatis
将MyBatis 与 Spring进行整合,主要解决的问题就是将SqlSessionFactory对象交由Spring来管理。所以,该整合,只需要将SqlSessionFactory的对象生成器 将与与Spring进行整合,主要解决的问题就是SqlSessionFactoryBean注册在Spring容器中,再将其注入给Dao的实现类即可完成整合。
实现Spring与MyBatis的整合常用的方式:扫描的Mapper动态代理
Spring 像插线板一样,mybatis框架是插头,可以容易的组合到一起。插线板spring插上 mybatis,两个框架就是一个整体。
1.1 MySQL创建数据库ssm,新建表Student表
创建实体类student
1.1 MySQL 创建数据库 ssm,新建表 Student
1.1.1 原JDBC建立连接形式的讲解
1)指定数据源
原来的jdbc形式的配置文件
指定数据源
2) 工具类
mybatis与spring
spring
1.1.2 创建Maven项目
1.2 maven 依赖 pom.xml
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- Spring整合MyBatis的依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.0</version>
</dependency>
<!-- --------------------- -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
</dependencies>
<!--插件:-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
1.3 定义实体类 Student
创建学生的实体类
编辑代码如下:
package com.ambow.pojo;
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
private int age;
}
1.4 定义 StudentDao 接口
此处选择接口,写的时候没有截全
package com.ambow.dao;
import com.ambow.pojo.Student;
import java.util.List;
public interface StudentDao {
int insertStudent(Student student);
int updateStudent(Student student);
int deleteStudent(Student student);
Student selectStudentById(int id);
List<Student> selectAllStudents();
}
1.5 定义映射文件 mapper
在 Dao 接口的包中创建 MyBatis 的映射文件 mapper,命名与接口名相 同,本例为 StudentDao.xml。mapper 中的 namespace 取值也为 Dao 接口 的全限定性名。
项目的路径在此处
编辑映射文件的代码:
<?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.ambow.dao.StudentDao">
<insert id="insertStudent">
insert into student values(null,#{name},#{age})
</insert>
<update id="updateStudent">
update student set name = #{name},age = #{age} where id = #{id}
</update>
<delete id="deleteStudent">
delete from student where id = #{id}
</delete>
<select id="selectStudentById" resultType="com.ambow.pojo.Student">
select * from student where id = #{id}
</select>
<select id="selectAllStudents" resultType="com.ambow.pojo.Student">
select * from student
</select>
</mapper>
1.6 定义 Service 接口和实现类
接口定义:
package com.ambow.service;
import com.ambow.pojo.Student;
import java.util.List;
public interface StudentService {
int addStudent(Student student);
int modifyStudent(Student student);
int removeStudent(int id);
Student findStudentById(int id);
List<Student> findAllStudents();
}
接口的实现类定义:
自动生成接口的实现类
package com.ambow.service.impl;
import com.ambow.dao.StudentDao;
import com.ambow.pojo.Student;
import com.ambow.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("studentService")
public class StudentServiceImpl implements StudentService {
//引入StudentDao的对象
@Autowired
private StudentDao studentDao;
//Spring有set注入方法
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public int addStudent(Student student) {
return studentDao.insertStudent(student);
}
@Override
public int modifyStudent(Student student) {
return studentDao.updateStudent(student);
}
@Override
public int removeStudent(Student student) {
return 0;
}
@Override
public int removeStudent(int id) {
return studentDao.deleteStudent(id);
}
@Override
public Student findStudentById(int id) {
return studentDao.selectStudentById(id);
}
@Override
public List<Student> findAllStudents() {
return studentDao.selectAllStudents();
}
}
1.7 定义 MyBatis 主配置文件
在 src 下定义 MyBatis 的主配置文件,命名为 mybatis.xml。
这里有两点需要注意:
(1)主配置文件中不再需要数据源的配置了。因为数据源要交给 Spring 容器 来管理了。
(2)这里对 mapper 映射文件的注册,使用标签,即只需给出 mapper 映射文件所在的包即可。因为 mapper 的名称与 Dao 接口名相同, 可以使用这种简单注册方式。这种方式的好处是,若有多个映射文件,这里的 配置也是不用改变的。当然,也可使用原来的标签方式。
原先的environment数据源将交由Spring来管理 【代管】
mybatis-config.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>
<properties resource="jdbc.properties"/>
<typeAliases>
<package name="com.ambow.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name ="com.ambow.dao"/>
</mappers>
</configuration>
jdbc.properties文件:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
username=root
password=root
可以先测试一下:
测试类的代码:
package com.ambow.test;
import com.ambow.dao.StudentDao;
import com.ambow.pojo.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class SMTest {
@Test
public void test01() throws IOException {
//先找到主配置文件
String url = "mybatis-config.xml";
//读取主配置文件
InputStream inputStream = Resources.getResourceAsStream(url);
//输入流拿到SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//再次拿到SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//最后拿到StudentDao对象
StudentDao studentDao = ((SqlSession) sqlSession).getMapper(StudentDao.class);
//拿到学生对象,进行遍历
List<Student> students = studentDao.selectAllStudents();
for (Student student : students) {
System.out.println(student);
}
}
}
等价于将原来的mybatis代码写一遍,然后修改
1.8 修改 Spring 配置文件
整合:把MyBatis中的核心对象,放到Spring容器
(1) 数据源的配置(掌握)
使用 JDBC 模板,首先需要配置好数据源,数据源直接以 Bean 的形式配置 在 Spring 配置文件中。根据数据源的不同,其配置方式不同:
Druid 数据源 DruidDataSource Druid 是阿里的开源数据库连接池。是 Java 语言中最好的数据库连接 池。Druid 能够提供强大的监控和扩展功能。Druid 与其他数据库连接池的 最大区别是提供数据库的
使用地址:Home · alibaba/druid Wiki · GitHub常见问题
配置连接池:
Spring 配置文件:
(2) 从属性文件读取数据库连接信息
为了便于维护,可以将数据库连接信息写入到属性文件中,使 Spring 配置 文件从中读取数据。
属性文件名称自定义,但一般都是放在 src 下。
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
Spring 配置文件从属性文件中读取数据时,需要在的 value 属性中使用${ },将在属性文件中定义的 key 括起来,以引用指定属性的值。
该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。 使用<context>标签。
<context:property-placeholder />方式(掌握)
该方式要求在 Spring 配置文件头部加入 spring-context.xsd 约束文件
<context:property-placeholder />标签中有一个属性 location,用于指定属 性文件的位置。
(3) 注册 SqlSessionFactoryBean
工厂类拿到工厂对象,以及主配置文件
(4) 定义 Mapper 扫描配置器 MapperScannerConfigurer
Mapper 扫描配置器 MapperScannerConfigurer 会自动生成指定的基本 包中 mapper 的代理对象。该 Bean 无需设置 id 属性。basePackage 使用分 号或逗号设置多个包。
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--整合:把MyBatis中的核心对象,放到Spring容器-->
<!--引入属性配置文件-->
<context:property-placeholder location="classpath:jdbc.properties" />
<context:component-scan base-package="com.ambow.service" />
<!--1.DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!--2.注册SqlSessionFactoryBean-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--起别名的也可以将其放在这里-->
<property name="typeAliasesPackage" value="com.ambow.pojo" />
<!--mapper映射文件的位置,此处的value里面包之间需要使用/分割-->
<property name="mapperLocations" value="classpath:com/ambow/dao/*.xml" />
</bean>
<!--3.mapper的扫描配置器 -> 生成mapper的代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.ambow.dao" />
</bean>
</beans>
1.9 向 Service 注入接口名
向 Service 注入 Mapper 代理对象时需要注意,由于通过 Mapper 扫描配置器 MapperScannerConfigurer 生成的 Mapper 代理对象没有名称,所以在 向 Service 注入 Mapper 代理时,无法通过名称注入。但可通过接口的简单类名注入,因为生成的是这个 Dao 接口的对象。
补充的地方:
<!--4.向service层注入Dao-->
<!-- <bean id="studentService" class="com.ambow.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao" />
</bean>-->
<!--注入的过程-->
<!--dataSource -> SqlSessionFactoryBean -> MapperScannerConfigurer(生成Dao代理对象) -> studentService-->
1.10 Spring 配置文件全部配置
测试一下:
@Test
public void test02() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService = (StudentService) context.getBean("studentService");
List<Student> allStudents = studentService.findAllStudents();
System.out.println(allStudents.size());
for (Student student : allStudents) {
System.out.println(student);
}
}
注意事项:
前面的jdbc的配置文件里面需要加上jdbc.的前缀,否则,这个找到的是本机的username,而不是mysql的用户名
此处可以看到拿到的并不是mysql的用户,而是电脑主机的用户名。
再次压缩一下:
原来的mybatis-config文件里面的内容可以整合一下
整合完之后删除mybatis-config文件
前面的第四部可以使用注解的方式来做
实际的开发环境当中。
//自定义的类就可以使用注解的方法来写注入
//非自定义的类就需要在配置文件里面注入
二 Spring 事务
2.1 Spring 的事务管理
事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
(1)使用 Spring 的事务注解管理事务
(2)使用 AspectJ 的 AOP配置管理事务
2.2 Spring 事务管理 API
Spring 的事务管理,主要用到两个事务相关的接口。
(1) 事务管理器接口(重点)
事务管理器是 PlatformTransactionManager 接口对象。
其主要用于完成事务的提交、回滚,及获取事务的状态信息。
接口无法直接使用,需要实现类,实际使用的是下面的两个类
A、 常用的两个实现类
PlatformTransactionManager 接口有两个常用的实现类
-
DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
-
HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
B、Spring 的回滚方式(理解)
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
C、回顾错误与异常(理解)
Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类 (或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。
- Error:是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError、ThreadDeath、NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出)的,JVM 一般会终止线程。
- Exception:程序在编译和运行时出现的另一类错误称之为异常,它是 JVM 通知程序员的一种方式。通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。
异常分为运行时异常与受查异常。
运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如,NullPointerException、ArrayIndexOutOfBoundsException、 IllegalArgumentException 等均属于运行时异常。这些异常由JVM抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码编写足够仔细,程序足够健壮,运行时异常是可以避免的。【因此,运行时异常,也叫非受查异常,编译器无法检查到,需要程序员通过修改代码来解决】
受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。如 SQLException, ClassNotFoundException,IOException 等都属于受查异常。
RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception 的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的为 RuntimeException 的子类,那么定义的就是受查异常。
Ø 非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求程序员必须处理这些异常。在运行阶段,倘若发生Error则虚拟机几乎崩溃,倘若发生RuntimeException若程序员没处理它则一直回溯向上抛给java虚拟机处理。当然,如果程序员愿意的话,也可以编写代码处理(使用try…catch…finally)这样的异常(但是通常情况下不会这样做。需要这样做的情况是比如搞数学运算的这个专业领域要处理ArithmeticException)。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理。这种异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
Ø 检查异常(checked exception):除了Error和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。
发生运行时异常才会回滚
Q&A:为什么添加事务管理器?
答:因为不同的技术管理事务的类不同,比如:
- JDBC:Connecton con.commit(); con.rollback();
- MyBatis:SqlSession sqlSession.commit(); sqlSession.rollback();
- Hibernate:Session session.commit(); session.rollback();
事务管理器用来生成相应技术的连接对象及执行语句。
不同框架提交事务是不同的
(2) 事务定义接口
事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:
事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。
I、事务隔离级别常量
定义了五个事务隔离级别常量(掌握)
在应用程序中,多个事务并发运行,操作相同的数据,可能会引起脏读、不可重复读、幻读等问题。
(1)脏读(Dirty read)
第一个事务访问并改写了数据,尚未提交事务,这时第二个事务进来了,读取了刚刚改写的数据,如果这时第一个事务回滚了,这样第二个事务读取到的数据就是无效的“脏数据”。
(2)不可重复读(Nonrepeatable read)
第一个事务在其生命周期内多次查询同一个数据,在两次查询之间,第二个事务访问并改写了该数据,导致第一个事务两次查询同一个数据得到的结果不一样。
(3)幻读(Phantom read)
幻读和不可重复读类似。它发生在第一个事务在其生命周期进行了两次按同一查询条件查询数据,第一次按该查询条件读取了几行数据,这时第二个事务进来了,且插入或删除了一些数据,然后第一个事务再次按同一条件查询,发现多了一些原本不存在的记录或者原有记录不见了。
为了解决并发问题,TransactionDefinition接口定义了5个事务隔离常量如下:
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
- ISOLATION_DEFAULT : 采用数据库默认的事务隔离级别 。MySql 的默认为 REPEATABLE_READ(可重复读); Oracle 默认为 READ_COMMITTED(读已提交)。 【REPEATABLE_READ存在幻读的情况,但MySQL的InnoDB解决了幻读】
- ISOLATION_READ_UNCOMMITTED:读未提交。允许另外一个事务读取到当前事务未提交的数据,隔离级别最低,未解决任何并发问题,会产生脏读,不可重复读和幻读。
- ISOLATION_READ_COMMITTED:读已提交。被一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。解决脏读,但还存在不可重复读与幻读。
- ISOLATION_REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
- ISOLATION_SERIALIZABLE:串行化读。按时间顺序一一执行多个事务,每次读都需要获得表级共享锁,读写相互都会阻塞,不存在并发问题,最可靠,但性能与效率最低。
从第2到第5,隔离级别越来越高。
II、事务传播行为常量
定义了七个事务传播行为常量(掌握)
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况(合并?互斥?)。
如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
PROPAGATION_REQUIRED :必须包含事务(增删改必用)
PROPAGATION_REQUIRES_NEW :自己新开一个事务,不管之前是否有事务
PROPAGATION_SUPPORTS :支持事务,如果加入的方法有事务,则支持事务;如果没有,不单开事务
PROPAGATION_NEVER :不能运行在事务中,如果包在事务中,抛异常
PROPAGATION_NOT_SUPPORTED :不支持事务,运行在非事务环境中,如果加入的方法有事务,则会把事务先挂起【不常用】
PROPAGATION_MANDATORY :必须包在事务中,没有事务则抛异常
PROPAGATION_NESTED:嵌套事务
最后的两个不怎么常用,了解一下即可。
a、 PROPAGATION_REQUIRED
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中; 若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther() 方法时就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
b、PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
c、 PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
III、事务默认超时时限常量【了解即可】
定义了默认事务超时时限
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置的none值。
注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。
2.3 程序举例环境搭建
举例:购买商品 trans_sale 项目
本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存。
实现步骤:
Step0:创建数据库表
创建两个数据库表 sale , goods
sale 销售表
goods 商品表
goods 表数据
创建项目:
Step1: maven 依赖 pom.xml
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
</dependencies>
<!--插件:-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
Step2:创建实体类
创建实体类 Sale 与 Goods
goods实体类
package com.ambow.pojo;
//dao是数据访问层
//service是业务层
public class Goods {
private Integer id;
private String nname;
private Integer amount;
private float price;
}
sale实体类
package com.ambow.pojo;
public class Sale {
private Integer id;
private Integer gid;
private Integer nums;
}
Step3:定义 dao 接口
定义两个 dao 的接口 SaleDao , GoodsDao
goodsdao数据访问层
package com.ambow.dao;
import com.ambow.pojo.Goods;
public interface GoodsDao {
int updateGoods(Goods goods);
Goods selectGoodsById(int id);
}
saledao数据访问层:
package com.ambow.dao;
import com.ambow.pojo.Sale;
public interface SaleDao {
int insertSale(Sale sale);
}
Step4:定义 dao 接口对应的 sql 映射文件
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.ambow.dao.SaleDao">
<insert id="insertSale">
insert into sale (gid,nums) values (#{gid},#{nums})
</insert>
</mapper>
GoodsDao.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.ambow.dao.GoodsDao">
<update id="updateGoods">
update goods
set amount = amount - #{amount}
where id = #{id};
</update>
<select id="selectGoodsById" resultType="com.ambow.pojo.Goods">
select * from goods where id = #{id}
</select>
</mapper>
update返回的是影响的行数
业务描述:
Step5:定义异常类
定义 service 层可能会抛出的异常类 NotEnoughException
package com.ambow.exception;
//库存不足的时候的异常抛出
public class NotEnoughException extends RuntimeException{
public NotEnoughException(){
super();
}
public NotEnoughException(String msg){
super(msg);
}
}
Step6:定义 Service 接口
定义 Service 接口 BuyGoodsService
package com.ambow.service;
public interface BuyGoodsService {
public void buy(Integer goodsId,Integer amount);
}
Step7:定义 service 的实现类
定义 service 层接口的实现类 BuyGoodsServiceImpl
1)类定义
2)Dao 属性
3)Buy 方法
完整代码段:
package com.ambow.service.impl;
import com.ambow.dao.GoodsDao;
import com.ambow.dao.SaleDao;
import com.ambow.exception.NotEnoughException;
import com.ambow.pojo.Goods;
import com.ambow.pojo.Sale;
import com.ambow.service.BuyGoodsService;
import org.springframework.transaction.annotation.Transactional;
public class BuyGoodsServiceImpl implements BuyGoodsService {
//1.SaleDao - 添加销售记录
private SaleDao saleDao;
//2.GoodsDao - 修改库存
private GoodsDao goodsDao;
//添加两个set方法
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
//购买商品
@Transactional
@Override
public void buy(Integer goodsId, Integer amount) {
//1.添加销售记录
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(amount);
saleDao.insertSale(sale);
//验证商品库存是否存在和商品的库存是否不足
Goods goods = goodsDao.selectGoodsById(goodsId);
if (goods == null){
throw new NullPointerException("无此商品");
}
if (goods.getAmount() < amount){
throw new NotEnoughException("库存不足");
}
//2.修改库存
goods = new Goods();
goods.setId(goodsId);
goods.setAmount(amount);
goodsDao.updateGoods(goods);
}
}
Step8:修改 Spring 配置文件内容
声明Mybatis 对象
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--整合:把MyBatis中的核心对象,放到Spring容器-->
<!--引入属性配置文件-->
<context:property-placeholder location="classpath:jdbc.properties" />
<context:component-scan base-package="com.ambow.service" />
<!--1.DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!--2.注册SqlSessionFactoryBean-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.ambow.pojo" />
<property name="mapperLocations" value="classpath*:com/ambow/dao/*.xml" />
</bean>
<!--3.mapper的扫描配置器 -> 生成mapper的代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.ambow.dao" />
</bean>
<!--4.向service层注入Dao-->
<bean id="buyGoodsService" class="com.ambow.service.impl.BuyGoodsServiceImpl">
<property name="saleDao" ref="saleDao" />
<property name="goodsDao" ref="goodsDao" />
</bean>
<!--注入的过程-->
<!--dataSource -> SqlSessionFactoryBean -> MapperScannerConfigurer(生成Dao代理对象) -> studentService-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--声明事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
声明业务层对象
Step9:定义测试类
定义测试类 MyTest。现在就可以在无事务代理的情况下运行了。
原先的库存数量:
执行测试代码:
package com.ambow.test;
import com.ambow.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TransTest {
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BuyGoodsService buyGoodsService = (BuyGoodsService) context.getBean("buyGoodsService");
buyGoodsService.buy(1001,15);//正常购买
// buyGoodsService.buy(1008,10);//无此商品
//buyGoodsService.buy(1001,100);//库存不足
}
}
数据库里面的数量:
对应的sale表里面:
另外一种情况:
当没有此商品编号时会出现异常
最后一种情况:
当此商品库存不足的时候也会出现异常
2.4 使用 Spring 的事务注解管理事务(掌握)
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
@Transactional 的所有可选属性如下所示:
-
propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举, 默认值为 Propagation.REQUIRED。
-
isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认 值为 Isolation.DEFAULT。
-
readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false【可修改】。
-
timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int, 默认值为-1,即没有时限。
-
rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
-
rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默 认值为空数组。当然,若只有一个异常类时,可以不使用数组。
-
noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数 组。当然,若只有一个异常类时,可以不使用数组。
-
noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[], 默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- 需要注意的是,@Transactional 若用在方法上,只能用于 public 方法 上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。
- 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
实现注解的事务步骤:
复制 trans_sale 项目,新项目 trans_sale_annotation
1. 声明事务管理器
要实现事务管理,需要先添加事务管理器声明。
基于mybatis的项目需要使用datasourcetransactionmanager,此处的属性四指定数据源
2. 开启注解驱动 transaction-manager:
上述的内容在前面2.3.8里面已经写了
此处注意选择第四个tx开头的这个
事务管理器 bean 的 id与1.相同
3. 业务层 public 方法加入事务属性
第一句是需要有事务
此处事务回滚是发生库存不足和商品为空时的class
此处可以直接写下面注释的那部分即可
//默认的已有rollbackfor事务回滚,此处可以直接省略
@Transactional(Propagation = Propagation.REQUIRED,
rollbackFor = {NotEnoughException.class,
NullPointerException.class})
// @Transactional
2.5 使用 AspectJ 的 AOP 配置管理事务(掌握)
使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代 理。当目标类较多,配置文件会变得非常臃肿。
使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代 理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如 下内容即可。
Step1:复制项目
复制 trans_sale 项目,并重命名为 trans_sal_aspectj。在此基础上修改。此处直接新建一个项目
Step2:maven 依赖 pom.xml
新加入 aspectj 的依赖坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.26</version>
</dependency>
Step3:在容器中添加事务管理器
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Step4:配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。 例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。
可以将全部的都在此处配置,配置完成之后,修改起来只要修改此处即可
<!--声明事务的注解驱动-->
<!--<tx:annotation-driven transaction-manager="transactionManager" />-->
<!--添加通知 通知的ID是购买的参数-->
<tx:advice id="buyAdvice" transaction-manager="transactionManager">
<!-- 增强 先添加事务propagation 其isolation选择默认即可 回滚直接写类名,还有我们自己创建的一个事务回滚 两个事务之间使用逗号隔开-->
<tx:attributes>
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.ambow.exception.NotEnoughException"/>
<!--在开发时有许多的add方法,直接写全部的,在发生异常时全部回滚-->
<tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.Exception"/>
<!--给所有的方法度加入method-->
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
Step5:配置增强器
指定将配置好的事务通知,织入给谁。【次粗通过aop织入】
此处指定切入点表达式,指定传进来的包【此处为任意包里面的任意service里面的任意方法,以及对应的方法名】,再加入增强的通知
<aop:config>
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<aop:advisor advice-ref="buyAdvice" pointcut-ref="servicePt" />
</aop:config>
Step6:修改测试类
测试类中要从容器中获取的是目标对象。
此处将原来的注解方式先注释,接着运行测试类可以看到aop的方式也可以
接着运行测试即可
总结
以上就是今天的内容~
欢迎大家点赞👍,收藏⭐,转发🚀,
如有问题、建议,请您在评论区留言💬哦。
最后:转载请注明出处!!