当前位置: 首页 > news >正文

Spring 框架知识整理

一、Spring 框架发展历程

历史:spring 目前发展到现在不仅仅是框架,而是一个生态体系(无论从 web 到大数据,都建立在 Spring 框架上)

演变:(自上而下)

  • Servlet 和 JSP
  • MVC 框架
  • EJB
  • SSH(Spring、Struts、Hibernate)
  • SSM(Spring、Spring MVC、MyBatis)
  • SpringBoot

二、loc(Inversion of control)

通过在Spring里面使用不同的封装好的api可以创建不同的容器(不同容器需要的介质就不一样,如有的需要xml配置文件、有的需要javaconfig类、有的需要注解),当new成功后,对应的那些配置里面有写的或者说那些有包含Bean注解的才加载入容器成为Bean
.
Bean创建过程: (@Componet class A --b—> 容器加载 new ApplicationContext()------>
new A(); DI…😉

2.1 如何配置Bean

  1. 依赖注入版本演化:

    1. spring1 xml
    2. spring2 xml (applicationcontext)
    3. spring3 javaconfig+@
  2. 配置操作:

    • xml配置文件里面:<bean class =“xxx” id=“xxx”> </bean>(如果bean之间要引用别的bean的话,通过property进行引用)
    • @Component(放在类上面,使用该注解意味着标记的类是一个Bean,下面是对于这个Bean更加精确的划分,在后续开放过程中含义特定的功能)
      • @Service
      • @Repository
      • @Configuration
    • @Bean
      • 写在方法上面
      • 通常写在配置类里面
        • @Bean方法调用另外一个@Bean方法,会从spring容器中去获取
      • 可以干预bean的实例化过程的,jar包中的类如果要配置bean就需要用@Bean
      • 在方法中声明的参数spring会帮我们自动注入进来
    • @Import
      • @lmport必须写在类上面
      • 标记的类必须是一个bean否则不会起作用
      • 普通类
        • 直接@Import(UserService.class)普通类把普通类注入spring
      • ImportSelectorr——用于根据完整限定名字符串批量注入Bean
        • 定义一个类实现lmportSelector
        • 批量注册bean(通过实现selectlmports,返回bean的完整类路径字符串数组)
        • .@lmport(实现lmportSelector的类.class)
      • ImportBeanDefinitionRegistrar——用于在 Spring 的上下文中动态注册 Bean
        • 定义一个类实现lmportBeanDefinitionRegistrar
        • 根据beanDefinition注册bean
  3. 实例化Bean(三种):

    • 使用实例工厂方法实例化@Bean
      • 选择实例化DefaultListableBeanFactory对象创建beanfactory实例
      • 创建 XmlBeanDefinitionReader 阅读器对象,并且传参传入的为创建的 beanfactory 实例
      • 使用XmlBeanDefinitionReader的loadBeanDefinitions的方法加载配置文件
      • 通过beanfactory的getBean()获取Bean实例
    • 使用ApplicationContext接口去调用具体的类:
      • ClassPathXmlApplicationContext:主要用于加载配置文件,后续直接可以调用getBean()获取Bean实例;
      • AnnotationConfigApplicationContext:启动类得预先书写好@ComponentScan,主要用于加载配置类(配置类里面得预先书写好@Bean注解下的不同Bean构造方法),后续直接可以调用getBean()获取Bean实例;
    • 使用@SpringBootApplication:该注解内部包含其他的注解,直接可以通过SpringApplication.run(MyApplication.class, args);调用启动类。

2.2 依赖注入DI

要实现自动DI一定得使用@Autowired?

  1. @Bean方法中参数会自动注入;
  2. 构造函数的参数会自动注入;

特性:

  1. @Autowired可以写在构造函数、方法、字段、参数
    a. 构造函数:

    • 如果bean只有一个有参构造函数,是可以省略@Autowired,会自动注入构造函数的参数。
    • 如果有多个有参构造函数,并且没有无参构造函数:会报错,解决:使用@Autowired指定某一个构造函数
    • 构造函数上指定的@Autowired(required=false);会失效
      b. 参数:
    • 如果想设置构造函数上面的参数为不是必须注入:单独去设置
    public AutowireService(@Autowired(required = false) ProductService productService){System.out.pritlin(productService);this.productService = productService;
    }

    c. 方法:

    • spring会自动调用你的@Autowired的方法进行自定注入
@Autowired
public void setStockService(StockService stockService) {this.stockService = stockService;
}
  1. @Autowired 默认会根据类型去容器中找(byType),如果找到了多个会在根据名字去找(byName),如果名字没有匹配上就会报错,解决方案:

    • 通过 @Primary 来设置某一个为主要的
      • 使用 @Primary 注解标识一个 Bean 为首选 Bean,当存在多个相同类型的 Bean 时,Spring 会优先注入这个 Bean。
    @Component
    @Primary
    public class AutowiredService {// 主要的 Bean 实现
    }
    
    • 通过 @Qualifier(“autowiredTestService2”) 告诉 Spring 需要的那个 Bean 的名字
      • 使用 @Qualifier 注解指定要注入的具体 Bean 名称,以避免类型冲突。
    @Autowired
    @Qualifier("autowiredTestService2")
    private AutowiredService autowiredService;
    
    • 使用 @Resource 注解
      • @Resource 注解可以根据名称注入 Bean,若未找到名称,则会根据类型进行注入。
    @Resource(name = "autowiredTestService2")
    private AutowiredService autowiredService;
    
    • 使用自定义的 Factory Method
      • 创建一个自定义的工厂方法,根据条件返回所需的 Bean,确保注入的 Bean 是你想要的。
    @Bean
    public AutowiredService autowiredService() {return condition ? new ServiceA() : new ServiceB();
    }
    
    • 使用 Profiles
      • 使用 Spring 的 Profile 功能,在不同环境中激活不同的 Bean 定义,避免冲突。
    @Bean
    @Profile("dev")
    public AutowiredService devService() {return new DevService();
    }@Bean
    @Profile("prod")
    public AutowiredService prodService() {return new ProdService();
    }
    
    • 使用 @Autowired(required = false)
      • required 设置为 false,这样如果找不到合适的 Bean,则会注入为 null,避免报错。
    @Autowired(required = false)
    private AutowiredService autowiredService;
    

2.3 loc有关注解

1. @Value():用于在字面量上方赋予值

  • @PropertySource(“classpath: xxx.properties”) :用于读取配置文件中的固定值,如何方便在类里面中的字面量上赋予@Value(“${xxxx }”)的固定值
    • 在Spring boot里面的application.properties不需要写该注解,直接可以在需要赋值的字面量上书写其对应的@Value(" $ {xxxx }“) ,如果配置文件里面不存在值的话,会报错。当然可以在Value(”$ {xxxx : xxx }")里面添加默认值进而不会报错。
    • 在Spring 里面就需要额外添加@PropertySource,且当配置文件里面没有的话就直接添加对应的@Value(" $ {xxxx }"),就默认的如value的用法一样,直接赋予” “里面的内容。

2. @Order注解:改变自动注入顺序

  • 当接口实现的子类有多个,然后对于Bean的使用有优先级顺序,那么此时可以使用@Order在对于的Bean类上面添加其对于的顺序,此时容器会根据Order里面变更其Bean执行的顺序
  • 当然如果不使用注解,可以通过其不同的子类都实现Ordered接口,重写里面的getOrder方法,返回的是int值,即其顺序,这样也可以实现对于的改变自动注入的顺序

3. @DependsOn注解:改变Bean创建顺序

  • 当存在两个Bean A、B有创建的先后顺序,此时有两个方法:
    • 在对应B Bean创建前加上注解@Dependson(“A”),此时优先创建Bean A
    • 在B Bean的构造方法里添加A的形参,当构建时默认先创建A Bean
    • 在同一个类文件里面,每一个Bean的摆放位置就是对应Bean的加载顺序

4. @Lazy注解:懒加载Bean

  • 在Bean类上添加对应的@Lazy注解,那么只有在访问时才会加载对应的Bean,提升程序运行的速度
  • 全局设置Bean懒加载,在application.properties配置文件里面添加spring.main.lazy-initialization=true,进而所有的Bean都是懒加载

5. @SpringBootTest注解:专门用于Spring进行单元测试的类注解

  • @SpringBootTest 注解用于指示 Spring Boot 创建一个应用程序上下文。通过 classes 属性,可以指定特定的配置类或测试类,这样 Spring 就会加载这个类的上下文配置。
  • 指定 xxxx.class 可以确保只加载与该类相关的 Bean,从而缩短测试的启动时间。这在测试大型应用时非常有用,因为它避免了加载不必要的 Bean。
  • 内部使用@Test注解进行测试函数方法。
  • 与此同时该测试类需要书写@ComponentScan(basePackages=“xxx”),这样在使用测试类的时候可以智能读取到对应的Bean

6. @Scope注解:Bean的作用域

  • 默认Bean单例(只会new一次,不管@autowired多少次,只会创建一次)
  • @Scope(“protctype”) //多例模式的Bean,每次调用就创建一个Bean,造成内存问题
  • @Scope(“singleton”) //单例模式的Bean,造成线程问题

7. @Conditional:条件注解

  • 必须指定一个实现了Condition接口的类,并且其matches方法的返回值决定当前的Bean是否生效,即在要添加条件注释的Bean上面添加@Conditional(xxx.class)
  • 实操:
    • 在一个项目里面如果有搭载多个数据库,那么可以设置对应不同的condition,然后对项目jar包进行判断其是否存在对应的jar包中特定的类,进而判断我的Bean哪些需要进行注解

8. @ComponentScan:用于扫描bean的注解

  • 当在Bean上标记此注解,其会在该bean运行时,动态扫描当前类的的package里面的bean,进而进行DI
  • @ComponentScan(basePackages = “com.example.springstudy.loc”)

9. @Retention

  • Retention 注解用于指示注解的保留策略,即该注解在什么级别可用,是否在运行时可访问。即决定了注解的生命周期和可访问性
  • 常用值:
    • SOURCE:注解仅在源代码中存在,编译后被丢弃。
    • CLASS:注解在编译后存在于类文件中,但在运行时不可用。
    • RUNTIME:注解在运行时可用,可以通过反射访问。
用法:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {String value();
}

10. @Target

  • @Target 注解用于指示该注解可以应用于哪些 Java 元素(如类、方法、字段等)。即决定了注解的应用范围。
  • 常用值:
    • TYPE:可以应用于类、接口或枚举。
    • FIELD:可以应用于字段。
    • METHOD:可以应用于方法。
    • PARAMETER:可以应用于方法参数。
    • CONSTRUCTOR:可以应用于构造器。
    • ANNOTATION_TYPE:可以应用于其他注解。
用法:
@Target(ElementType.METHOD) 
public @interface MyMethodAnnotation { String description(); }

2.4 什么是Bean的生命周期

  1. 配置@Component @Bean … @Autowired
  2. 加载Spring容器
  3. 实例化(new Bean类)
  4. 解析依赖注入(解析@Autowired @Value)
  5. 初始化(调用初始化回调方法,由程序有来配置)
    1. 基于接口进行回调:通过类实现 InitializingBean 借口,并且重写afterPropertiesSet方法,再初始化Bean时自动调用
    2. 基于注解@PostConstruct,在Bean类中的函数里面上方书写注解,在最初初始化时会调用该函数(且注解的调用的优先级大于接口调用)
    3. 基于initMethod属性,即对应的Bean的注解旁边加一个@Bean(initMethod = “类内部函数”)

顺序:注解@PostConstruc——>基于initMethod属性——>基于接口

  1. 最终放入Map<beanName,bean对象>
  2. Sring容器.getBean(“beanName”)---->Map<beanName,bean对象>
  3. spring容器关闭 bean就会销毁(调用销毁回调方法,由程序有来配置)
    1. 基于接口进行回调:通过类实现 DisposableBean 借口,并且重写 destory 方法,再容器关闭时Bean时自动销毁
    2. 基于注解 @PreDestory,在Bean类中的函数里面上方书写注解,在容器关闭时会调用该函数
    3. 基于 destoryMethod 属性,即对应的Bean的注解旁边加一个@Bean(destoryMethod = “类内部函数”)

a. 在springboot中当jvm进程结束后会自动调用容器.close(),自动销毁
b. 在spring中,需要手动调用容器.close()
顺序:注解@PreDestory——>基于接口——>基于 destoryMethod 属性


2.5 Bean循环依赖问题

循环依赖过程:
new applicationContextO; ---->new BeanA();—>解析@autowired----->去容器中获取BeanB(容器中没有B,创建B)---->---->newBeanB();——>解析@autowired----->去容器中获取BeanA(容器中没有A,创建A)---->循环执行…

问题的根本:Bean要经过依赖注入完(解析@autowired之后)后才会放到spring容器

  1. Spring已经帮我解决了这个问题,关于Spring底层源码的概念(Spring怎么解决的循环依赖)
  2. Spring Boot中会报错,因为Spring Boot认为这个是一个不合理的设计

解决:

  1. 在Spring Boot里面的配置文件applicationcontext里面放开循环依赖的限制:
#放开循环依赖的限制
spring.main.allow-circular-references=true
  1. 代码设计层面:
    1. 把依赖的方法,直接写在本类中;
    2. 添加一个中间体,中间类去依赖A/B,然后让中间类去阻止他们的依赖方法;
  2. 延迟注入:
    1. 添加需要依赖的构造函数参数
    2. 添加@Lazy注解
@lazy
public BeanA(BeanB beanb){this.beanb = beanb;
}@lazy
public BeanB(BeanA beana){this.beana = beana;
}

2.6 Bean底层原理

  • Bean 定义:被Spring容器管理的对象。

  • 实例过程:
    1.概念态: < bean class=“” name=“” scope lazy> @Component @Bean
    2.定义态:BeanDefinition(beanclass=com.xs.UserService scope=‘singleton’)

    • 内部维护一个BeanDefinitionMap<beanName,BeanDefinition>
      3.纯净态:Object bean=BeanDefinition.beanclass.newlnstance();反射 实例对象// new UserService();
    • 依赖注入@Autowired
    • 初始化:实现 InitializingBean 接口,进而重写 afterPropertiesSet() 进行方法初始化
    • 缓存:单例Map <beanName ,bean>
      4.getBean(“userService”)new UserService();DI

项目中定义 Bean 名相同的两个类,那么在初始ComponentScan扫描的时候会出现不同的情况:

  • Spring框架会采用覆盖形式,即只保留一个Bean
  • Spring Boot 会报错,说只能拥有一个Bean
  • Spring Ioc 加载过程(即 AnnotationConfigApplicationContext 这个的加载过程)
    • @Component---->new ApplicationContext()----->refresh------->invokeBeanFactoryPostProcessors–>finishBeanFactorylnitialization-----doGetBean----bean是否已经创建完成—doCreateBean—>实例化----->属性注入(doGetBean(“orderServer”)----初始化----map

三、AOP(Aspect Oriented Programming)

面向切面编程 && 编程思想
.
在不改变原有代码的基础进行增强(额外运行切面里面的代码)

aspectj的jar包:AOP概念并不是spring独家提出来的,AOP早就有成熟框架已经实现了,aspectj就是一个成熟的AOP的解决方案,spring只不过是aspectj集成进来了

如何在Spring里面手写一个AOP?

  • 内容包括:@Aspect+@Component+通知+切点
  • @Aspect:标记类为切面类(前提该类为Bean)
  • @Around():切点(在切面类里定义要进行插入的函数上方标记,其中里面内容设置其对应的表达式进行选择要被插入的Bean)
  • ProcedingJoinPoint(在该切点方法里面的形参)

3.1 AOP有关注解介绍

1.@EnableAspectJAutoProxy

作用: 启用AOP,没有这个注解AOP功能无法使用

为什么没有加AOP也起作用了?

  • SpringBoot其实会通过启动类自动帮我们加上@EnableAspectJAutoProxy,所以可以省略。

但是依然建议加上@EnableAspectJAutoProxy,在Spring中是要加的,

2.@Poincut(“xxxxx”)

  • 定义切点的表达式
  • xxx里面用于设置要被切入的类、包等;
  • 主要放在含有@Aspect切面注解的类里面的方法上面;
  • 含义@Pointcut的方法主要用于放在@Before、@After等注解里面;
//execution 表达式  
@Pointcut("execution(* com.example.springstudy.AOP..*(..))")  
public void serviceMethods() {  
}  //within表达式  
@Pointcut("within(com.example.springstudy.AOP.JDK_Server)")  
public void serviceMethods_within() {  
}  //注解表达式  
@Pointcut("@annotation(com.example.springstudy.AOP.Log)")  
public void logExceutionTime() {  
}

3.@Aspect

  • 主要用于标记类为切面类(前提该类需要为Bean)

4.@Around()

  • 切点(在切面类里定义要进行插入的函数上方标记,其中里面内容设置其对应的表达式进行选择要被插入的Bean)
  • 更加灵活,可以自由标记原方法的位置,如:
public Object aroundLog2(ProceedingJoinPoint pjp) throws Throwable {  System.out.println("环绕开始:方法前");  Object result = pjp.proceed();  System.out.println("环绕结束:方法后");  return result;  
}
  • 其中,ProceedingJoinPoint 参数代表原始方法的调用上下文,通过 proceed() 方法可以执行原方法并获取其返回值。这在代理模式中体现得尤为明显

3.2 AOP术语介绍

  • 目标对象(Target):目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。

要增强的对象(通常会有很多个),被插入增强代码的对象

  • 切面(Aspect):指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@Aspect注解方式)来实现。

要增强的代码放入那个类就叫切面类,@Aspect注解标记的Bean

  • 通知(Advice):在切面的某个特定的连接点上执行的动作。通知有多种类型,包括"around","before” and "after” 等等。许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
  • 用来放增强的代码的那个方法
    • 环绕通知@Around:可以把代码增强在目标方法的任意地方,更通用
    • 前置通知@Before:目标方法之前执行
    • 后置通知@After:目标方法之后执行
    • ·异常通知@AfterThrowing:目标方法出现了异常执行
    • 返回通知@AfterReturning:目标方法返回值执行
  • 执行顺序:
    • 正常:前置–>目标方法–>返回通知—>后置通知(finally)
    • 异常:前置–>目标方法–>异常通知—>后置通知(finally)
  • 切点(Pointcut):匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。

增强代码要切入到哪些方法中切点表达式

  • 连接点(Join point):在Spring AOP中,一个连接点总是代表一个方法的执行,其实就代表增强的方法。

通知和目标方法的一个桥梁,可可以获取目标方法的信息,就得通过 JoinPoint

  • 顾问(Advisor):顾问是Advice的一种包装体现,Advisor是Pointcut以及Advice的一个结合,用来管理Advice和Pointcut。应用无需关心.

源码中的体现,会封装切点和通知

  • 织入(Weaving):将通知切入连接点的过程叫织入

即将增强代码载入到业务代码中的过程

3.3 切点表达式

SpringAOP支持使用以下AspectU切点标识符(PCD),用于切点表达式:

  • 即用 @Pointcut(“xxxxxxxxxxxxx”) 声明在方法上面,其中里面xxxx的填充用下面的表达式进行填充。
  • execution:用于匹配方法执行连接点。这是使用Spring AOP时使用的主要切点标识符。可以匹配到方法级别,细粒度更精细。
官方文档
execution(modifiers-pattern?       ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)- modifiers-pattern:- 可选,方法的修饰符,如 `public`, `protected`, `private` 等。
- ret-type-pattern:- 方法的返回类型,如 `void`, `int`, `String`, `*`(表示任意类型)。
- declaring-type-pattern:    - 可选,表示方法所在的类或接口。可以使用 `..` 表示子类或子包。
- name-pattern:  - 方法名,可以使用 `*` 表示任意名称,`..` 表示任意参数。
- param-pattern:    - 方法参数类型,可以使用 `..` 表示任意参数或任意数量的参数。
- throws-pattern:    - 可选,表示方法可能抛出的异常    - 
  • within 是 AOP 中的一种切点表达式,用于匹配特定类型中的所有方法。它允许指定一个类或接口,表示要拦截该类或接口中的所有方法调用
within(type-pattern)如:within(com.example.service.UserService) 这将匹配 `UserService` 类中的所有方法。within(com.example.service..*)这将匹配 `com.example.service` 包及其子包中的所有类的方法。
  • @annotation 是一种切点表达式,用于匹配特定注解标记的方法。它允许你拦截被特定注解修饰的方法,从而实现对横切关注点的处理.
@annotation(annotation-type)如果要在通知的参数中绑定注解就不能单独抽取
如果要在通知的参数中绑定注解,声明就是参数名了,不是注解类型!
- 在通知方法中,参数名是用来绑定实际参数的,而不是直接使用注解类型。
- 需要通过反射来访问被注解的参数的具体信息。

3.4 AOP 底层原理

在不改动业务基础上,实现增强(增强跟业务没有关系公共功能)
AOP通常用代理实现比如拦截器,事务控制,还有测试框架 mock、用户鉴权、日志、全局异常处理等功能。

静态代理弊端:

  1. 代理类不需要自己声明
  2. 一个代理类只能代理一个类型

动态代理从静态代理演变而来的

动态代理分类:

  • JDK(Java Development Kit)动态代理
    • JDK 代理是基于 Java 的接口实现的动态代理。它只能代理实现了接口的类。
    • 实现原理:
      • JDK 代理通过 java.lang.reflect.Proxy 类来创建代理对象。
      • 需要提供一个接口和一个实现了 InvocationHandler 接口的处理器。调用代理对象的方法时,会转发到处理器的 invoke 方法。
  • CGLIB动态代理
    • CGLIB(Code Generation Library)代理是通过字节码生成技术实现的,可以代理没有实现接口的类。
    • 实现原理:
      • CGLIB 使用 ASM 库生成目标类的子类,并重写其方法来实现代理。
      • 由于它是通过继承来实现的,因此不能对 final 类和 final 方法进行代理。

动态代理:

  1. 创建容器ioc=new applicationContext();
  2. spring把所有的bean进行创建,进行依赖注入
  3. spring( new $$ SpringCGLIB $$0))---->ioc.getBean(UserService.class)
  4. 当实现了AOP , spring会根据当前的bean创建动态代理
  5. 把bean进行替换—>所以自动装配的类就称为new $ S p r i n g C G L I B SpringCGLIB SpringCGLIB$0()

通俗易懂:

  • 原始 Bean
    - 在没有 AOP 之前,你的原始 Bean(例如 UserService)只包含你定义的方法和逻辑。
  • 代理 Bean
    - 当你在 Spring 中启用 AOP 时,Spring 会为你的原始 Bean 创建一个代理对象。这个代理对象的内部逻辑除了包含原始 Bean 的方法外,还会包含 AOP 相关的逻辑(例如切面、通知等)。

四、事务

一组关联的数据库操作(转账);要么都成功,要么都失败一保证业务操作完整性的一种数据库机制。

  • 我们常说的事务,一般指的就是数据库事务
  • 数据库事务是指一个逻辑工作单元中执行的一系列(数据库)操作,要么一起成功,要么一起失败(当工作单元中的所有操作全部正确完成时,工作单元里的操作才会生效。如果检测到一个错误,程序执行回滚操作,恢复程序原状。即要么都执行要么都不执行,逻辑工作单元就是一个不可分割的操作序列,操作序列就是一系列的数据库操作)
  • Spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,Spring的事务是没有作用的。
  • 数据库的事务有开启、执行(数据库操作)、提交、回滚,还有个关闭是数据库连接操作
  • Spring 事务管理是基于 AOP 实现的。当控制层调用某个方法时,AOP 代理会在方法执行前后自动插入事务处理逻辑,包括事务的开启、提交或回滚。事务支持不同的传播行为,如嵌套事务(NESTED)、独立事务(REQUIRES_NEW),并且可以在不同的数据库或服务间使用分布式事务来保证一致性。

4.1 ACID 四大特性

作用:(ACID)

  • 原子性:事务中的所有操作要么全部成功,要么全部失败。
  • 一致性:事务必须使数据库从一个一致性状态转变为另一个一致性状态。
  • 隔离性:多个事务并发执行时,互不影响。
  • 持久性:一旦事务提交,其结果是永久性的,即使系统崩溃也不会丢失。

4.2 DataSource 连接数据库

DataSource是一个接口,它提供了一种标准的方式来获取数据库连接,而不是直接使用
DriverManager等方式。DataSource它提供了getConnection方法,这样更为灵活、高效、可维护的方式。

  • 不同的提供商只需实现DataSource,而程序员无需关心连接的提供方式。所以通过DataSource可以有很多种连接提供方式。可以用最原始的通过DriverManager获取链接,也可以通过连接池。
  • DriverManager每次使用都得建立数据库的远程连接;而且DataSource可以从连接池中获取一个现有的连接,而不是每次都创建新连接
DataSource 的使用:
- Springboot(直接用,因为 @SpringBootApplication 注解集成了该Bean):- 通过设置其项目的配置文件,设置对应的sql驱动器、账号密码;- 直接通过依赖注入其DataSource,后续直接使用其对象的getConnection()方法进行连接,就可以直接访问,且Spring boot默认使用的是Hikari连接池
- Spring- 得自己创建Bean,并且内部得实现一遍对应的连接器,连接池如DriverManagerDataSource等。DriverManagerDataSource 的使用;DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();  driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");  driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false  "); driverManagerDataSource.setUsername("root");  driverManagerDataSource.setPassword("123456");

什么是连接池:数据库连接池是一种管理数据库连接的技术,它通过缓存和重复利用连接,减少了应用程序与数据库交互时的连接创建和关闭开销,提高了效率。连接池还能控制连接数量,有效管理系统资源。(Druid、Hikari(spring boot默认)

4.3 Jdbc && JdbcTemplate 操作数据库

JDBC 是 Java 数据库连接的标准 API,提供基本的 CRUD 操作,但操作较为繁琐,需要手动管理连接、语句和结果集。而 JdbcTemplate 是 Spring 框架提供的工具类,它封装了 JDBC 的底层细节,简化了数据库操作,自动处理连接管理和异常,允许开发者更方便地执行 SQL 语句

Jdbc

  • 首先通过定义好 DataSource 对象;
  • 通过实例 Connection 对象 ,即Connection connection = dataSource.getConnection();
  • 接着创建Statement对象,即Statement statement = connection.createStatement();
  • 后续就调用 Statement对象的一些方法如:executeUpdate(“sql”)、executeQuery(“sql”)等方法进行数据库的操作。

JdbcTemplate

  • Springboot 内部注解配置好了jdbcTemplate,使用时直接使用@Autowired 注解标记 JdbcTemplate 这个bean,这样在事务代码在直接使用其 对象.update(“sql”) 来进行数据库的CRUD,代码如下:
String sql="update t_user set username=? where id=?";
int result = jdbcTemplate.update(sql,"徐庶",1);
  • Spring里面得和DataSource一样,在配置类里面配置好对应的Bean,才能在业务逻辑中使用,使用代码如下:
@Bean  
public JdbcTemplate jdbcTemplate(DataSource dataSource) {  //这里的DataSource也是在我的配置类里面配置好 return new JdbcTemplate(dataSource);  
}

4.4 Spring 声明式事务 && 编程式事务

原生的事务代码(编程式事务)

  • 在前期原始的 jdbc 中实例化 connection 对象后,分别在三个不同位置开启事务:
  • 在使用sql语句访问数据库前用于开启事务:connection.setAutoCommit(false);
  • 在执行sql语句结束后提交事务:connection.commit();
  • 在catch捕获异常中回滚事务:connection.rollback();

声明式事务

统一在业务代码方法上面添加 @Transactional 注解
@Transactional
- @Transactional 可以标记在Bean上面(当前类所有的方法都运用上了事务)
- @Transactional标记在方法则只是当前方法运用事务
(也可以类和方法上面同时都存在,如果类和方法都存在@Transactional会以方法的为准。如果方法上面没有@Transactional会以类上面的为准)

建议:@Transactional写在方法上面,控制粒度更细,建议@Transactional写在业务逻辑层上,因为只有业务逻辑层才会有嵌套调用的情况。(同时在类和方法上面存在就以方法为主)
.
在使用 @Transactional 注解时,测试类中的事务会在方法执行结束后自动回滚。这是为了确保测试的幂等性,即每次运行测试时都不会对数据库产生持久影响

Spring 框架 和 Spring boot应用的区别

  • spring里面(额外添加)
    • 在配置类上面额外添加开启事务注解 @EnableTransactionManagement
    • 配置事务管理器,即在配置类里面定义事务管理器方法
@Bean
public TransactionManager transactionManager(DataSource dataSource){return new DataSourceTransactionManager(dataSource);
}
  • spring boot里面(直接方法加个注解就可以)

4.5 事务配置的属性

属性类别:

  • isolation:设置事务的隔离级别
  • propagation:事务的传播行为
  • noRollbackFor:那些异常事务可以不回滚
  • noRollbackForClassName:填写的参数是全类名
  • rollbackFor:哪些异常事务需要回滚
  • rollbackForClassName:填写的参数是全类名
  • readOnly:设置事务是否为只读事务
  • timeout:事务超出指定执行时长后自动终止并回滚,单位是秒

1. isolation 隔离

主要出现的是并发中存在的问题

脏读:同一个数据库中,事务2先修改数据,事务1后提取数据;事务2回滚数据,数据1提取的是事务2修改后的数据。————造成了数据不一致,违背了A(原子性)

解决方案:@Transactional(isolation=Isolation.READ_COMMITTED)——读已提交 (行锁,读不会加锁)

  • 不用设置,因为数据库已经帮我们保证读已提交( 本质上其实是加了行锁,但读不会加锁),即事务1只能读取事务2已修改的数据。

不可重复读:同一个数据库中,事务1先读取数据,事务2后修改数据;事务1再提取数据,事务1提取的是事务2修改后的数据。————造成了数据不一致,违背了A(原子性)

解决方案:@Transactional(isolation=Isolation.REPEATABLE_READ)——重复读卖 (行锁,读和写都会上锁)

  • 确保Transactlon01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。 (行锁)

幻影读:针对整张表,事务1,多次对数据进行整表数据读取(统计),但读取其间,事务2突然对表插入数据,导致事务1在读取前后的数据不一致————造成了数据不一致,违背了A(原子性)

解决方案:@Transactional(isolation = Isolation.SERIALIZABLE)——串行化 (表锁)

  • 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。(表锁)

总结:

  • 并发安全:SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED
  • 运行效率:READ_COMMITTED>REPEATABLE_READ>SERIALIZABLE
    .
    MySql:默认就是重复度的级别 REPEATABLE_READ
    Oracle:默认是读已提交 READ_COMMITTED

2.propagation 传播

事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?希望如果外部存在事务就用外部的,外部不存在就自己开启事务。
使用时直接:@Transactional(propagation = xxxx)

传播属性说明适用场景
REQUIRED如果当前存在事务,则加入该事务;如果没有,则创建一个新的事务。大多数情况下使用此属性。适用于增删改查
REQUIRES_NEW每次调用都会创建一个新的事务。如果当前存在事务,则将其外层事务挂起,执行其新的事务。需要确保当前操作独立于其他事务。适用于内部事务和外部事务不存在业务关联情况,如日志
NESTED在当前事务中嵌套一个新的事务;如果没有事务,则与 REQUIRED 相同。需要能够回滚嵌套的操作,而不影响外部事务。
SUPPORTS如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式执行。希望在有事务时使用事务,没有时则不需要事务。适用于查询
NOT_SUPPORTED始终以非事务方式执行,即使当前存在事务,也会将其挂起。执行不需要事务的操作,例如某些查询。
NEVER始终以非事务方式执行,如果当前存在事务则抛出异常。确保某个操作绝对不在事务中执行。
MANDATORY方法必须在一个已经存在的事务中运行。如果没有活动的事务,抛出异常。确保操作只能在事务上下文中执行。

3.readOnly 事务只读

readonly:只会设置在查询的业务方法中(怎么说肉眼看不出优化的,相当于暗示数据库去优化)

  • connection.setReadOnly(true)通知数据库,当前数据库操作是只读,数据库就会对当前只读做相应优化
  1. readonly并不是所有数据库都支持的,不同的数据库下会有不同的结果。
  2. 设置了readonly后,connection都会被赋予readonly,效果取决于数据库的实现。
  • 使用:在业务逻辑的读方法中书写@Transactional(readonly = true),如果改业务逻辑包含其他的插入操作会报错

4.timeout 超时属性

指定事务等待的最长时间 (秒)

  • 当前事务访问数据时,有可能访问的数据被别的数据进行加锁的处理,那么此时事务就必须等待,如果等待时间过长给用户造成的体验感差。
  • 使用:在业务逻辑方法中书写@Transactional(timeout = x) 设置x秒,当该业务代码在运行CRUD之前已经浪费时间超过x秒,这会导致当前事务超时,进而回滚操作

5.异常属性

设置当前事务出现的那些异常就进行回滚或者提交。
默认对于RuntimeException及其子类采用的是回滚的策略。
默认对于非RuntimeException就不会回滚(事件全回滚)

1、设置哪些异常不回滚(noRollbackFor)
2、设置哪些异常回滚(rollbackFor)

  • 在业务逻辑方法上书写 @Transactional(rollbackFor = Exception,calss) 意味这所有的异常都将会回滚

4.6 事务的底层原理即失效原因

事务底层原理:动态代理(注解里面内置的,而且AOP里面的切面是我们自定义的)

public cglib extends 目标类{@overriderpublic add(){try{开启事务super.add()  //执行数据库的操作提交事务}catch(){回滚事务}}
}

事务(AOP)的失效原因:
配置层面:
1.保证事务的类,配置为了一个Bean
2.事务的方法不能是private
3.自己把异常捕捉了,并且没有抛出去(即在方法add里面抛出的异常我动态代理里面的catch没办法捕获)

底层原理层面
4.动态代理层面失效原因
1. 要让AOP、事务生效记住一个原则,一定要通过动态代理的对象调用目标方法,不能通过普通对象直接调用
2. 直接调用本类的方法一没有通过动态代理的对象调用目标方法 解决方案
i.将本类Bean自动装配进来(会产生循环依赖,springboot中需要单独开启循环依赖支持)
ii . ((StockService)AopContext.currentProxyO) 前提:
1. @EnableAspectJAutoProxy(exposeProxy=true)
- 意味着通过一个AOP动态代理执行方法,当前的动态代理对象存在ThreadLocal
2. 添加aop的依赖支持
iii. 把本类的方法移动到其他的Bean中,然后再把对应的那个Bean自动装配进来

五、AOT

AOT的全称是“Ahead-Of-Time Compilation”,即提前编译。它是一种编译技术,在程序运行之前将源代码或字节码编译成机器码。这与即时编译(JIT,Just-In-Time Compilation)相对,后者是在程序运行时进行编译。

AOT的优点包括:

  1. 启动速度快:因为在运行时不需要再编译。
  2. 更好的性能:可以进行更多的优化,因为可以在编译时考虑整个应用程序。
  3. 减少内存使用:可以减少运行时的内存占用。

AOT通常用于嵌入式系统、移动应用和某些Java框架(如GraalVM)

相关文章:

  • K8S_ResourceQuota与LimitRange的作用
  • Materials Studio学习笔记(一)——Materials Studio软件介绍
  • Flutter学习 滚动组件(1):ListView基本使用
  • 【差分隐私相关概念】瑞丽差分隐私(RDP)命题4
  • 宝塔面板中解锁Laravel日志查看的奥秘
  • pull.rebase 三种模式的应用场景
  • java的类加载器及其双亲委派机制
  • 解决docker安装OpenWebUI 报错 500
  • Node.js 数据库 CRUD 项目示例
  • uni-app/微信小程序接入腾讯位置服务地图选点插件
  • STM32F407实现SD卡的读写功能
  • #[特殊字符]Rhino建模教程 · 第一章:正方体建模入门
  • docker 启用portainer,容器管理软件
  • Flowable工程化改造相关文档
  • AI大模型如何重塑科研范式:从“假说驱动”到“数据涌现”
  • 11【模块学习】DS18B20(一):使用学习
  • 免费的内网穿刺工具和免费域名
  • **Windows 系统**的常用快捷键大全
  • C语言实战:用Pygame打造高难度水果消消乐游戏
  • Linux路漫漫
  • 最高法报告重申保护创新主体权益:加大侵权损害赔偿力度
  • 在黄岩朵云书院,邂逅陈丹燕与月季花的故事
  • 拉卡拉一季度净利约1亿降超五成,去年净利3.5亿降逾23%
  • 《哪吒之魔童降世》电影版权方诉《仙侠神域》游戏运营方侵权案开庭
  • “两高”司法解释:升档为境外非法提供商业秘密罪的量刑标准
  • 上海开展2025年“人民城市 文明风采”群众性主题活动