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
-
依赖注入版本演化:
- spring1 xml
- spring2 xml (applicationcontext)
- spring3 javaconfig+@
-
配置操作:
- 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
-
实例化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);调用启动类。
- 使用实例工厂方法实例化@Bean
2.2 依赖注入DI
要实现自动DI一定得使用@Autowired?
- @Bean方法中参数会自动注入;
- 构造函数的参数会自动注入;
特性:
-
@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;
}
-
@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;
- 通过 @Primary 来设置某一个为主要的
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的生命周期
- 配置@Component @Bean … @Autowired
- 加载Spring容器
- 实例化(new Bean类)
- 解析依赖注入(解析@Autowired @Value)
- 初始化(调用初始化回调方法,由程序有来配置)
- 基于接口进行回调:通过类实现 InitializingBean 借口,并且重写afterPropertiesSet方法,再初始化Bean时自动调用
- 基于注解@PostConstruct,在Bean类中的函数里面上方书写注解,在最初初始化时会调用该函数(且注解的调用的优先级大于接口调用)
- 基于initMethod属性,即对应的Bean的注解旁边加一个@Bean(initMethod = “类内部函数”)
顺序:注解@PostConstruc——>基于initMethod属性——>基于接口
- 最终放入Map<beanName,bean对象>
- Sring容器.getBean(“beanName”)---->Map<beanName,bean对象>
- spring容器关闭 bean就会销毁(调用销毁回调方法,由程序有来配置)
- 基于接口进行回调:通过类实现 DisposableBean 借口,并且重写 destory 方法,再容器关闭时Bean时自动销毁
- 基于注解 @PreDestory,在Bean类中的函数里面上方书写注解,在容器关闭时会调用该函数
- 基于 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容器
- Spring已经帮我解决了这个问题,关于Spring底层源码的概念(Spring怎么解决的循环依赖)
- Spring Boot中会报错,因为Spring Boot认为这个是一个不合理的设计
解决:
- 在Spring Boot里面的配置文件applicationcontext里面放开循环依赖的限制:
#放开循环依赖的限制
spring.main.allow-circular-references=true
- 代码设计层面:
- 把依赖的方法,直接写在本类中;
- 添加一个中间体,中间类去依赖A/B,然后让中间类去阻止他们的依赖方法;
- 延迟注入:
- 添加需要依赖的构造函数参数
- 添加@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
- 内部维护一个BeanDefinitionMap<beanName,BeanDefinition>
项目中定义 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、用户鉴权、日志、全局异常处理等功能。
静态代理弊端:
- 代理类不需要自己声明
- 一个代理类只能代理一个类型
动态代理从静态代理演变而来的
动态代理分类:
- JDK(Java Development Kit)动态代理
- JDK 代理是基于 Java 的接口实现的动态代理。它只能代理实现了接口的类。
- 实现原理:
- JDK 代理通过
java.lang.reflect.Proxy
类来创建代理对象。 - 需要提供一个接口和一个实现了
InvocationHandler
接口的处理器。调用代理对象的方法时,会转发到处理器的invoke
方法。
- JDK 代理通过
- CGLIB动态代理
- CGLIB(Code Generation Library)代理是通过字节码生成技术实现的,可以代理没有实现接口的类。
- 实现原理:
- CGLIB 使用 ASM 库生成目标类的子类,并重写其方法来实现代理。
- 由于它是通过继承来实现的,因此不能对 final 类和 final 方法进行代理。
动态代理:
- 创建容器ioc=new applicationContext();
- spring把所有的bean进行创建,进行依赖注入
- spring( new $$ SpringCGLIB $$0))---->ioc.getBean(UserService.class)
- 当实现了AOP , spring会根据当前的bean创建动态代理
- 把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)通知数据库,当前数据库操作是只读,数据库就会对当前只读做相应优化
- readonly并不是所有数据库都支持的,不同的数据库下会有不同的结果。
- 设置了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的优点包括:
- 启动速度快:因为在运行时不需要再编译。
- 更好的性能:可以进行更多的优化,因为可以在编译时考虑整个应用程序。
- 减少内存使用:可以减少运行时的内存占用。
AOT通常用于嵌入式系统、移动应用和某些Java框架(如GraalVM)