Spring 学习记录
- 1. Spring和SpringFrameWork
- 1.1 广义的Spring
- 2.1 狭义的Spring
- 2.3 SpringFrameWork / Spring框架图
- 2. Spring IOC容器(即上图中的Core Container)
- 2.1 相关概念 (IOC DI 容器 组件)
- 2.2 Spring IOC容器的作用
- 2.3 Spring IOC容器接口和具体实现类
- 3. Spring IOC 实践
- 3.1 IOC / DI 一般步骤
- 3.2 基于 XML配置 方法
- 3.2.1 组件信息声明配置 (IOC) 和 依赖注入配置 (DI)
- 3.2.2 IOC容器创建和使用
- 3.2.3 组件周期方法配置
- 3.2.4 FactoryBean的使用
- 3.3 基于 注解配置 方法
- 3.3.1 Spring提供的常见注解
- 3.3.2 注解中BeanName的问题
- 3.3.2 Autowired 和 Resourc 注解
- 3.4 基于 配置类 方法
- 3.5 三种方法总结
- 4. Spring AOP
- 4.1 AOP是什么
- 4.2 为什么引入AOP
- 4.3 主要应用场景
- 4.4 Spring AOP 基于注解实现
- 4.4.1 Spring AOP 底层技术组成
- 4.3.2 通知增强的类型
- 4.4.3 切点表达式
- 4.4.4 代码示例
- 5.Spring-tx
- 5.1什么是Spring-tx?为什么需要Spring-tx?
- 5.2 代码示例
- 5.3 事务属性
1. Spring和SpringFrameWork
通常情况下,我们所说的Spring是狭义概念的Spring,即SpringFrameWork框架。
1.1 广义的Spring
广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。
经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。
2.1 狭义的Spring
狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。
Spring Framework(Spring框架)是一个开源的应用程序框架,由SpringSource公司开发,最初是为了解决企业级开发中各种常见问题而创建的。它提供了很多功能,例如:依赖注入(Dependency Injection)、面向切面编程(AOP)、声明式事务管理(TX)等。其主要目标是使企业级应用程序的开发变得更加简单和快速,并且Spring框架被广泛应用于Java企业开发领域。
2.3 SpringFrameWork / Spring框架图
2. Spring IOC容器(即上图中的Core Container)
2.1 相关概念 (IOC DI 容器 组件)
- IOC:Inversion of Control(控制反转)
主要是针对对象的创建和调用控制而言的。当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权 。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。 - DI: Dependency Injection(依赖注入)
组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。例如在A类内部需要引用B类,不需要A类的内部创建B类的对象,而是在容器内完成。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入 - IOC容器:IOC是是一种思想而不是某种技术 简而言之即对象不再由应用程序创建,而是由另外的独立的模块来创建,我们把这个独立的模块称为容器,由于应用了IOC思想,所以叫做IOC容器。Spring框架应用了IOC思想将对象的创建放在了独立的模块中,称为Spring IOC容器。
- 组件:简而言之,组件是对象。但是对象不一定是组件
2.2 Spring IOC容器的作用
Spring 框架中的IOC容器负责创建组件、管理组件的依赖关系、存储组件,销毁组件。减少编码压力,让程序员更加专注进行业务编写。
容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这些组件之间丰富的相互依赖关系。
配置元数据的方式:
- XML配置方式:是Spring框架最早的配置方式之一,通过在XML文件中定义Bean及其依赖关系、Bean的作用域等信息,让Spring IoC容器来管理Bean之间的依赖关系。该方式从Spring框架的第一版开始提供支持。
- 注解方式:从Spring 2.5版本开始提供支持,可以通过在Bean类上使用注解来代替XML配置文件中的配置信息。通过在Bean类上加上相应的注解(如@Component, @Service, @Autowired等),将Bean注册到Spring IoC容器中,这样Spring IoC容器就可以管理这些Bean之间的依赖关系。
- Java配置类方式:从Spring 3.0版本开始提供支持,通过Java类来定义Bean、Bean之间的依赖关系和配置信息,从而代替XML配置文件的方式。Java配置类是一种使用Java编写配置信息的方式,通过@Configuration、@Bean等注解来实现Bean和依赖关系的配置。
2.3 Spring IOC容器接口和具体实现类
- 接口:BeanFactory接口提供了配置框架和基本功能,ApplicationContext接口继承自BeanFactory接口,添加了更多特定于企业的功能。
- 实现类:以下实现类都继承了某些父类,这些父类实现了ApplicatiContext接口。
例如:ClassPathXmlApplicationContext的类继承关系如下:引用自该文章
3. Spring IOC 实践
3.1 IOC / DI 一般步骤
元数据配置——容器读取配置并实例化——获取对应的组件
元数据配置:此步骤只是进行配置而不进行具体的实例化
容器读取配置并实例化组件:此步骤容器会对配置读取进而实例化组件
获取对应的组件:此步骤是获取所需要的组件
3.2 基于 XML配置 方法
3.2.1 组件信息声明配置 (IOC) 和 依赖注入配置 (DI)
- 基于无参构造函数
public class HappyComponent {
//默认包含无参数构造函数
public void doWork() {
System.out.println("HappyComponent.doWork");
}
}
...
<bean id="happyComponent" class="com.local.ioc_01.HappyComponent"/>
- 基于有参的构造函数
如果是基本类型,就用value。如果是引用类型,就用ref。所ref的类型要在配置文件中已经配置过,并且ref的是对应类型的id。
public class UserDao {
}
public class UserService {
private UserDao userDao;
private int age;
private String name;
public UserService(int age , String name ,UserDao userDao) {
this.userDao = userDao;
this.age = age;
this.name = name;
}
}
<bean id="userDao" class="com.local.ioc_01.UserDao"/>
<bean id="userService" class="com.local.ioc_01.UserService">
<constructor-arg name="name" value="zzz"/>
<constructor-arg name="age" value="12"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
- 基于静态工厂方法(创建对象的方法是静态方法)
注:工厂方法——在方法内部创建对象,外部访问的时候只需要访问方法就可以,类似在“工厂”内部对对象进行创建,而使用者不用关心在工厂内部发生了什么。
factory-method: 指定静态工厂方法,注意,该方法必须是static方法。
public class ClientService {
public static ClientService createInstance() {
return new ClientService();
}
}
<bean id="clientService" class="com.local.ioc_01.ClientService" factory-method="createInstance"/>
- 基于实例工厂方法(创建对象的方法是非静态方法)
factory-bean属性:指定当前容器中工厂Bean 的名称。
factory-method: 指定实例工厂方法名。注意,实例方法必须是非static的
public class DefaultServiceLocator {
public ClientService createClientServiceInstance() {
return new ClientService();
}
}
<bean id="serviceLocator" class="com.local.ioc_01.DefaultServiceLocator"/>
<bean id="clientService2" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
- 基于Setter方法依赖注入
public class MovieFinder {
}
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String movieName;
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void setMovieName(String movieName){
this.movieName = movieName;
}
}
<bean id="movieFinder" class="com.local.ioc_02.MovieFinder"/>
<bean id="movieLister" class="com.local.ioc_02.SimpleMovieLister">
<property name="movieFinder" ref="movieFinder"/>
<property name="movieName" value="电影"/>
</bean>
3.2.2 IOC容器创建和使用
# spring-ioc-01.xml是要读取的配置文件
# getBean()中填写要创建的组件对应的id和对应的反射
ClassPathXmlApplicationContext Context = new ClassPathXmlApplicationContext("spring-ioc-01.xml");
UserService userService = Context.getBean("userService", UserService.class);
3.2.3 组件周期方法配置
可以在组件类中定义方法,然后当IOC容器实例化和销毁组件对象的时候进行调用。这两个方法我们成为生命周期方法。
类似于Servlet的init/destroy方法,我们可以在周期方法完成初始化和释放资源等工作。
方法命名随意,但是要求方法必须是 public void 无形参列表
public class BeanOne {
public void init(){
System.out.println("init....");
}
public void destroy(){
System.out.println("destroy....");
}
}
<bean id="bean" class="com.local.ioc_03.BeanOne" init-method="init" destroy-method="destroy"/>
ClassPathXmlApplicationContext Context = new ClassPathXmlApplicationContext("spring-ioc-03.xml");
BeanOne bean = Context.getBean("bean", BeanOne.class);
Context.close();
//init....
//destroy....
3.2.4 FactoryBean的使用
FactoryBean 是接口。类实例化该接口后可以将创建复杂对象的过程存储在FactoryBean 的getObject方法中。
待定…
在这里插入代码片
3.3 基于 注解配置 方法
和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
3.3.1 Spring提供的常见注解
@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字,在语法层面没有区别。但是为了代码的可读性,不要随意乱起。
- 使用注解的组件
@Controller
public class UserController {
@Autowired
private UserService userService;
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
@Repository
public class UserDao {
}
- xml配置文件进行扫描
基本扫描
<context:component-scan base-package="com.atguigu.components"/>
排除扫描
<context:component-scan base-package="com.atguigu.components">
<!-- context:exclude-filter标签:指定排除规则 -->
<!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
<!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
指定扫描
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.atguigu.ioc.components" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 获取对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("annotation01.xml");
UserController bean = context.getBean(UserController.class);
3.3.2 注解中BeanName的问题
使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。
默认情况:类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。
还可以使用value属性指定:
@Controller(value = "tianDog")
public class SoldierController {
}
3.3.2 Autowired 和 Resourc 注解
- AutoWired注解
在成员变量上直接标记@Autowired注解即可。容器创建对象的时候会自动寻找对应的组件进行注入。注入流程如下:
@Controller(value = "tianDog")
public class SoldierController {
@Autowired
@Qualifier(value = "maomiService222")
// 根据面向接口编程思想,使用接口类型引入Service组件
private ISoldierService soldierService;
//依赖注入的时候,去寻找value值对应的注解
- Resource注解
@Resource注解默认根据 Bean名称装配,未指定name时,使用属性名作为name通过name找不到的话会自动启动通过类型装配。
@Autowired注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用。
@Autowired注解是Spring提供的注解,@Resource注解是JDK扩展包中的。【高于JDK11或低于JDK8需要引入以下依赖】
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
@Controller
public class UserController {
/**
* 1. 如果没有指定name,先根据属性名查找IoC中组件xxxService
* 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找
* 3. 可以指定name名称查找! @Resource(name='test') == @Autowired + @Qualifier(value='test')
*/
@Resource
private UserService UserService;
}
3.4 基于 配置类 方法
完全注解开发:Spring 完全注解配置(Fully Annotation-based Configuration)是指通过 Java配置类 代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性
- 配置类:使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类
@Configuration
@ComponentScan(basePackages = {"com.local"})
// 上述代替了使用xml文件配置
public class configuration {
}
- IOC容器创建对象
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configuration.class);
UserController bean = context.getBean(UserController.class);
3.5 三种方法总结
-
XML方式配置
-
XML+注解方式配置
-
完全注解方式配置
4. Spring AOP
4.1 AOP是什么
AOP:Aspect Oriented Programming(面向切面编程)面向切面编程是一种思维,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
4.2 为什么引入AOP
某种程度AOP上完善和解决OOP的非核心代码冗余和不方便统一维护问题。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
4.3 主要应用场景
- 日志记录:在系统中记录日志是非常重要的,可以使用AOP来实现日志记录的功能,可以在方法执行前、执行后或异常抛出时记录日志。
- 事务处理:在数据库操作中使用事务可以保证数据的一致性,可以使用AOP来实现事务处理的功能,可以在方法开始前开启事务,在方法执行完毕后提交或回滚事务。
- 安全控制:在系统中包含某些需要安全控制的操作,如登录、修改密码、授权等,可以使用AOP来实现安全控制的功能。可以在方法执行前进行权限判断,如果用户没有权限,则抛出异常或转向到错误页面,以防止未经授权的访问。
- 性能监控:在系统运行过程中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈并进行优化。可以使用AOP来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算方法执行时间并输出到日志中。
- 异常处理:系统中可能出现各种异常情况,如空指针异常、数据库连接异常等,可以使用AOP来实现异常处理的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志、发送邮件等)。
- 缓存控制:在系统中有些数据可以缓存起来以提高访问速度,可以使用AOP来实现缓存控制的功能,可以在方法执行前查询缓存中是否有数据,如果有则返回,否则执行方法并将方法返回值存入缓存中。
- 动态代理:AOP的实现方式之一是通过动态代理,可以代理某个类的所有方法,用于实现各种功能。
4.4 Spring AOP 基于注解实现
4.4.1 Spring AOP 底层技术组成
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
- cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。
4.3.2 通知增强的类型
即表示将切面中的方法切入到核心方法中的哪里去。
- 前置通知:在被代理的目标方法前执行 @Before
- 返回通知:在被代理的目标方法成功结束后执行 @After
- 异常通知:在被代理的目标方法异常结束后执行 @AfterThrowing
- 后置通知:在被代理的目标方法最终结束后执行 @AfterReturning
- 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,
包括上面四种通知对应的所有位置 @Around
4.4.3 切点表达式
- 什么是切点表达式
在准备好切面后,需要确定将切面切入到哪里,即切入到核心方法的哪里,切点表达式即指定说明了切入的点。 - 切点表达式语法
- 第一位:execution( ) 固定开头
- 第二位:方法访问修饰符
public private 直接描述对应修饰符即可
- 第三位:方法返回值
int String void 直接描述返回值类型
注意:
特殊情况 不考虑 访问修饰符和返回值
execution(* * ) 这是错误语法
execution(*) == 你只要考虑返回值 或者 不考虑访问修饰符 相当于全部不考虑了
- 第四位:指定包的地址
固定的包: com.atguigu.api | service | dao
单层的任意命名: com.atguigu.* = com.atguigu.api com.atguigu.dao * = 任意一层的任意命名
任意层任意命名: com.. = com.atguigu.api.erdaye com.a.a.a.a.a.a.a ..任意层,任意命名 用在包上!
注意: ..不能用作包开头 public int .. 错误语法 com..
找到任何包下: *..
- 第五位:指定类名称
固定名称: UserService
任意类名: *
部分任意: com..service.impl.*Impl
任意包任意类: *..*
- 第六位:指定方法名称
语法和类名一致
任意访问修饰符,任意类的任意方法: * *..*.*
- 第七位:方法参数
第七位: 方法的参数描述
具体值: (String,int) != (int,String) 没有参数 ()
模糊值: 任意参数 有 或者 没有 (..) ..任意参数的意识
部分具体和模糊:
第一个参数是字符串的方法 (String..)
最后一个参数是字符串 (..String)
字符串开头,int结尾 (String..int)
包含int类型(..int..)
- 切点表达式重用
Q:为什么要对切点表达式重用?
A:当多个切面表达式相同时,后期进行修改维护较麻烦,例如以下:前2个和后2个的切面表达式相同,因此将切点表达式提取到同一个类中,方便重用。
@Before(value = "execution(public int *..Calculator.sub(int,int))")
..........
@AfterReturning(value = "execution(public int *..Calculator.sub(int,int))")
.........
@AfterThrowing(value = "execution(* *..*Service.*(..))")
.........
@After(value="execution(* *..*Service.*(..))")
......
Q:如何重用?如何重用后引用?
// 重用
@Component
public class PointCut {
@Pointcut(value = "execution(public int *..Calculator.sub(int,int))")
public void GlobalPointCut(){}
@Pointcut(value = "execution(* *..*Service.*(..))")
public void SecondPointCut(){}
}
//引用
@Before(value = "GlobalPointCut") //value中是方法名
public void printLogBeforeCoreOperation() {}
4.4.4 代码示例
//定义计算接口
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
//接口的核心代码实现类(只实现核心代码而忽略一些打印输出和异常处理的代码)
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
//重用切面表达式
@Component
public class MyPointCut {
@Pointcut(value = "execution(* service.impl.*.*(..))")
public void pointCutOne(){}
}
//切面
@Component
@Aspect
public class LogAdvice {
@Before("PointCut.MyPointCut.pointCutOne()")
public void start(){
System.out.println("start");
}
@After("PointCut.MyPointCut.pointCutOne()")
public void end(){
System.out.println("end");
}
@AfterThrowing("PointCut.MyPointCut.pointCutOne()")
public void error(){
System.out.println("error");
}
}
//配置类扫描和开启aspectj注解
@Configuration
@ComponentScan({"service","advice","config"}) //扫描
@EnableAspectJAutoProxy //开启aspectj注解
public class JavaConfig {
}
//测试
@SpringJUnitConfig(value = JavaConfig.class)
public class SpringAopTest {
@Autowired
private Calculator calculator;
@Test
public void test1(){
int res=calculator.add(1,1);
System.out.println(res);
}
}
//输出
start
end
2
5.Spring-tx
5.1什么是Spring-tx?为什么需要Spring-tx?
- Spring-tx是Spring框架支持以声明性的方式管理事务,而不是编程式的方式。将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 业务代码
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
- 编程式事务:手动编写程序来管理事务,即通过编写代码的方式直接控制事务的提交和回滚。
- 声明式事务:使用注解或 XML 配置的方式来控制事务的提交和回滚。开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作。
5.2 代码示例
//jdbc.properties
atguigu.url=jdbc:mysql://localhost:3306/fruitdb
atguigu.driver=com.mysql.cj.jdbc.Driver
atguigu.username=root
atguigu.password=123456
//javaconfig
@Configuration
@ComponentScan({"Dao","Service"})
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement //开启事务注解的支持
public class JavaConfig {
@Value("${atguigu.driver}")
private String driver;
@Value("${atguigu.url}")
private String url;
@Value("${atguigu.username}")
private String username;
@Value("${atguigu.password}")
private String password;
//druid连接池
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
//jdbcTemplate
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//使用事务管理器
//事务管理器需要数据库连接信息datasource
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
//FruitDao
@Repository
public class FruitDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updatePriceByName(String name,Integer price){
String sql = "update t_fruit set price = ? where fname = ? ;";
int rows = jdbcTemplate.update(sql, price,name);
}
public void updateRemarkByName(String name,String remark){
String sql = "update t_fruit set remark= ? where fname = ? ;";
jdbcTemplate.update(sql,remark,name);
}
}
//FruitService
@Service
public class FruitService {
@Autowired
private FruitDao fruitDao;
@Transactional //添加事务注解
public void changeInfo(){
fruitDao.updatePriceByName("苹果",30);
int i=1/0; //这里会报错 那么整个事务将会回滚,2次修改信息都将失败 如果没有事务 那么第一次将修改成功而第二次失败
fruitDao.updateRemarkByName("苹果","ok");
}
}
上述代码在FruitService的方法中添加了Transactionnal注解,该注解可以作用与类和方法上,作用于类上说明对类内的方法都生效,作用于方法则只对方法生效。
5.3 事务属性
- 只读
在Transactionnal注解中设置属性readOnly属性为True,默认值为False
@Transactional(readOnly = true)
- 超时时间
程序运行过程中因为某些原因卡住占用资源,设置超时时间,事务运行的时间超过超过设置的超时时间则回滚,释放资源。通过timeout属性设置。默认值是-1,即无限。
@Transactional(timeout = 3)
- 事务异常
关于异常的分类可以参考此文章-异常分类总结
默认只针对运行时异常回滚,编译时异常不回滚
rollbackForClassName:指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
noRollbackForClassName:指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
public class FruitService {
@Autowired
private FruitDao fruitDao;
@Transactional(noRollbackFor = ArithmeticException.class) //添加事务注解 发生该异常时不回滚
public void changeInfo(){
fruitDao.updatePriceByName("苹果",30);
int i=1/0; //这里会报错 但是设置为不回滚 所以第一条修改成功,但是第二条修改失败
fruitDao.updateRemarkByName("苹果","ok");
}
}
- 隔离级别
数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:
- 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
- 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
- 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
- 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
@Transactional(isolation = Isolation.REPEATABLE_READ)
- 事务传播
@Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是
Propagation propagation() default Propagation.REQUIRED;
//jdbc.properties
atguigu.url=jdbc:mysql://localhost:3306/fruitdb
atguigu.driver=com.mysql.cj.jdbc.Driver
atguigu.username=root
atguigu.password=123456
//javaconfig
@Configuration
@ComponentScan({"Dao","Service"})
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement //开启事务注解的支持
public class JavaConfig {
@Value("${atguigu.driver}")
private String driver;
@Value("${atguigu.url}")
private String url;
@Value("${atguigu.username}")
private String username;
@Value("${atguigu.password}")
private String password;
//druid连接池
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
//jdbcTemplate
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//使用事务管理器
//事务管理器需要数据库连接信息datasource
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
//FruitDao
@Repository
public class FruitDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updatePriceByName(String name,Integer price){
String sql = "update t_fruit set price = ? where fname = ? ;";
int rows = jdbcTemplate.update(sql, price,name);
}
public void updateRemarkByName(String name,String remark){
String sql = "update t_fruit set remark= ? where fname = ? ;";
jdbcTemplate.update(sql,remark,name);
}
}
//FruitService
@Service
public class FruitService {
@Autowired
private FruitDao fruitDao;
@Transactional(noRollbackFor = ArithmeticException.class,propagation = Propagation.REQUIRES_NEW) //添加事务注解 发生该异常时不回滚 并且修改默认传播
public void changePrice(){
fruitDao.updatePriceByName("香蕉",50);
int i=1/0; //这里会报错 但是设置了不回滚
}
@Transactional
public void changeRemark(){
fruitDao.updateRemarkByName("苹果","good");
}
}
//TopService 整合的Service
@Service
public class TopService {
@Autowired
private FruitService fruitService;
@Transactional
public void changeInfo(){
fruitService.changePrice();
fruitService.changeRemark();
}
}
//测试
@Test
public void test(){
topService.changeInfo();
}
结果:父事务默认的传播行为,changePrice方法中修改了默认的传播方法,并且遇到运行错误时不回滚。
那么changePrice方法则忽略父方法中的传播行为,独立创建事务。
结果是成功修改了price而没有修改remark。