1、@Transactional注解控制事务有哪些不生效的场景
- 数据库引擎不支持事务
- 数据源没有配置事务管理器
- 没有在spring配置文件中开启事务管理器
- 事务注解被覆盖导致事务失效
- 配置错误的 @Transactional 注解
- 事务超时时间设置过短
- 使用了错误的事务传播机制(Propagation.NOT_SUPPORTED传播特性不支持事务)
- rollbackFor属性配置错误(其实rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeException和Error两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效)
- service类没有被spring管理
- 方法的访问权限不是public的
- 事务的方法被final、static关键字修饰了
- 同一个类中方法调用,导致@Transactional失效
- 异常被捕获了并处理了,没有重新抛出(事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚。)
- 手动抛了别的异常(失效的原因 :上面的代码例子中,手动抛了Exception异常,但是是不会回滚的,因为Spring默认只处理RuntimeException和Error,对于普通的Exception不会回滚,除非,用rollbackFor属性指定配置。
解决方案:添加属性配置@Transactional(rollbackFor = Exception.class)) - 事务多线程的调用(这是因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文 ,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。Spring事务管理器通过使用线程本地变量(ThreadLocal)来实现线程安全。)
举例最后一个的解决办法:
- 第一种解决办法,新建一个类一个方法,在a中注入新类,再通过新类调用事务注解的方法。
- 第二种解决方法,就是在该类中注入自己,通过在a方法中,通过自己类的对象调用b的事务注解方法。
2、MySQL的优化
- 做MySQL优化,我们要善用EXPLAIN查看SQL执行计划
- SQL语句中IN包含的值不应过多
- SELECT语句务必指明字段名称
- 当只需要一条数据的时候,使用limit 1
- 如果限制条件中其他字段没有索引,尽量少用or
- 尽量用union all代替union(使用union all前提是两张表没有重复数据)
- 使用合理的分页方式以提高分页的效率(数据量太大,分页会越来越慢,建议使用id为条件来进行分页处理更快)
- 分段查询(数据达到百万级,可以使用分段查询,循环展示数据,要的时候再加载数据)
- 避免在where子句中对字段进行null值判断
- 不建议使用%前缀模糊查询
- 避免在where子句中对字段进行表达式操作
- 对于联合索引来说,要遵守最左前缀法则(举列来说索引含有字段id、name、school,可以直接用id字段,也可以id、name这样的顺序,但是name;school都无法使用这个索引。所以在创建联合索引的时候一定要注意索引字段顺序,常用的查询字段放在最前面。)
- 注意范围查询语句(对于联合索引来说,如果存在范围查询,比如between、>、<等条件时,会造成后面的索引字段失效。)
- JOIN优化尽量使用inner join(等值关联查询),尽量使用小表来驱动大表。
3、除了使用try…catch来抛异常还有其他的方法吗?
用 Assert(断言) 替换 throw exception
想必 Assert(断言) 大家都很熟悉,比如 Spring 家族的 org.springframework.util.Assert,在我们写测试用例的时候经常会用到,使用断言能让我们编码的时候有一种非一般丝滑的感觉,比如:
@Test
public void test1() {
...
User user = userDao.selectById(userId);
Assert.notNull(user, "用户不存在.");
...
}
@Test
public void test2() {
// 另一种写法
User user = userDao.selectById(userId);
if (user == null) {
throw new IllegalArgumentException("用户不存在.");
}
}
也非常推荐大家使用断言来抛异常,会让代码更加的优雅。
4、Feign 第一次调用为什么会很慢?
首先要了解Feign是如何进行远程调用的,这里面包括,注册中心、负载均衡、FeignClient之间的关系,微服务通过不论是eureka、nacos也好注册到服务端,Feign是靠Ribbon做负载的,而Ribbon需要拿到注册中心的服务列表,将服务进行负载缓存到本地,然后FeignClient客户端在进行调用,大概就是这么一个过程。
我们知道ribbon是靠LoadBalancer做负载的 无非就是ILoadBalancer接口的方法,依次是添加新的服务、在负载均衡里选择一个服务、markServerDown服务下线、获取服务列表、获取存活的服务器、获取所有服务器(包括健康和不健康的)。
解决方法:
- 开启Ribbon饥饿加载;在项目启动的时候,可以从日志看到,已经把Lxlxxx-system2服务进行加载,从而避免了第一次请求超时的情况;
- 其实这种饥饿加载模式,类似于“客户端负载预热”的一个操作,项目启动的时候进行加载,防止服务之间调用可以因为数据量、业务逻辑处理复杂性导致接口超时,如果你的服务之间调用业务处理比较复杂、且慢,不妨可以试试这种解决方式。