Spring框架

1.Spring事务失效的几种场景及原因

spring事务失效.png

  1. 抛出检查异常导致事务不能正确回滚

原因:Spring默认只会回滚非检查异常。检查异常指的是try…catch和throws exception的异常
解法:在事务注解上配置rollbackFor属性,如:
**@Transactional(rollbackFor = Exception.class)**

  1. 业务方法内自己try-catch异常导致事务不能正确回滚

原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉。
解决方案一:异常原样抛出。如:**throw new RuntimeException(e)**;
解决方案二:手动设置**TransactionStatus.setRollbackOnly()**;

  1. AOP切面顺序导致事务不能正确回滚

原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这是若自定义切面没有正确抛出异常,外围的事务切面没有接收到异常信息,就会导致事务失效。
解决方案一:异常原样抛出。如:**throw new RuntimeException(e)**;00.
解决方案二:手动设置**TransactionStatus.setRollbackOnly()**;

  1. 非public方法导致的事务失效

原因:Spring为方法创建代理、添加事务通知、前提条件是该方法是被public修饰的。
解决方案:改为public

  1. 父子容器导致的事务失效

原因:子容器扫描范围过大,把未加事务配置的service扫描进来
解决方案一:各扫描各的,不要图简便
解决方案二:不要用父子容器,所有bean放在同一容器。如springboot就是把bean放在同一容器里,就没有这样的问题。

  1. 调用本类方法导致事务传播行为失效

原因:本类方法调用不经过代理,因此无法增强。
解决方案一:依赖注入自己(代理)来调用;

  1. @AutoWire
  2. private Service service
  3. service.bar();

解决方案二:通过AopContext拿到代理对象,来调用;如,**((Service)AopContext.currentProxy()).bar()**
解决方案三:通过CTW、LTW实现功能增强;

  1. @Transactional没有保证原子行为

原因:事务的原子性仅涵盖insert、update、delete、select…for update语句,select方法并不阻塞
解决方案一:synchronized范围扩大至代理方法调用
解决方案一:使用select…for update替换select

  1. @Transactional方法导致的synchronized失效

原因:synchronized保证的只是目标方法的原子性,环绕目标方法的还有commit等操作,它们并未处于synchronized范围内。
解决方案一:synchronized范围扩大至代理方法调用
解决方案一:使用select…for update替换select

2.Spring的两大核心是什么?谈一谈你对IOC的理解? 谈一谈你对DI的理解? 谈一谈你对AOP的理解?(必会)

  1. Spring的两大核心是:IOC(控制翻转)和AOP(面向切面编程) DI(依赖注入)
  2. IOC的意思是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。最直观的表达就是,IOC让对象的创建不用去new了,可以由spring根据我们提供的配置文件自动生产,我们需要对象的时候,直接从Spring容器中获取即可。是通过反射创建类的对象。

Spring的IOC有三种注入方式 :构造器注入, setter方法注入, 根据注解注入。

  1. DI的意思是依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖Io c容器来动态注入对象需要的外部资源。
  2. AOP,一般称为面向切面编程,作为面向对象的一种补充,主要是为了在不修改源代码的情况下,通过代理对象,可以实现功能的增强。

Spring AOP 中的动态代理主要有两种方式:JDK 动态代理和 CGLIB 动态代理:
1.JDK 动态代理只提供接口代理,不支持类代理,核心 InvocationHandler 接口和 Proxy 类,invoke( )方法,动态地将横切逻辑和业务编织在一起,Proxy 利用 InvocationHandler 动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
2.如果代理类没有实现 InvocationHandler 接口,那么 Spring AOP会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP。CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

3.Spring的生命周期?(高薪常问)

  1. 实例化一个Bean,也就是我们通常说的new;
  2. 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入;
  3. 如果这个Bean实现dao了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID;
  4. 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean);
  5. 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文,该方式同样可以实现步骤4,但比4更好,因为为ApplicationContext是BeanFactory的子接口,有更多的实现方法;
  6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术;
  7. 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法;
  8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postAfterInitialization(Object obj, String s)方法,以上工作完成之后就可以调用这个Bean了,这个Bean是一个single。
  9. 当Bean不在需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法;

    4.Spring支持bean的作用域有几种吗? 每种作用域是什么样的?(必会)

    1.singleton:默认作用域,单例bean,每个容器只有一个bean的实例;
    2.prototype:为每一个bean请求创建一个实例即多例;
    3.request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收;
    4.session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例;
    5.global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这个全局变量需要存储在global-session中。

    5.BeanFactory和ApplicationContext有什么区别(了解)

    BeanFactory:
    Spring最顶层的接口,实现了Spring容器的最基础的一些功能, 调用起来比较麻烦, 一般面向Spring自身使用
    BeanFactory在启动的时候不会去实例化Bean,从容器中拿Bean的时候才会去实例化
    ApplicationContext:
    是BeanFactory的子接口,扩展了其功能, 一般面向程序员身使用
    ApplicationContext在启动的时候就把所有的Bean全部实例化了

    6.Spring框架中都用到了哪些设计模式?(必会)

  1. 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
  2. 单例模式:Bean默认为单例模式
  3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
  4. 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
  5. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现—ApplicationListener

    7.Spring事务的实现方式和实现原理(必会)

    Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的,控制事务的对象由数据库管理转给Spring管理。

spring事务实现主要有两种方法:
1、编程式,beginTransaction()、commit()、rollback()等事务管理相关的方法
2、声明式,利用注解Transactional 或者aop配置

8.你知道的Spring的通知类型有哪些,分别在什么时候执行?(了解)

Spring的通知类型有四种,分别为:
前置通知[]before]:在切点运行之前执行
后置通知[after-returning]:在切点正常结束之后执行
异常通知[after-throwing]:在切点发生异常的时候执行
最终通知[after]:在切点的最终执行
Spring还有一种特殊的通知,叫做环绕通知
环绕通知运行程序员以编码的方式自己定义通知的位置, 用于解决其他通知时序

9.Spring的对象默认是单例的还是多例的? 单例bean存不存在线程安全问题呢?(必会)

1. 在spring中的对象默认是单例的,但是也可以配置为多例。
2. 单例bean对象对应的类存在可变的成员变量并且其中存在改变这个变量的线程时,多线程操作该bean对象时会出现线程安全问题。
原因是:多线程操作如果改变成员变量,其他线程无法访问该bean对象,造成数据混乱。
解决办法:在bean对象中避免定义可变成员变量;
在bean对象中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中。

10.@Resource和@Autowired依赖注入的区别是什么? @Qualifier使用场景是什么?(了解)

@Resource
只能放在属性上,表示先按照属性名匹配IOC容器中对象id给属性注入值若没有成功,会继续根据当前属性的类型匹配IOC容器中同类型对象来注入值
若指定了name属性@Resource(name = “对象id”),则只能按照对象id注入值。
@Autowird
放在属性上:表示先按照类型给属性注入值如果IOC容器中存在多个与属性同类型的对象,则会按照属性名注入值
也可以配合@Qualifier(“IOC容器中对象id”)注解直接按照名称注入值。

11.Spring的常用注解(必会)

  1. @Component(任何层) @Controller @Service @Repository(dao): 用于实例化对象
  2. @Scope : 设置Spring对象的作用域
  3. @PostConstruct @PreDestroy : 用于设置Spring创建对象在对象创建之后和销毁之前要执行的方法
  4. @Value: 简单属性的依赖注入
  5. @Autowired: 对象属性的依赖注入
  6. @Qualifier: 要和@Autowired联合使用,代表在按照类型匹配的基础上,再按照名称匹配。
  7. @Resource 按照属性名称依赖注入
  8. @ComponentScan: 组件扫描
  9. @Bean: 表在方法上,用于将方法的返回值对象放入容器
  10. @PropertySource: 用于引入其它的properties配置文件
  11. @Import: 在一个配置类中导入其它配置类的内容
  12. @Configuration: 被此注解标注的类,会被Spring认为是配置类。Spring在启动的时候会自动扫描并加载所有配置类,然后将配置 类中bean放入容器
  13. @Transactional 此注解可以标在类上,也可以表在方法上,表示当前类中的方法具有事务管理功能。

    12.Spring的事务传播行为(高薪常问)*

    spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
    备注(方便记忆): propagation传播
    require必须的/suppor支持/mandatory 强制托管/requires-new 需要新建/ not -supported不支持/never从不/nested嵌套的
    ① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
    ② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
    ③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
    ④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
    ⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    ⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
    ⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

    13.Spring中的隔离级别 (高薪常问)*

    Spring事务隔离级别比数据库事务隔离级别多一个default
    1) DEFAULT (默认)
    这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
    2) READ_UNCOMMITTED (读未提交)
    这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
    3) READ_COMMITTED (读已提交)
    保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
    4) REPEATABLE_READ (可重复读)
    这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。
    5) SERIALIZABLE(串行化)
    这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。

    14.Spring循环依赖创建代理的时机,以及过程(了解)

    1.最基本的切面是Advisor,一个Aspect切面对应一到多个Advisor;
    2.最基本的Advice是MethodInterceptor,其他Advice最终豆浆适配为MethodInterceptor;
    3.创建代理的方式:
    a.实现了用户自定义接口,采用jdk动态代理;
    b.没有实现用户自定义接口,采用CGLIB代理;
    c.设置了setProxyTargetClass(true),统一采用CGLIB代理。
    4.切面、切点、通知等不会被代理;
    5.AnnotationAwareAspectJAutoProxyCreator调用时机:创建阶段、依赖注入阶段、初始化阶段。

    15.Spring的单例循环依赖是怎么解决的?以及构造方法和多例循环依赖是怎么解决的?*

    单例set方法(包括成员变量)循环依赖,Spring会利用三级缓存解决,无需额外配置:
    a.一级缓存存放成品对象;
    b.二级缓存存放发生了循环依赖时的产品对象(可能是原始Bean,也可能是代理Bean);
    c.三级缓存存放工厂对象,发生循环依赖时,会调用工厂获取产品;
    d.Spring期望在初始化时创建代理,但如果发生了循环依赖,会由工厂提前创建代理,后续初始化时就不必重复创建代理
    e.二级缓存的意义在于,如果提前创建了代理对象,在最后的阶段需要从二级缓存中获取此代理对象,作为最终结果。

构造方法及多例循环依赖解决方法:
@Lazy (推荐)
@Scope(比较low的方式,不推荐)
ObjectFactory & ObjectProvider
Provider

Spring MVC框架

1.谈一下你对SpringMVC框架的理解(了解)

SpringMVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
在我看来,SpringMVC就是将我们原来开发在servlet中的代码拆分了,一部分由SpringMVC完成,一部分由我们自己完成

2.SpringMVC主要组件(必会)

前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
处理器映射器 HandlerMapping:根据请求的URL来查找Handler
处理器适配器 HandlerAdapter:负责执行Handler
处理器 Handler:处理业务逻辑的Java类
视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名将ModelAndView解析成真正的视图(view)
视图View:View是一个接口, 它的实现类支持不同的视图类型,如jsp,freemarker,pdf等等

3.谈一下SpringMVC的执行流程以及各个组件的作用(必会)

image.png
1、用户发送请求给前端控制器
2、前端控制器收到请求后调用处理器映射器
3、处理映射器找到相应的处理器,生成处理器对象及拦截器一并返回前端控制器
4、前端控制器再调用处理适配器
5、处理适配器经过适配调用相应的Controller
6、Controller执行完成以后,把ModelAndView返回给前端控制器
7、前端控制器将ModelAndView传给视图解析器
8、视图解析器将解析的结果返回具体view
9、前端处理器根据view进行视图渲染,并相应给用户

4.说一下SpringMVC支持的转发和重定向的写法(必会)

1)转发:
forward方式:在返回值前面加”forward:”,比如”””forward:user.do?na
.
…………………………………………………………………………………………………………………………………………………………………………me=method4”。

2) 重定向:
redirect方式:在返回值前面加redirect:, 比如”redirect:http://www.baidu.com"。

5.SpringMVC的常用注解(必会)

1.@RequestMapping:用于处理请求 url 映射的注解,可用于类中的所有响应请求的方法都是以该地址作为父路径。
2.@RequestBody:注解实现接收http请求的json数据,将json数据转换为java对象。
3.@ResponseBody:注解实现将conreoller方法返回的java对象转化为json数据响应给客户。
4.@PathVariable :用户从url路径上获取指定参数,标注在参数前 @PathVariabel(“要获取的参数名”)。
5.@RequestParam: 标注在方法参数之前,用于对传入的参数做一些限制,支持三个属性:
- value:默认属性,用于指定前端传入的参数名称
- required:用于指定此参数是否必传
- defaultValue:当参数为非必传参数且前端没有传入参数时,指定一个默认值

6. @ControllerAdvice 标注在一个类上,表示该类是一个全局异常处理的类。
7. @ExceptionHandler(Exception.class) 标注在异常处理类中的方法上,表示该方法可以处理的异常类型。

6.谈一下SpringMVC统一异常处理的思想和实现方式(必会)(此处可结合项目来讲)

使用SpringMVC之后,代码的调用者是SpringMVC框架,也就是说最终的异常会抛到框架中,然后由框架指定异常处理类进行统一处理。
方式一: 创建一个自定义异常处理器(实现HandlerExceptionResolver接口),并实现里面的异常处理方法,然后将这个类交给Spring容器管理。
方式二: 在类上加注解(@ControllerAdvice)表明这是一个全局异常处理类
在方法上加注解(@ExceptionHandler),在ExceptionHandler中有一个value属性,可以指定可以处理的异常类型。

7.在SpringMVC中, 如果想通过转发将数据传递到前台,有几种写法?(必会)

方式一:直接使用request域进行数据的传递
request.setAttirbuate(“name”, value);
方式二:使用Model进行传值,底层会将数据放入request域进行数据的传递
model.addAttribuate(“name”, value);
方式三:使用ModelMap进行传值,底层会将数据放入request域进行数据的传递
modelmap.put(“name”,value);
方式四:借用ModelAndView在其中设置数据和视图
mv.addObject(“name”,value);
mv.setView(“success”);
return mv;

8.在SpringMVC中拦截器的使用步骤是什么样的?(必会)

1 )定义拦截器类:
SpringMVC为我们提供了拦截器规范的接口,创建一个类实现HandlerInterceptor,重写接口中的抽象方法:
preHandle方法:在调用处理器之前调用该方法,如果该方法返回true则请求继续向下进行,否则请求不会继续向下进行,处理器也不会调用
postHandle方法:在调用完处理器后调用该方法
afterCompletion方法:在前端控制器渲染页面完成之后调用此方法
2 )注册拦截器:
在SpringMVC核心配置文件中注册自定义的拦截器






  1. <mvc:interceptors>
  2. <mvc:interceptor>
  3. <mvc:mapping path="拦截路径规则"/>
  4. <mvc:exclude-mapping path="不拦截路径规则"/>
  5. <bean class="自定义拦截器的类全限定名"/>
  6. </mvc:interceptor>
  7. </mvc:interceptors>

9.在SpringMVC中文件上传的使用步骤是什么样的? 前台三要素是什么?(必会)

文件上传步骤:
1.加入文件上传需要的commons-fileupload包
2.配置文件上传解析器,springmvc的配置文件的文件上传解析器的id属性必须为multipartResolver
3.后端对应的接收文件的方法参数类型必须为MultipartFile,参数名称必须与前端的name属性保持一致
文件上传前端三要素:
1.form表单的提交方式必须为post
2.enctype属性需要修改为:multipart/form-data
3.必须有一个type属性为file的input标签,其中需要有一个name属性;如果需要上传多个文件需要添加multiple属性

10.SpringMVC 中如何解决GET|POST请求中文乱码问题?(了解)

1)解决post请求乱码问题:在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;

  1. <filter>
  2. <filter-name>CharacterEncodingFilter</filter-name>
  3. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  4. <init-param>
  5. <param-name>encoding</param-name>
  6. <param-value>utf-8</param-value>
  7. </init-param>
  8. </filter>
  9. <filter-mapping>
  10. <filter-name>CharacterEncodingFilter</filter-name>
  11. <url-pattern>/*</url-pattern>
  12. </filter-mapping>

2)get请求中文参数出现乱码解决方法有两个:
①修改tomcat配置文件添加编码与工程编码一致,如下:
**<ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>** ②另外一种方法对参数进行重新编码:
**String userName= new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")**
ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。

MyBatis框架

1.谈一谈你对Mybatis框架的理解(了解)

MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)在数据库中的记录。

2.在mybatis中,${} 和 #{} 的区别是什么?(必会)

#{}占位符,用于传递参数 ${}用于SQL语句的拼接
#{} 预编译,没有SQL注入问题 ${} 直接拼接,存在SQL注入问题
#{} 不需要关注数据类型 ${} 需要关心,因为不做类型转换
使用#{}可以有效的防止SQL注入,提高系统安全性。

3. MyBatis编程步骤是什么样的?(了解)

1)创建SqlSessionFactory
2) 通过SqlSessionFactory创建SqlSession
3) 通过sqlsession执行数据库操作
4) 调用session.commit()提交事务
5) 调用session.close()关闭会话

4.在mybatis中,ResultType和ResultMap的区别是什么?(必会)

如果数据库结果集中的列名和要封装实体的属性名完全一致的话用 resultType 属性
如果数据库结果集中的列名和要封装实体的属性名有不一致的情况用 resultMap 属性,通过resultMap手动建立对象关系映射,resultMap要配置一下表和类的一一对应关系,所以说就算你的字段名和你的实体类的属性名不一样也没关系,都会给你映射出来

5.在Mybatis中你知道的动态SQL的标签有哪些?作用分别是什么?(必会)

1)if是为了判断传入的值是否符合某种规则,比如是否不为空.
2) where标签可以用来做动态拼接查询条件,当和if标签配合的时候,不用显示的声明类型where 1 = 1这种无用的条件
3) foreach标签可以把传入的集合对象进行遍历,然后把每一项的内容作为参数传到sql语句中.
4) include可以把大量的重复代码整理起来,当使用的时候直接include即可,减少重复代码的编写;
5) 适用于更新中,当匹配某个条件后,才会对该字段进行更新操作

6.谈一下你对mybatis缓存机制的理解?(了解)

Mybatis有两级缓存,一级缓存是SqlSession级别的,默认开启,无法关闭;二级缓存是Mapper级别的,二级缓存默认是没有开启的,但是手动开启
1. 一级缓存:基础PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session flush或close之后,Session中的所有Cache就将清空
2. 二级缓存其存储作用域为Mapper(Namespace),使用二级缓存属性类需要实现Serializable序列化接口
3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了C(增加)/U(更新)/D(删除)操作后,默认该作用域下所有select中的缓存将被clear.
需要在setting全局参数中配置开启二级缓存,如下conf.xml配置:
image.png
当我们的配置文件配置了cacheEnabled=true时,就会开启二级缓存,二级缓存是mapper级别的,如果你配置了二级缓存,那么查询数据的顺序应该为:二级缓存→一级缓存→数据库。