Spring
1、基础
-
Spring是一个轻量级的容器框架。两大核心控制反转(IoC)和面向切面编程(AOP)。
Spring5在2017年9月。
优点:
- 集成很多其他的框架,使用其他框架会很方便
- IOC,AOP,声明式事务
- 免费开源,轻量级,非入侵式
缺点:
- 体系庞大,有学习成本
- 使用了大量的反射机制,反射机制非常占用内存。
-
Spring的模块,参考
- Spring Core:核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。主要提供IOC等功能。
- Spring AOP:面向切面编程。
- Spring MVC:用于前后端调用。
- 持久层:用于数据在数据库中的持久化。
-
Spring项目的创建,就是先建立一个maven工程,然后加入springframework
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency>
-
程序的耦合和解耦
- 耦合:程序间的依赖关系。在开发中,应该解决一些编译期就存在的依赖的问题,比如加载驱动的时候,没有相关的类就报错error。所以我们想的是,编译期不依赖,运行时才依赖。
- 解耦的思路: 1、使用反射来创建对象,而避免使用new关键字。2、通过读取配置文件来获取要创建的对象全限定类名。(作用是更加解耦,比如换一个驱动名)
public static void main(String[] args) throws SQLException, ClassNotFoundException { //注册驱动的两种方式 // 1. 创建驱动类的实例 ,若没有该jar,则编译器就直接编译不过去,提示的是错误error。 //DriverManager.registerDriver(new com.mysql.jdbc.Driver()); // 2. 通过反射加载驱动类,若没有该jar,则会报运行时异常, Class.forName("com.mysql.jdbc.Driver"); // 实际开发中此类名从properties文件中读取 }
-
解耦实例2:controller层,Service层,Dao层的调用
private IMyDao myDao = new MyDaoImpl(); // 业务层要调用持久层的接口和实现类
在业务Service层直接调用了持久层的接口和实现类,若没有该实现类,则直接编译过不去。我们希望编译能过去,只是运行时,即使不存在,也只是报错运行时异常。
按照上面的思路:第一个,反射;第二个,配置文件。
解决耦合的思路: 工厂模式解耦
在实际开发中可以把三层的对象的全类名都使用配置文件保存起来,当启动服务器应用加载的时候,创建这些对象的实例并保存在
容器
中。在获取对象时,不使用new的方式,而是直接从容器
中获取,这就是工厂设计模式。下面代码还存在单例和多例的问题,所以解决多例变单例的思路,可以在static类初始化的时候,先创建对象,然后使用map存起来。之后直接根据类名获取即可。
在 Spring 中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化、组装和管理的对象。
2、IOC/DI
-
IOC和DI
- IOC是控制反转,只是一种思想,是程序将创建bean和维护bean的关系的控制权反转由IOC容器完成。
- IOC的作用:解耦,解决程序间的依赖关系,也就是程序解耦。
- DI是依赖注入,是IOC的一种具体实现方式。
我们之前说,解耦的思想是反射和全限类名。那么可以看到xml配置文件中,就有一个id和class指定的全限类名。
<?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">
<!--把对象的创建交给spring来管理-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"</bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
</beans>
那么在获取的时候,就可以从spring上下文,也就是一个容器中获取了。
public class Client {
public static void main(String[] args) {
// 获取核心容器对象(spring上下文)
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
}
}
那么这个容器有三种,其最上面的接口都是beanfactory。
-
classPathXmlApplicationContext
: 它是从类的根路径下加载配置文件 -
FileSystemXmlApplicationContext
: 它是从磁盘路径上加载配置文件 -
AnnotationConfigApplicationContext
: 读取注解创建容器
//类的根路径下
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
RegisterDAO registerDAO = (RegisterDAO)ac.getBean("RegisterDAO");
//按照文件的磁盘路径
ApplicationContext appCt2 = new FileSystemXmlApplicationContext("D:/app.spring.xml");
ApplicationContext appCt2 = new FileSystemXmlApplicationContext("classpath:app.spring.xml");
//配合@Configuration使用
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
@Configuration
public class AppConfig {
@Bean(name="entitlement")
public Entitlement entitlement() {
Entitlement ent = new Entitlement();
ent.setName("Entitlement");
ent.setTime(1);
return ent;
}
}
几乎所有的应用场合我们都直接使用ApplicationContext 而非底层的BeanFactory。
由于若是单例的,所以最好还是容器一创建的时候,就一起创建对象比较好。资源匮乏的环境可以使用bean工厂,它也适合多例对象的情况,用的时候就去创建。
【注意】
ssm项目是打包为war,所以在上下文启动的时候,就加载了bean到容器中。若是main 方法时候的话,就需要new ac进行装载bean。而springboot是jar,里面有自带的tomcat,所以有一个启动类。
-
配置bean 的三种方式:XML、注解、javaConfig方式(@Configuration,@Bean)
//该注解表示这个类为javaConfig类 @Configuration public class MyConfig { //该注解表示:向容器中注册一个叫做myCar的对象;名字为方法名car @Bean public Car car() { return new Car("保时捷","911",300); } @Bean("myCar") public Car car() { return new Car("保时捷","911",300); } }
-
创建bean的三种方式:构造器,静态工厂,实例工厂
1:调用构造器创建Bean
最常见的方式,
<bean id="user" class="test.spring.bean.User">
2:调用实例(普通)工厂方法创建Bean
场景:用于某个方法的返回值,放入上下文容器中
被创建的bean指定factory-bean和factory-method
<bean id="ch" factory-bean="personFactory" factory-method="getPerson">
场景:用于某个方法的返回值,放入上下文容器中
被创建的bean指定class(class指定静态工厂实现类)和factory-method,<bean id="chinese" class="com.mao.staticFactory.PersonFactory" factory-method="getPerson">
实例(普通)工厂方法:
factory-bean :该属性指定工厂Bean的id
factory-method:该属性指定实例工厂的工厂方法。
静态工厂方法:
class:指定静态工厂的实现类,告诉Spring该Bean实例应该由哪个静态工厂创建(指定工厂地址)
factory-method:指定由静态工厂的哪个静态方法创建该Bean实例(指定由工厂的哪个车间创建Bean)
三者的区别:
通过默认的无参构造方式创建,本质就是把类交给Spring自带的工厂(BeanFactory)管理
通过实例工厂创建,其本质就是把创建实例的工厂类交由Spring管理
通过静态工厂创建,其本质就是把类交给我们自己的静态工厂管理
其实调用实例工厂创建Bean和调用静态工厂创建Bean的区别就在于,调用实例工厂将工厂单独拿了出来(先实例化工厂)创建一个工厂Bean。
人话就是,工厂类也被配置为一个bean了,交给了spring管理
https://blog.csdn.net/ligeforrent/article/details/80744756
https://blog.csdn.net/weixin_40929150/article/details/81262891
-
DI依赖注入三种方式:setter属性注入,构造器注入,和其他注入(p空间,c空间,工厂方法等)
DI依赖注入都是完成属性的赋值,xml方式里面有3种,注解方式里面也有3种
-
setter属性注入
<bean id="user" class="test.spring.bean.User"> <property name="name" value="小明"/> <property name="password" value="123456"/> </bean>
-
构造器注入:顺序,类型,参数名
<bean id="accountService" class="org.com.qst.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="mys"></constructor-arg> <constructor-arg name="age" value="21"></constructor-arg> <constructor-arg name="time" ref="now"></constructor-arg> </bean> <bean id="now" class="java.util.Date"></bean>
-
其他(工厂方法,p和c命名空间等)
-
-
bean的声明周期
初始化,属性注入,3个aware,前置方法,2个init方法,后置方法,可使用,2个destory
https://www.cnblogs.com/javazhiyin/p/10905294.html
-
bean的作用域参考
主要是单例和多例。其他三个都是做用于web应用,也就是http请求相关的逻辑。
作用域 描述 singleton单例 该作用域将 bean 的定义的限制在每一个 Spring IoC 容器中的一个单一实例(默认)。 prototype多例 该作用域将单一 bean 的定义限制在任意数量的对象实例。 request 该作用域将 bean 的定义限制为 HTTP 请求。只在 web-aware Spring ApplicationContext 的上下文中有效。 session 该作用域将 bean 的定义限制为 HTTP 会话。 只在web-aware Spring ApplicationContext的上下文中有效。 global-session 该作用域将 bean 的定义限制为全局 HTTP 会话。只在 web-aware Spring ApplicationContext 的上下文中有效。集群中,某台服务器创建的session,全集群知道。 <!-- A bean definition with singleton scope --> <bean id="..." class="..." scope="singleton"> <!-- collaborators and configuration for this bean go here --> </bean>
-
注解方式的装配bean
//反射创建一个对象,并放入上下文容器中,id为类名的首字母小写,在spring中,还需要在xml配置文件中编写开启注解的代码。 @Service public class HelloServiceImpl implements HelloService { }
-
@Autowired 与@Resource
- 注解Autowired:默认使用byType来自动装配,如果存在类型的多个实例,就尝试使用byName匹配,也就是该变量的名字,如果通过byName也确定不了,可以通过Primary和Priority注解来确定。可以与
@Qualifier
一起指定使用。 - 注解@Resource:默认名字,没有则按照类型
https://blog.csdn.net/weixin_38237873/article/details/83650429
https://www.jianshu.com/p/2f1c9fad1d2d
https://blog.csdn.net/weixin_40423597/article/details/80643990
-
自动装配
public interface AutowireCapableBeanFactory{ //无需自动装配 int AUTOWIRE_NO = 0; //按名称自动装配bean属性 int AUTOWIRE_BY_NAME = 1; //按类型自动装配bean属性 int AUTOWIRE_BY_TYPE = 2; //按构造器自动装配 int AUTOWIRE_CONSTRUCTOR = 3; //设置全局默认的 }
-
@Configuration和@Import
@Configuration定义是一个配置类、
@Import只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中;
1、直接导入类名。这里类名可以是一个配置类,也可以是一个具体的业务类。
@Import({ 类名.class , 类名.class… })
public class TestDemo {}
2、ImportSelector方式【重点】
3、ImportBeanDefinitionRegistrar方式
参考https://www.zhihu.com/question/428542278/answer/1645069838
3、AOP
-
AOP (Aspect Orient Programming,AOP 是一种编程思想,就面向切面编程。作用就是保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。aop
- 切面:就是一个类,就是加入的增强功能的类,比如日志切面。
- 通知:类里面的方法,这些都是增强的方法。前置通知,后置通知,返回通知,异常通知,环绕通知。
- 切入点:具体切入哪个地方,被代理对象里想被增强的方法。
- 连接点:哪些地方可以使用切面,就是被代理对象里的各个方法。是个虚的概念,可简单理解为切入点的集合
按照 AOP 框架修改源代码的时机,可以将其分为两类:
1、静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
2、动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
https://www.cnblogs.com/yy3b2007com/p/9129612.html
https://www.cnblogs.com/joy99/p/10865391.html
https://www.cnblogs.com/joy99/p/10941543.html
https://blog.csdn.net/yellow__star/article/details/102510545
-
静态代理参考:代理类和被代理的类实现了同样的接口,代理类同时持有被代理类的引用。装饰者模式就是静态代理的一种体现,需要一开始就实现所有的代码并加载好。
- 优点:不改原来代码,还扩充新功能
- 缺点:一个真实角色就会产生一个代理角色
public class Secretary implements IWork { private Leader mLeader; public Secretary(Leader mLeader) { this.mLeader = mLeader; } @Override public void meeting() { System.out.println("秘书先给qsm老板准备材料"); mLeader.metting(); } @Override public int evaluate(String name) { return mLeader.evaluate(name); } }
-
动态代理参考:动态代理的代理类是动态生成的,有一个模板代码,直接去调用。
- 基于接口的动态代理:JDK 动态代理,利用反射机制生成一个实现代理接口的类(使用Proxy类),在调用具体方法前调用InvokeHandler来处理。
- 基于类的动态代理:CGlib 动态代理,利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别: JDK代理只能对实现接口的类生成代理; CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。 性能: 从 jdk6 到 jdk7、jdk8 ,动态代理的性能得到了显著的提升,而 cglib 的表现并未跟上,甚至可能会略微下降。传言的 cglib 比 jdk动态代理高出 10 倍的情况也许是出现在更低版本的 jdk 上吧。 设计: 2个,都运行10万次,100万次的某个方法。
因此SpringAOP是由动态代理来实现的;
也可以在SpringBoot项目中使用AspectJ,但是它是静态代理,在编译的时候会修改被代理的class字节码文件。
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
-
InvocationHandler接口 和Proxy类:基于接口的动态代理
Proxy生成代理对象的,InvocationHandler 增强方法,并执行对象的方法,也需要返回值
/** * Proxy类中的newProxyInstance方法: * ClassLoader:类加载器;它是用于加载代理对象字节码的。写和被代理对象使用相同的类加载器。 * interfaces:字节码数组;它是让代理对象和被代理对象有相同的方法。即,接口集合。 * InvocationHandler:用于增强逻辑的代码;可以写一个实现它的类,也可以使用匿名内部类。 */ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
实际的例子
public class Teacher implements Person { public static void main(String[] args) { Teacher teacher = new Teacher(); //匿名内部类InvocationHandler Person proxyInstance = (Person)Proxy.newProxyInstance(teacher.getClass().getClassLoader(), teacher.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置通知"); Object result = method.invoke(teacher, args); System.out.println("后置通知"); return result; } }); proxyInstance.tea(); //实现了MyInvocationHandler,并且内部维护一个Object的被代理人,并把Proxy.newProxyInstance的方法也维护了。主要是简化外部生成代理的代码。 MyInvocationHandler myInvocationHandler = new MyInvocationHandler(teacher); Person proxy = (Person) myInvocationHandler.getProxy(); //实现了MyInvocationHandler,可以不维护被代理人。也就是显示的实现InvocationHandler的invoke方法而已。 Person proxy = (Person) Proxy.newProxyInstance(teacher.getClass().getClassLoader(), teacher.getClass().getInterfaces(), myInvocationHandler); System.out.println(proxy.tea()); } @Override public String tea() { System.out.println("tea"); return "1"; } } public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target = target; } public Object getProxy() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } /** * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0 * method:我们所要调用某个对象真实的方法的Method对象 * args:指代代理对象方法传递的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("反射开始"); System.out.println("proxy" + proxy.getClass().getSimpleName()); System.out.println("target" + target.getClass().getSimpleName()); Object result = method.invoke(target, args); System.out.println("反射结束"); return result; } }
-
MethodInterceptor接口和Enhancer 类:基于类的动态代理
public class Teacher implements Person { public static void main(String[] args) { //使用MethodInterceptor匿名内部类 Teacher teacher2 = new Teacher(); Teacher cglibProxyTeacher2 = (Teacher)Enhancer.create(teacher2.getClass(), new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("增强逻辑"); Object result = methodProxy.invokeSuper(proxy, args); System.out.println("收尾逻辑"); return result; } }); cglibProxyTeacher2.tea(); //MethodInterceptor的实现类 Teacher teacher3 = new Teacher(); Teacher cglibProxyTeacher3 = (Teacher)Enhancer.create(teacher3.getClass(), new CglibProxyIntercepter()); cglibProxyTeacher3.tea(); } } public class CglibProxyIntercepter implements MethodInterceptor { @Override public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("执行前..."); Object object = methodProxy.invokeSuper(sub, objects); System.out.println("执行后..."); return object; } }
利用aop
1、声明式事务
2、mybatis的mapper
3、controller层的日志打印
4、 每个线程的HttpServletRequest,使用@Autowired
即可。
在spring中获取HttpServletRequest,一般在controller的方法的参数直接写入,若是其他地方要用,也可以直接使用自动装配@Autowired
。
原因就在于,注入的HttpServletRequest实际上就是一个代理对象,利用的就是aop的接口类型的动态代理,每次执行一个方法,都是该代理对象去获取该线程正在的request去执行的。那么如何保证线程安全性,多个线程不会共用同一个request,那这个代理对象还有一个对象,该对象经过一些调用操作,最后会看到,有个地方会使用threadlocal线程内部的存储类,存储了每个线程对应的request。
https://www.cnblogs.com/kismetv/p/8757260.html -
AspectJ的通知:基于静态代理。
可以xml配置,也可以注解。可参考https://www.cnblogs.com/huangzhe1515023110/p/9276055.html
/** * @author qsm * log功能 */ @Component("logginAspectJ") @Aspect public class LogginAspectJ { /* *定义一个方法,用于声明切点表达式,该方法一般没有方法体 *@Pointcut用来声明切点表达式 *通知直接使用定义的方法名即可引入当前的切点表达式 */ @Pointcut("execution(* com.linjie.aop.Arithmetic.*(..))") public void PointcutDeclaration() {} //前置通知,方法执行之前执行 @Before("PointcutDeclaration()") public void BeforeMethod(JoinPoint jp) { String methodName = jp.getSignature().getName(); Object[] args = jp.getArgs(); System.out.println("BeforeMethod The method "+ methodName +" parameter is "+ Arrays.asList(args)); System.out.println("add before"); System.out.println(); } //返回通知,方法正常执行完毕之后执行 @AfterReturning(value="PointcutDeclaration()",returning="result") public void AfterReturningMethod(JoinPoint jp,Object result) { String methodName = jp.getSignature().getName(); Object[] args = jp.getArgs(); System.out.println("AfterReturningMethod The method "+ methodName +" parameter is "+Arrays.asList(args)+" "+result); System.out.println(); } //后置通知,方法执行之后执行(不管是否发生异常) @After("PointcutDeclaration()") public void AfterMethod(JoinPoint jp) { String methodName = jp.getSignature().getName(); Object[] args = jp.getArgs(); System.out.println("AfterMethod The method "+ methodName +" parameter is "+Arrays.asList(args)); System.out.println(); } //异常通知,在方法抛出异常之后执行 @AfterThrowing(value="PointcutDeclaration()",throwing="e") public void AfterThrowingMethod(JoinPoint jp,Exception e) { String methodName = jp.getSignature().getName(); System.out.println("AfterThrowingMethod The method "+ methodName +"exception :"+e); } //环绕通知需要携带ProceedingJoinPoint类型的参数 //环绕通知类似于动态代理的全过程,这个类型ProceedingJoinPoint的参数可以决定是否执行目标方法 //且环绕通知必须有返回值,返回值即为目标方法返回值 @Around(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))") public void around(ProceedingJoinPoint proceedingJoinPoint){ Object result = null; String methodName = proceedingJoinPoint.getSignature().getName(); Object[] args = proceedingJoinPoint.getArgs(); //执行目标方法 try { //前置通知 System.out.println("beforeMethod "+methodName+" start with "+args); result = proceedingJoinPoint.proceed(); //返回通知 System.out.println("afterMethod "+methodName+" end with "+result); } catch (Throwable throwable) { //异常通知 System.out.println("afterThrowing "+methodName+" exception with "+ throwable ); throwable.printStackTrace(); } //后置通知 System.out.println("afterMethod "+methodName+" end with "+args); //System.out.println("around "+proceedingJoinPoint); return; } }
4、事务
-
ACID,分别是原子性、一致性、隔离性和持久性
-
声明式事务 xml文件
<!-- 配置事务管理器,不管是用注解方式或xml方式配置事务,一定要有DataSourceTransactionManager事务管理器的支持 事务依赖于数据源所产生的连接对象,只有连接对象创建成功了才能够处理事务 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务通知,依赖于事务管理器 --> <tx:advice id="tx" transaction-manager="dataSourceTransactionManager"> <tx:attributes> <!-- 在设置好的切入点表达式下再次进行事务设置 --> <tx:method name="buyBook"/> <tx:method name="checkOut"/> <!-- 只有select开头的方法才会被事务处理 ,利用通配符--> <tx:method name="select*" read-only="true"/> <tx:method name="insert*"/> <tx:method name="update*"/> <tx:method name="delete*"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 配置切入点表达式,一定是针对于切面如何作用于连接点上。需要将切入点表达式和事务通知联系起来 --> <aop:config> <aop:pointcut id="pointCut" expression="execution(* com.atguigu.book_xml.service.impl.*.*(..))" /> <aop:advisor advice-ref="tx" pointcut-ref="pointCut"/> </aop:config>
-
注解事务 @Transactional,也属于声明式事务,利用SpringAOP的动态代理。
@Transactional(rollback =Exception.class) public void create(String staffId, Integer alloted) { //执行 this.variationDao.create(staffId, alloted); //手动制造异常,由于加了事务,上一条语句产生的数据库更新会被回滚 int i = 10 / 0; }
属性名 说明 name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 propagation 事务的传播行为,默认值为 REQUIRED,一定有事务。查询可以使用SUPPORTS isolation 事务的隔离度,默认值采用 DEFAULT,标识用于数据库的默认隔离级别 timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。 事务的传播行为有7个:常见的有REQUIRED和REQUIRES_NEW,SUPPORTS
**REQUIRED:**支持当前事务,如果当前没有事务,就新建一个事务。如果有,就加入当前事务。适合绝大多数情况。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
下面是required
下面是required_new
并发下事务会产生的问题
1、脏读
所谓脏读,就是指事务A读到了事务B还没有提交的数据。
比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务-->取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。
2、不可重复读
所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。导致不可重复读出现的原因主要是update操作
还是以银行取钱为例,事务A开启事务-->查出银行卡余额为1000元,此时切换到事务B事务B开启事务-->事务B取走100元-->提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
3、幻读
所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。导致幻读问题的原因主要是insert和delete操作。
幻读的例子
比如学生信息,事务A开启事务-->修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务-->事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
事务的隔离级别
READ_UNCOMMITTED:读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
READ_COMMITED:读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读。
REPEATABLE_READ:可重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
SERLALIZABLE:可序列化,即串行话。最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了。
-
编程式事务 TransactionTemplate
https://www.jianshu.com/p/066c989274c6
【完】
正在去BAT的路上修行