IOC是什么?解决了什么问题?

IoC(Inverse of Control:控制反转)是一种设计思想 或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。

AOP 是什么?解决了什么问题?

AOP:Aspect oriented programming 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。
AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
AOP的难点:切面逻辑的难点不是不修改原有业务,而是对所有业务生效

比如:日志记录、性能统计、安全校验、事务管理,等等。这些边缘逻辑往往贯穿你整个核心业务,传统 OOP 很难将其封装:如图。
image.png

AOP 不是 OOP 的对立面,它是对 OOP 的一种补充。OOP 是纵向的,AOP 是横向的,两者相结合方能构建出良好的程序结构。AOP 技术,让我们能够不修改原有代码,便能让切面逻辑在所有业务逻辑中生效

通知有5种类型

  • before 目标方法执行前执行,前置通知
  • after 目标方法执行后执行,后置通知
  • after returning 目标方法返回时执行 ,后置返回通知
  • after throwing 目标方法抛出异常时执行 异常通知
  • around 在目标函数执行中执行,可控制目标函数是否执行,环绕通知

    AOP的实现原理

    JDK的动态代理(proxy)

  • 目标类是接口,对目标接口进行加强

  • 实现InvocationHandler接口

    CGLIB代理

  • 实现MethodInterceptor接口

  • CGLIB 动态代理的实现机制是生成目标类的子类,通过调用父类(目标类)的方法实现,在调用父类方法时再代理中进行增强。

    spring事务的实现

    spring支持编程式事务管理和声明式事务管理两种方式。

  • 编程式事务使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

  • 声明式事务是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

编程式事务指的是通过编码方式实现事务;
声明式事务基于 AOP,将具体的逻辑与事务处理解耦。
声明式事务管理使业务代码逻辑不受污染,因此实际使用中声明式事务用的比较多。


事务属性包含了5个方面,如图所示:

image.png
一、传播行为:当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
    Spring 定义了如下七中传播行为,这里以A业务和B业务之间如何传播事务为例说明:
  ①、PROPAGATION_REQUIRED :required , 必须。默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。
  ②、PROPAGATION_SUPPORTS:supports ,支持。A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。
  ③、PROPAGATION_MANDATORY:mandatory ,强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。
  ④、PROPAGATION_REQUIRES_NEW :requires_new,必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。
  ⑤、PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。
  ⑥、PROPAGATION_NEVER :never,从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行。
  ⑦、PROPAGATION_NESTED :nested ,嵌套。A和B底层采用保存点机制,形成嵌套事务。
二、隔离级别:定义了一个事务可能受其他并发事务影响的程度。
  并发事务引起的问题:
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致以下的问题。
①、脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
②、不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
    ③、幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
注意:不可重复读重点是修改,而幻读重点是新增或删除。
  在 Spring 事务管理中,为我们定义了如下的隔离级别:
  ①、ISOLATION_DEFAULT:使用后端数据库默认的隔离级别
  ②、ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  ③、ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  ④、ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  ⑤、ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的
三、只读
  这是事务的第三个特性,是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
四、事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
五、回滚规则
  事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的) 。但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。


@Transactional失效的场景

1、@Transactional 应用在非 public 修饰的方法上

如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效。之所以会失效是因为在Spring AOP 代理时, TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用AbstractFallbackTransactionAttributeSource的computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

  1. protected TransactionAttribute computeTransactionAttribute(Method method,
  2. Class<?> targetClass) {
  3. // Don't allow no-public methods as required.
  4. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
  5. return null;
  6. }

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

2、@Transactional 注解属性 propagation 设置错误

这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

3、@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。
image.png

  1. // 希望自定义的异常可以进行回滚
  2. @Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class

4、同一个类中方法调用,导致@Transactional失效

解决方案:同一个类中方法调用@Transactional注解失效解决方案
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
同一个类中的方法save()调用insert()事务方法,insert()出错,并不会回滚,即事务不起作用。代码如下

  1. @Test
  2. public void save(){
  3. t.insert();
  4. BrandEntity brandEntity = new BrandEntity();
  5. brandEntity.setName("huawei---");
  6. brandEntity.setDescript("这是一款国产手机");
  7. brandService.save(brandEntity);
  8. }
  9. @Transactional
  10. public void insert(){
  11. BrandEntity brandEntity = new BrandEntity();
  12. brandEntity.setName("huawei-t");
  13. brandEntity.setDescript("这是一款国产手机");
  14. brandService.save(brandEntity);
  15. int i = 1/0;
  16. }

5、异常被 catch“吃了”导致@Transactional失效

这种情况是最常见的一种@Transactional注解失效场景,

  1. @Transactional
  2. private Integer A() throws Exception {
  3. int insert = 0;
  4. try {
  5. CityInfoDict cityInfoDict = new CityInfoDict();
  6. cityInfoDict.setCityName("2");
  7. cityInfoDict.setParentCityId(2);
  8. /**
  9. * A 插入字段为 2的数据
  10. */
  11. insert = cityInfoDictMapper.insert(cityInfoDict);
  12. /**
  13. * B 插入字段为 3的数据
  14. */
  15. b.insertB();
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. }

如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?
答案:不能!
会抛出异常:

  1. org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的
UnexpectedRollbackException异常。
实验结果如下:
image.png
image.png

6、数据库引擎不支持事务

这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。