一、Spring 概述
1.1、什么是Spring框架:
- Spring框架就是整合其它框架的框架,核心是IOC和AOP,由20多个模块组成,Spring、SpringMVC、SpringBoot、SpringCloud都属于它的模块
1.2、Spring的特点:
- 轻量级:
- 由20多个模块组成,每个jar包小于1M,核心jar包也就3M左右,对代码无污染。
- 面向接口编程:
- 使用接口,项目的可拓展性,可维护行极高,使用时接口指向实现类,切换功能只需要切换指向的实现类即可
- AOP面向切面编程:
- 将公共的,通用的,重复的代码、业务逻辑单独开发,在使用的时候再反织回去。底层是动态代理。
- 整合其它框架:
- 整合其它框架后使其它框架更好用。
1.3、什么是控制反转IOC:
- 控制权交给Spring,Spring容器来控制对象的创建以及对象和对象之间的依赖关系(A对象含有B对象的实例)
- 正转:程序员控制对象创建 Student stu=new Student()
- 反转:Spring容器控制对象的创建。例如:
<bean id="stu" class="com.lhl.pojo.Student"><bean>
1.4、什么是依赖注入:
- 程序运行期间,依赖于外部容器给对象的属性赋值
1.5、什么是AOP
- AOP就是面向切面编程,切面就是重复的,
二、IOC 控制反转
2.1、Spring入门程序
2.1.1、创建maven项目添加依赖
<dependencies>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
2.1.2、定义实体类
package com.lhl.pojo;
public class Student {
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
}
2.1.3、编写spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建对象 调用构造方法-->
<bean id="student" class="com.lhl.pojo.Student"></bean>
</beans>
2.1.4、测试程序
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) ac.getBean("student");
System.out.println(student);
}
2.2、基于XML的DI
2.2.1、set注入
- 使用set注入必须提供set方法和无参构造器
- 定义实体类Student和School
public class Student {
private String name;
private int age;
private School school;
//setter
//toString
}
public class School {
private String name;
private String address;
//setter
//toString
}
2.2.1.1、简单类型注入
<!--
id="student" 对象的引用 ==> Student student = new Student()
class="com.lhl.pojo.Student" ==> 创建对象的类型
name="name" value="张三" ==> 给对象的name属性赋值张三
-->
<bean id="student" class="com.lhl.pojo.Student">
<property name="name" value="张三"/>
<property name="age" value="23"/>
</bean>
2.2.1.2、引用类型注入
<!--
name="school" ref="school" ==> 引用类型赋值
-->
<bean id="student" class="com.lhl.pojo.Student">
<property name="school" ref="school"/>
</bean>
2.2.2、构造方法注入
- 必须提供相应的有参构造方法
public class Student {
private String name;
private int age;
private School school;
public Student(String name, int age, School school) {
this.name = name;
this.age = age;
this.school = school;
}
}
public class School {
private String name;
private String address;
public School(String name, String address) {
this.name = name;
this.address = address;
}
}
2.2.2.1、使用构造方法的参数名
<bean id="school" class="com.lhl.pojo.School">
<property name="name" value="清华大学"/>
<property name="address" value="北京海淀"/>
</bean>
<bean id="student" class="com.lhl.pojo.Student">
<constructor-arg name="name" value="李四"/>
<constructor-arg name="age" value="10"/>
<constructor-arg name="school" ref="school"/>
</bean>
2.2.2.2、使用构造方法的下标
<bean id="student" class="com.lhl.pojo.Student">
<constructor-arg index="0" value="王五"/>
<constructor-arg index="1" value="20"/>
<constructor-arg index="2" ref="school"/>
</bean>
2.2.2.3、使用默认顺序
<bean id="student" class="com.lhl.pojo.Student">
<constructor-arg value="赵六"/>
<constructor-arg value="18"/>
<constructor-arg ref="school"/>
</bean>
2.2.3、引用类型自动注入
- 通过bean标签的autowrie属性为引用类型隐式的自动注入
2.2.3.1、byName 方式自动注入
- 底层通过调用set()方法进行注入,没有set()方法就无法注入
<!--
autowire="byName" ==> 为引用类型属性自动注入与bean的id值一致的对象
-->
<bean id="student" class="com.lhl.pojo.Student" autowire="byName"/>
<property name="name" value="张三"/>
<property name="age" value="23"/>
<!--<property name="school" ref="school"/>-->
</bean>
2.2.3.2、byType 方式自动注入
-
底层也是调用set()方法进行注入,没有set()方法就无法注入
-
通过byType方式,配置文件中的School类型的Bean必须是唯一的,出现多个无法注入。
-
注入类型也可以是School类型的子类或实现类
<!--
autowire="byType" ==> 注入类型也可以是School类型的子类或实现类
-->
<bean id="student" class="com.lhl.pojo.Student" autowire="byType">
<property name="name" value="小明"/>
<property name="age" value="10"/>
<!--<property name="school" ref="school"/>-->
</bean>
2.3、基于注解的DI
2.3.1、定义Bean的注解
- 以下四个注解都是创建对象,@Controller、@Service、@Repository这三个注解都是@Component注解的别名。为了增强可读性,建议分层使用。它们都有一个value属性用来指定bean的id,不指定value属性对象名默认是类型首字母小写。
- @Component:创建所有对象都可以使用
- @Controller:建议使用在控制层
- @Service:建议使用在业务层
- Repository:建议使用在数据访问层
//@Component("student")
//@Component(value = "student")
//@Controller
//@Service
//@Repository
@Component // ==> 默认对象名是类名首字母小写
public class Student {}
2.3.2、使用注解创建对象步骤
- 在配置文件中添加包扫描
<!--spring容器会扫描该包及其子包下的注解-->
<context:component-scan base-package="com.lhl.pojo"/>
- 在类上使用注解
@Component
public class Student {
}
2.3.3、简单类型注入@Value
- @Value注解可以出现在属性上、setter方法上,spring6可以出现在构造器的形参上。出现在属性上可以没有setter方法仍然可以注入,@Value给八种基本类型和String注入值。
public class Student {
@Value("张三")
private String name;
@Value("20")
private int age;
}
2.3.4、byType 自动注入@AutoWried
- @AutoWried默认按照类型注入,如果同类型存在多个,则按照属性名进行二次匹配,匹配到则注入,没有匹配到则报错。
- @Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
//@Autowired(required = false)
@Autowired // 自动注入与Schoo类型相同的对象
private School school;
2.3.5、byName 自动注入@Qualifier
- @Autowired 与@Qualifier联合使用。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
@Autowired
@Qualifier("school") // 自动注入id为school的School类对象
private School school;
2.4、Spring 配置文件的整合
1、按层拆分
applicationContext_controller.xml
<!--创建界面层对象-->
<bean id="userController" class="com.lhl.controller.UserController">
<property name="userService" ref="userService"/>
</bean>
applicationContext_dao.xml
<!--创建数据访问层对象-->
<bean id="userMapper" class="com.lhl.dao.impl.UserMapperImpl">
</bean>
- applicationContext_service.xml
<!--创建业务层对象-->
<bean id="userService" class="com.lhl.service.impl.UserServiceImpl">
<property name="userMapper" ref="userMapper"/>
</bean>
- 整合``total.xml`
<!--批量导入-->
<import resource="applicationContext_*.xml"/>
2、按功能拆分
applicationContext_users.xml
<bean id="uController" class="com.bjpowernode.controller.UsersController">
<bean id="uService" class="com.bjpowernode.controller.UsersService">
<bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
applicationContext_book.xml
<bean id="bController" class="com.bjpowernode.controller.BookController">
<bean id="bService" class="com.bjpowernode.controller.BookService">
<bean id="bMapper" class="com.bjpowernode.controller.BookMapper">
- 整合
total.xml
<import resource="applicatoinContext_*.xml"></import>
三、Spring AOP
3.1、AOP概述
- 动态代理其实就是AOP思想的实现。
- AOP(Aspect Oriented Programming):面向切面编程,是一种思想,底层是通过动态代理实现的
- 面向切面编程:将与业务逻辑无关的通用的、重复的代码单独提取出来形成独立的组件,比如:事务、日志、安全验证等交叉业务。在执行业务逻辑时,需要它就以横向交叉的方式应用到业务流程当中。
- Spring AOP使用的动态代理:JDK动态代理+CGLIB动态代理,如果代理的是接口,使用的是JDK动态代理。如果代理的是类,使用的是CGLIB动态代理。可以通过配置强制全部使用CGLIB动态代理。
3.2、Spring 的通知类型
- Spring支持AOP的编程,常用的有以下几种:
- Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
- After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;
- Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;
- Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。
3.3、AOP 编程术语
- **1.切面(Aspect):**就是交叉的业务。重复的、公共的、通用的功能。例如:日志、事务、权限。
- **2.连接点(Joinpoint):**就是目标方法。
- 3.切入点(Pointcut):指定切入的位置,即指定切入到哪个方法或哪些方法。多个连接点构成切入点。切入点可以是目标方法,可以是一个类中的所有方法,某个包下的所有类的方法等等。
- 4.通知(Advice):就是切面方法或者说切面功能。是在目标方法执行前还是执行后还是出错时,还是环绕目标方法切入切面功能。
- 5.目标对象(Target):操作谁谁就是目标对象
- 6.代理对象(Proxy):目标对象织入通知后产生的新对象
- 7.织入(Weaving):把通知应用到目标对象上的过程
3.4、AspectJ 对AOP的实现
3.4.1、AspectJ 的通知类型
- 前置通知@Before
- 后置通知@AfterReturning
- 环绕通知@Around
- 最终通知@After
3.4.2、AspectJ的切入点表达式
- 切入点表达式就是切入目标方法的方法名
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
- []中的内容表示可有可无,所以可以简化为:
execution(返回值类型 方法名(形式参数列表))
- 各部分可以使用一下符号
- * 代码任意个任意的字符(通配符)
- … 如果出现在方法的参数中,则代表任意参数。如果出现在路径中,则代表本路径及其所有的子路径
- 实例:
execution(public * *(..)) //任意的公共方法
execution(* set*(..)) //任何一个以“set”开始的方法
execution(* com.xyz.service.impl.*.*(..)) //在com.xyz.service.impl包下的所有类的所有方法
execution(* com.xyz.service..*.*(..)): //在com.xyz.service及其子包下的所有类的所有方法
execution(* *..service.*.*(..)) //serrvice之前可以有任意的子包
execution(* *.service.*.*(..)) //service只能是二级包下的所有类的所有方法
3.4.3、AspectJ基于注解式AOP
3.4.3.1、实现步骤
- pom.xml文件引入依赖
<dependencies>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
- 定义目标类和切面类,并使用注解创建对象和在目标方法加入通知
//目标类
@Service
public class TargetServiceImpl implements TargetService {
//目标方法
@Override
public void targetMain() {
System.out.println("目标类中的目标方法");
}
}
//切面类
@Aspect//声明该类为切面类,交给AspectJ框架去识别切面类,来进行切面方法的调用
@Component
public class MyAspect {
//切面方法
//切点表达式
@Before(value = "execution(public void targetMain())")
public void aspectMain(){
System.out.println("切面类中的切面方法");
}
}
- 编写spring配置文件
<?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: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--添加包扫描-->
<context:component-scan base-package="com.lhl"/>
<!--开启自动代理-->
<!--
<aop:aspectj-autoproxy/>
默认proxy-target-class="false"表示使用jdk动态代理
proxy-target-class="true" 表示使用CGLIB动态代理
-->
<aop:aspectj-autoproxy/>
</beans>
- 在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。
- 测试程序
//测试类
public class MyTest {
@Test
public void test(){
//创建容器并生成对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取切入后的目标类的代理类
TargetService proxy = (TargetService) ac.getBean("targetServiceImpl");
//执行增强功能的目标方法
proxy.targetMain();
}
}
3.4.3.2、@Before前置通知
/**
* 在目标方法执行之前执行
* 前置通知方法规范:
* 1.访问权限是public
* 2.方法没有返回值void
* 3.方法的名字随意
* 4.方法可以没有参数,如果有只能是JoinPoint类型
* 该JoinPoin类型参数就是切入点表达式==>execution(public void targetMain())
* 5.@Before注解表示是加入目标方法的前面:
* value:指定切入点表达式,即目标方法
*/
@Before(value = "execution(public void targetMain())")
public void myBefore(JoinPoint joinPoint){
System.out.println("我是前置通知");
System.out.println("目标方法名:" + joinPoint.getSignature());
System.out.println("目标方法参数:" + joinPoint.getArgs());
System.out.println("目标方法对象" + joinPoint.getTarget());
}
3.4.3.3、@AfterReturning 后置通知
/**
* 在目标方法后执行
* 后置通知方法规范:
* 1.方法访问权限public
* 2.方法没有返回值void
* 3.方法名自定义
* 4.方法参数可以目标方法的返回值,可以没有,也可以包含JoinPoint参数(它必须是第一个参数)
* 5.使用@AfterReturning注解:
* 参数value:指定切入点表达式
* returning:指定目标方法返回值的形参名称,此名称必须与切面方法参数名称一致
* returning = "obj" ==> obj = 目标方法返回值
*/
@AfterReturning(value = "execution(public void targetMain())",returning = "obj")
public void myAfterReturning(Object obj){
System.out.println("我是后置通知");
/*
在切面方法中可以对目标方法的返回值进行操作
目标方法返回值是八大基本类型或String,则无法改变目标方法的返回值
目标方法的返回值是引用类型,则可以改变目标方法的返回值
*/
System.out.println("目标方法返回值:"+ obj);
}
//也可以之加入后切方法,不对目标方法返回值操作
@AfterReturning(value = "execution(public void targetMain())")
public void myAfterReturning(){
System.out.println("我是后置通知");
}
}
3.4.3.4、@Around 环绕通知
/**
* 环绕通知方法的规范
* 1.访问权限是public
* 2.方法返回值可有可无,和目标方法一致,需要就返回
* 3.方法名自定义
* 4.方法有参数,参数就是目标方法,它是ProceedingJoinPoint接口类型
* 该接口的proceed()方法用于执行目标方法,返回值是目标方法返回值
* 5.必须抛出异常
* 6.环绕通知其实是拦截了目标方法
*/
@Around(value = "execution(public void targetMain())")
public void myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//在目标方法前加入功能
System.out.println("前环绕");
//执行目标方法并获取返回值
Object returnNum = proceedingJoinPoint.proceed();
//在目标方法后加入功能
System.out.println("后环绕");
/*
return returnNum; 该切面方法可以返回值,此时方法返回值类型最好为Object
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
*/
}
3.4.3.5、@After 最终通知
/**
* 无论怎样都会执行该方法
* 最终方法的规范
* 1.访问权限是public
* 2.方法没有返回值
* 3.方法名自定义
* 4.方法参数可有可无,有参数就是JoinPoint类型,就是切入点表达式
* 5.使用 @After
* value:指定切入点表达式
*/
@After(value = "execution(public void targetMain())")
public void myAfter(){
System.out.println("最终通知");
}
3.4.3.6、@Pointcut 定义切入点别名
- 当很多切面方法使用相同的切入点表达式时,可以使用@Pointcut注解给表达式起别名
//切面类
@Aspect
@Component
public class MyAspect {
//使用注解的方法的方法名就是切入表达式的别名,一般使用private,因为这个方法没用
@Pointcut(value = "execution(public void targetMain())")
public void pointCut(){}
@Before(value = "pointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.println("我是前置通知");
}
@AfterReturning(value = "pointCut()")
public void myAfterReturning(){
System.out.println("我是后置通知");
}
@Around(value = "pointCut()")
public void myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("前环绕");
Object returnNum = proceedingJoinPoint.proceed();
System.out.println("后环绕");
}
@After(value = "pointCut()")
public void myAfter(){
System.out.println("最终通知");
}
}
3.4.3.6、@Order定义切面的优先级
- 可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。
@Aspect
@Component
@Order(1) //设置优先级
public class YourAspect {
}
3.4.4、基于XML的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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--纳入spring bean管理-->
<bean id="vipService" class="com.powernode.spring6.service.VipService"/>
<bean id="timerAspect" class="com.powernode.spring6.service.TimerAspect"/>
<!--aop配置-->
<aop:config>
<!--切点表达式-->
<aop:pointcut id="p" expression="execution(* com.powernode.spring6.service.VipService.*(..))"/>
<!--切面-->
<aop:aspect ref="timerAspect">
<!--切面=通知 + 切点-->
<aop:around method="time" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>
四、Spring整合MyBatis
- 整合步骤:
- IDEA新建maven项目,pom.xml文件中添加各种依赖
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring核心ioc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--做spring事务用到的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--事务底层使用jdbc依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!--mybatis和spring集成的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--阿里公司的数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
- 基于三层架构,创建所有的包,并创建pojo类、mapper接口、service接口及实现类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-haOtlmOR-1684911731066)(C:\Users\lhl\AppData\Roaming\Typora\typora-user-images\image-20230522103359427.png)]
- 编写sqlMapper.xml文件,Mybatis配置文件、jdbc.properties文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrHwrmf8-1684911731068)(C:\Users\lhl\AppData\Roaming\Typora\typora-user-images\image-20230522103505583.png)]
- 编写spring核心配置文件
-
核心配置文件可以写在一个xml文件中,但最好分开写,每一层对应一个spring配置文件。
-
applicationContext_mapper.xml配置文件
<!--读取外部数据库配置文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--创建数据源-->
<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>
<!--注册SqlSessionFactoryBean对象,给pojo类起别名,注册MyBatis核心配置文件-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注册MyBatis核心配置文件-->
<property name="configLocation" value="mybatis-config.xml"/>
<!--配置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--注册实体类,给pojo包下的类起别名-->
<property name="typeAliasesPackage" value="com.lhl.pojo"/>
</bean>
<!--注册Sqlmapper文件,MapperScannerConfigurer这个类生成Mapper动态代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.lhl.mapper"/>
</bean>
- applicationContext_service.xml
<!--引入applicationContext_mapper.xml spring配置文件-->
<import resource="applicationContext_mapper.xml"/>
<!--使用注解创建对象,添加包扫描,扫描该包下的注解-->
<context:component-scan base-package="com.lhl"/>
五、Spring 事务
5.1、Spring 事务管理API
- Spring对事务的管理底层实现方式是基于AOP实现的。采用AOP的方式进行了封装。
- PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:
- DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
- JtaTransactionManager:支持分布式事务管理。
5.2、Spring 事务的五大隔离级别
- 这些常量都是以Isolation_开头。形如: Isolation_XXX
-
默认(DEFAULT) :采用默认隔离级别。MySQL默认为REPEATABLE_READ(可重复读),Oracle默认为READ_COMMITTED(读已提交)。
-
读未提交(READ_UNCOMMITTED):存在脏读、不可重复读和幻读。
-
读已提交(READ_COMMITTED):解决脏读,存在不可重复读和幻读。
-
可重复读(REPEATABLE_READ):解决脏读、不可重复读。存在幻读。
-
串行化(SERIALIZABLE):不存在并发问题
- 读取数据库的三大问题:
- 脏读:一个事务读取到另一个事务没有提交(增删改)的数据
- 不可重复读:在一一个事务中,因为其他事务所提交的修改和删除,第一次查询和第二次查询数据不一致。
- 幻读:在一个事务中,因为其他事务提交的插入操作,每次读取的数据不一致
5.3、Spring 事务的七大传播特性
- 所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如:A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
- 事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
- REQUIRED:必须开启事务,加入的方法有事务就加入,没有就新建。
- REQUIRES_NEW:开启新事务,无论有没有事务都开启新事务,将之前的事务挂起,不存在事务嵌套。
- SUPPORTS:支持当前事务,有就支持,没有也不新建。
- NEVER:以非事务运行,如果有事务存在,抛出异常。
- NOT_SUPPORTED:不支持事务,以非事务方式运行,之前有事务就将其挂起。
- MANDATORY :必须运行在事务当中,如果有就使用当前事务。
- NESTED:有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。
5.4、声明式事务之注解方式
-
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
-
@Transactional有几点需要注意
- 只能声明在public的method。原因是spring是通过JDK代理或者CGLIB代理的,生成的代理类,只能处理public方法,注解放在类名称上面,这样你配置的这个@Transactional 对这个类中的所有public方法都起作用,@Transactional 在方法名上,只对这个方法有作用,同样必须是public的方法。
- 不能被类内部方法调用。还是因为代理的原因,类内部自调用,不会经过代理类,所以@Transactional不会生效
-
配置事务注解驱动
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--ref="dataSource" ==> 引入的applicationContext_mapper.xml文件中的数据源对象-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--添加事务驱动,即开启事务注解,使用事务注解放假添加事务-->
<!--<tx:annotation-driven> 也可以这样写==> <tx:annotation-driven transaction-manager="事务管理器对象名字">-->
<tx:annotation-driven></tx:annotation-driven>
- 在类或方法上添加@Transactional设置属性
@Transactional //使用默认方式
/*@Transactional(propagation = Propagation.REQUIRED,//设置事务的传播特性,默认为Propagation.REQUIRED
isolation = Isolation.DEFAULT,//设置事务的隔离级别,默认为Isolation.DEFAULT
readOnly = true,//设置事务为只读事务,只允许select语句执行默认值是false
timeout = 10,//设置事务的超时时间,只计算DML语句执行时间,超过10秒DML语句没有执行完毕,就会回滚。默认是-1永不超时
rollbackFor = RuntimeException.class,//方法抛出运行异常就回滚。
rollbackForClassName = "RuntimeException",//方法抛出运行异常就回滚。
noRollbackFor = ArithmeticException.class,//方法遇到算术异常不回滚。
noRollbackForClassName = "ArithmeticException"//方法遇到算数异常不回滚
)*/
public int insert(Accounts accounts) {
int count = accountsMapper.insertAccount(accounts);
return count;
}
5.5、声明式事务之XML方式
<!--声明式事务的配置 为事务管理添加数据源-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--使用xml方式声明事务-->
<!--配置切面的属性,哪些方法需要添加什么事务传播特性-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/> <!--所有以get开头命名的方法设为只读-->
<tx:method name="select*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="search*" read-only="true"/>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>
<tx:method name="insert*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="clean*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="set*" propagation="REQUIRED"/>
<tx:method name="change*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!--使用AOP的技术进行切入点织入-->
<aop:config >
<!--切入点表达式:指定在哪个包下的哪些类中的哪些方法添加事务处理-->
<aop:pointcut id="pointcat" expression="execution(* com.bjpowernode.service.*.*(..))"></aop:pointcut>
<!--完成切面与切入点绑定-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcat"></aop:advisor>
</aop:config>