关于Spring Boot和MyBatis常见的十道面试题

拦截器和过滤器有什么区别?

拦截器(Interceptor)和过滤器(Filter)都是用于在请求道道目标资源的之前或之后进行处理的组件。主要区别有以下几点:

  1. 依赖对象不同:过滤器是来时Servlet,而拦截器是来自Spring
  2. 底层实现不同:过滤器是基于方法回调实现的。拦截器是基于动态代理(底层是反射)实现的
  3. 触发时机不同:请求执行顺序是:请求进入容器、进入过滤器、进入Servlet、进入拦截器、执行控制器(Controller),所以过滤器和拦截器的执行时机是,过滤器现调用,拦截器在执行
  4. 支持的项目类型不同:过滤器是 Servlet 规范中定义的,所以过滤器要依赖 Servlet 容器,它只能用在 Web 项目中;而拦截器是 Spring 中的一个组件,因此拦截器既可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中;
  5. 使用的场景不同:因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录等业务;而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、字符集编码设置、响应数据压缩等功能。

什么是跨域问题?解决跨域的方案都有哪些?日常工作中会使用哪种解决方案?

跨域问题指的是不同站点之间,使用ajax无法互相调用的问题。跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站盗取数据。但是这个保护机制也带来了新的问题,它的问题是给不同站点之间的正常调用的正常调用,也带来了阻碍,那么如何解决这三种问题呢?

跨域问题的三种情况

在请求时,如果出现以下情况的的任意一种,那么它就是跨域请求:

  1. 协议不同:

    • 例如,从一个使用HTTP协议的网页请求使用HTTPS协议的资源,或者反之
    • 示例:从 http://example.com 发起请求到 https://api.example.com/data
  2. 域名不同:

    • 例如,从一个域名为 example.com 的网页请求另一个域名为 api.example.com 的资源
    • 示例:从 http://www.example.com 请求到 http://api.example.net/data
  3. 端口不同:

    • 例如,从一个使用默认HTTP端口80的网页请求使用非默认HTTP端口的资源,比如8080端口
    • 示例:从 http://www.example.com 请求到 http://www.example.com:8080/data

或者类似以下这几种情况:

 跨域问题演示

接下来,我们使用两个 Spring Boot 项目来演示跨域的问题,其中一个是端口号为 8080 的前端项目,另一个端口号为 9090 的后端接口项目

前端代码 (端口号为8080)

<!-- index.html --><!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Frontend Project</title>
</head>
<body><h1>跨域测试</h1><button id="getDataBtn">发送跨域请求</button><div id="responseData"></div><script>document.getElementById('getDataBtn').addEventListener('click', function() {fetch('http://localhost:9090/api/data').then(response => response.text()).then(data => {document.getElementById('responseData').innerText = data;}).catch(error => {console.error('Error:', error);});});</script>
</body>
</html>

后端代码 (端口号为9090)

后端接口项目首先先在 application.properties 配置文件中,设置项目的端口号为 9090,如下所示:

server.port=9090
// BackendController.javaimport org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class BackendController {@GetMapping("/api/data")public String getData() {return "Hello";}
}

在这个示例中:

  • 前端代码包含一个简单的HTML页面,通过JavaScript发起一个跨域请求到后端项目的 /api/data 接口,并将返回的数据显示在页面上。
  • 后端代码是一个简单的Spring Boot控制器,暴露了一个 /api/data 的GET接口,当收到请求时返回一条简单的消息。

 解决跨域问题

Spring Boot中跨域问题会有很多解决方案,如下:

  1. 使用@CrossOrigin注解实现跨域
  2. 通过配置文件实现跨域
  3. 通过CorsFilter对象实现跨域
  4. 通过Response对象实现跨域
  5. 通过实现ResponseBodyAdvice实现跨域
通过注解实现跨域

使用@CrossOrigin注解可以轻松实现跨域,此注解即可以修饰类,也可以修饰方法。当修饰类时,表示此类中的所有接口都是可以跨域的,实现方式如下:

// BackendController.javaimport org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class BackendController {@CrossOrigin(origins = "http://localhost:8080") // 允许来自端口为8080的源的跨域请求@GetMapping("/api/data")public String getData() {return "Hello";}
}

使用此方式只能实现局部跨域,当一个项目中存在多个类的话,使用此方式就会比较麻烦(需要给所有类上都添加此注解)

通过配置文件实现跨域

接下来我们通过设置配置文件的方式就可以实现全局跨域了,它的实现步骤如下:

  • 创建一个新配置文件;
  • 添加 @Configuration 注解,实现 WebMvcConfigurer 接口;
  • 重写 addCorsMappings 方法,设置允许跨域的代码。

实现代码如下:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration // 一定不要忽略此注解
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // 所有接口.allowCredentials(true) // 是否发送 Cookie.allowedOriginPatterns("*") // 支持域.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) // 支持方法.allowedHeaders("*").exposedHeaders("*");}
}
通过CorsFilter实现跨域

此实现方式和上一种实现方式类似,它也可以实现全局跨域,它的具体实现代码如下:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;@Configuration // 一定不能忽略此注解
public class MyCorsFilter {@Beanpublic CorsFilter corsFilter() {// 1.创建 CORS 配置对象CorsConfiguration config = new CorsConfiguration();// 支持域config.addAllowedOriginPattern("*");// 是否发送 Cookieconfig.setAllowCredentials(true);// 支持请求方式config.addAllowedMethod("*");// 允许的原始请求头部信息config.addAllowedHeader("*");// 暴露的头部信息config.addExposedHeader("*");// 2.添加地址映射UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();corsConfigurationSource.registerCorsConfiguration("/**", config);// 3.返回 CorsFilter 对象return new CorsFilter(corsConfigurationSource);}
}
通过Response实现跨域 

此方式是解决跨域问题最原始的方式,但它可以支持任意的 Spring Boot 版本(早期的 Spring Boot 版本也是支持的)。但此方式也是局部跨域,它应用的范围最小,设置的是方法级别的跨域,它的具体实现代码如下:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;@RestController
public class TestController {@RequestMapping("/test")public HashMap<String, Object> test(HttpServletResponse response) {// 设置跨域response.setHeader("Access-Control-Allow-Origin", "*");return new HashMap<String, Object>() {{put("state", 200);put("data", "success");put("msg", "");}};}
}
 通过ResponseBodyAdive实现跨域

通过重写 ResponseBodyAdvice 接口中的 beforeBodyWrite(返回之前重写)方法,我们可以对所有的接口进行跨域设置,它的具体实现代码如下:

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {/*** 内容是否需要重写(通过此方法可以选择性部分控制器和方法进行重写)* 返回 true 表示重写*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}/*** 方法返回之前调用此方法*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {// 设置跨域response.getHeaders().set("Access-Control-Allow-Origin", "*");return body;}
}

此实现方式也是全局跨域,它对整个项目中的所有接口有效 

原理分析

为什么通过以上方法设置之后,就可以实现不同项目之间的正常交互呢? 这个问题的答案也很简单,我们之前在说跨域时讲到:“跨域问题本质是浏览器的行为,它的初衷是为了保证用户的访问安全,防止恶意网站窃取数据”,那想要解决跨域问题就变得很简单了,只需要告诉浏览器这是一个安全的请求,“我是自己人”就行了,那怎么告诉浏览器这是一个正常的请求呢?

只需要在返回头中设置“Access-Control-Allow-Origin”参数即可解决跨域问题,此参数就是用来表示允许跨域访问的原始域名的,当设置为“*”时,表示允许所有站点跨域访问,如下图所示:

所以以上 5 种解决跨域问题的本质都是给响应头中加了一个 Access-Control-Allow-Origin 的响应头而已

@Transactional底层是如何实现的?

@Transactional时Spring框架中用于声明事务的的注解。它可以应用于方法级别,表示该方法应该在事务的范围内执行。当方法被调用时,Spring会在方法开始时开启一个事务,在方法结束时根据方法执行的结果来决定事务的提交或回滚。例如:

@Transactionalpublic void addUser(User user) {userRepository.save(user);}

 当使用@Transactional注解标注一个方法的时候,Spring Boot在运行时会生成一个代理对象,该代理对象拦截被注解的方法调用,并在方法调用前后进行事务的管理。事务管理包括开始十五、提交事务或回滚事务等操作

导致@Transactional失效的场景有哪些?导致事务失效背后的原因是啥?

导致@Trancsctional失效的常见场景有以下几种:

  1. @Transactional添加在非public方法上
  2. 使用try/catch处理异常
  3. 调用类内部的@Transactional方法
  4. 事务传播机制使用不当
  5. 数据库不支持事务

非public方法

当使用@Transactional修饰非public方法,事务会失效,失效的原因主要是两种:

浅层原因:浅层原因是@Transactional源码限制了必须是public才能执行后续的代理流程,它的源码如下:

protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {  // 不允许非public方法作为必需的方法。  // 如果只允许public方法,并且方法不是public,则设置为null。  if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {  return null;  }  // 这里可以添加更多的逻辑来处理事务属性,例如根据方法名、参数等决定事务的隔离级别、只读性等。  // 下面是一个示例,仅作参考。  // 根据方法的名称或其他属性来决定事务的隔离级别。  Isolation isolationLevel = Isolation.READ_COMMITTED;  if ("someSpecificMethod".equals(method.getName())) {  isolationLevel = Isolation.READ_UNCOMMITTED;  }  // 设置事务的其他属性,如只读、超时等。  TransactionAttribute transactionAttribute = new Propagation(Propagation.REQUIRED)  .and(isolationLevel)  .and(Transactional.timeout(10)) // 设置事务超时时间为10秒  .and(new ReadOnly()) // 设置事务为只读模式  .build();  return transactionAttribute;  
}

深层次原因:深层次原因是Spring Boot的动态代理只能代理公告方法,而不能代理私有方法或受保护方法。这是因为欸Spring的动态代理是基于Java的接口代理机制或者基于CGLib库来实现的,而这两种代理方式都是能代理公共方法:

  • 接口代理:当目标类种实现了接口,Spring使用JDK动态代理来生成代理对象。JDK动态代理是通过生成实现目标类的匿名类,并将方法调用委托给目标类的实例来实现的。由于接口的方法都是公共的,所以JDK动态代理只能代理公共方法
  • CGLib代理:当目标没有实现接口的时候,Spring使用CGLib动态代理来生成代理对象。CGLib动态代理使用过生成目标类的子类,并且将方法调用委托给目标类的实例来实现的。然而,Java中的继承要求要求子类能够继承父类的方法,因此CGLib动态代理也只能代理目标类中的公共方法

使用try/catch处理异常

如果在@Transactional方法内部捕获了所有可能抛出的异常,但是没有将它们重新抛出,那么十五就不会发现异常的存在了,从而就不会回滚事务

从 @Transactiona 实现源码也可以看出,@Transactional 注解只有执行方式捕获到异常了才会回滚,反之则不会,核心源码如下:

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final Invocation invocation) throws Throwable {// 如果事务属性为null,则方法为非事务性方法final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);final PlatformTransactionManager tm = determineTransactionManager(txAttr);final String joinpointIdentification = methodIdentification(method, targetClass);// 前半部分代码补全if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// 使用 getTransaction 和 commit/rollback 方法进行标准事务划分// 自动开启事务TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// 这是环绕通知:调用链中的下一个拦截器。// 正常情况下会调用目标对象的方法// 反射调用业务方法retVal = invocation.proceedWithInvocation();} catch (Throwable ex) {// 目标调用异常// 异常时,在 catch 逻辑中,自动回滚事务completeTransactionAfterThrowing(txInfo, ex);throw ex;} finally {cleanupTransactionInfo(txInfo);// 自动提交事务commitTransactionAfterReturning(txInfo);}return retVal;} else {// 补全部分}
}

调用类内部的@Transactional方法

在Spring中,@Transactional 注解通常与动态代理结合使用来实现事务管理。当你在一个类的方法上标注了 @Transactional 注解时,Spring会为该类创建一个代理对象,并在代理对象上添加事务管理的逻辑。因此,只有通过代理对象调用的方法才能被事务管理器所控制。

如果你在类的内部调用一个带有 @Transactional 注解的方法,而是直接通过 this 对象来调用,那么事务管理器就无法介入,因为调用不经过代理对象。这样的调用会绕过代理,导致事务管理失败。

事务传播机制使用不当

如果事务传播机制设置为 Propagation.NEVER 以非事务方式运行,或者是 Propagation.NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起的传播机制,则当前 @Transactional 修饰的方法也是不会正常执行事务的

数据库不支持事务

Spring Boot/Spring 框架内之所以能使用事务是因为它们连接的数据库支持事务,这是前提条件,所以当数据库层面不支持事务时,那么框架中的代码无论怎么写都不会存在事务的

什么是事务传播机制?它有啥用?

Spring Boot事务传播机制是指,包含多个事务的方法在相互调用的时候,事务是如何在这些方法间传播的

Spring Boot事务传播机制可以使用@Transactional(propagation=Propagation.REQUIREN)来定义,事务传播机制级别重要是包含以下七种:

  1. Propagation.REQUIRED:默认的传播级别,它表示如果当前存在事务,则加入该事务;如果当前不存在事务,则会创建一个新的事务
  2. Propagation.SUPPORTS:如果当前存在事务,则加入事务;如果当前不存在,则以非事务的方式运行
  3. Propagation.MANDATORY:(mondatory:强制的)如果当前存在事务,则加入事务;如果不存在,则抛出异常
  4. Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就i是不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法回信开启自己的事务,且开启的事务相互独立,互不干扰
  5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
  6. Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常
  7. Propagation.NESTED:(nested:内嵌)如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果没有事务,则等价于Propagation.REQUIRED。其中嵌套事务是一个事务内部又包含另一个事务,它被包含的事务称为子事务。当外部事务执行时,它开启一个主事务,如果主事务需要执行多个操作,它可以开启一个或多个子事务。子事务的执行依赖于外部事务的执行状态,如果外部事务失败,所有的子事务都会被回滚,保证数据的一致性和完整性。

SpringBoot中使用了哪些设计模式?

设计模式是编写高质量、可维护和可扩展软件的重要工具。设计模式主要有以下几种:

  1. 工厂模式:Spring通过BeanFactory接口及其实现类如ApplicationContextdeng,为应用程序提供了一个统一的Bean工厂,负责创建和管理各种Bean的对象
  2. 单例模式:Spring会将所有的bean声明为单例。当容器启动的时候,每一个Bean只会被初始化一次,后续对同一个Bean的所有请求都会返回相同大的实例。例如,通过SingletonBeanRegistry接口保证单例Bean在整个应用上下文只存在一个实例
  3. 代理模式:Spring AOP大量使用了代理模式。它利用JDK动态代理或CGLib库生成代理对象,实现代理功能以添加额外的功能
  4. 观察者模式:Spring事件驱动模型以实现了观察者模式。用于在对象之间建立一种一对多的依赖关系。通过 ApplicationEventPublisher 发布和监听事件
  5. 策略模式:Spring框架中的资源加载(Resource)就是一个策略模式的例子。根据不同的资源路径,Spring会选择合适的策略进行资源加载
  6. 装饰器模式:Spring MVC的拦截器Interceptor可以看作一种装饰器模式的应用。根据不同的资源路径,它允许我们呢包装HandlerExecutionChain,在执行处理器方法的前后插入自定义行为
  7. 适配器模式:Spring通过适配器模式这个和不同类型的组件,比如对第三方数据源的连接池(如DBCP、HikariCP等)进行适配,使其能够与Spring容器无缝集成
  8. 模板方法模式:Spring JDBC等模块种,提供了如JdbcTemplate这样的模板类,它封装了通用的数据访问逻辑,而具体的操作由用户提供的SQL来实现

说一下MyBatis执行流程?

MyBatis是一款优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需要关注SQL语句本身,而不需要花费精力去处理加载驱动、创建连接等的过程,MyBatis的执行流程如下:

  1. 加载配置文件:MyBatis的执行流程从加载配置文件开始。通常,MyBatis的配置文件是一个XML文件,其中包含了了数据源配置,SQL映射配置、连接池配置
  2. 构架SqlSessionFactory:在配置文件加载后,MyBatis使用配置信息来构建SqlSessionFactory,这是MyBatis的核心工厂类。SqlSessionFactory是线程安全的,它用于创建SqlSession对象
  3. 创建SqlSession:应用程序通过SqlSessionFactory床渐渐SqlSession对象。SqlSession代表依次数据库会话,它提供了执行SQL操作的方法。通常情况下,每个线程都应该由自己的SqlSession对象
  4. 执行SQL查询:在SqlSession种,开发人员可以执行SQL查询你,这可以通过两种方式实现:
    1. 使用注解加SQL:Mybatis提供了注解加执行SQL的实现方式,MyBatis会为Mapper接口生成实现类的代理对象,实际执行SQL查询
    2. 使用XML映射文件:开发人员可以在XML映射文件中定义SQL查询语句和映射关系。然后,通过SqlSession执行这些SQL擦汗寻,将结果有映射到Java对象上
  5. SQL解析和执行:MyBatis会解析SQL查询,执行查询操作,并获取查询结果
  6. 结果映射:MyBatis使用配置的结果映射规则,将查询结果映射到Java对象上。这包括将数据库列映射到Java对象的属性上,并处理关联关系
  7. 返回结果:查询结果被返回给应用程序,开发热恩于那可以对结果进行进一步处理、展示或之旧话
  8. 关闭SqlSession:完成数据库操作后,关闭SqlSession释放资源

${}#{}有什么区别?什么情况下一定要使用${}

${}和#{}都是MyBatis种用来代替参数的特殊标识,如下:

但是二者的区别二还是很大的,主要区别如下:

  1. 含义不同:${}是直接替换(运行时已经替换成具体的执行SQL了),而#{}是预处理(运行时只设置了占位符"?",之后通过声明器(statement)来替换占位符)
  2. 使用场景不同:普通参数使用#{},如果传递的是SQL命令(如desc还是asc)或者SQL关键字的时候,需要使用${},但是使用之前一定要做好安全验证
  3. 安全性不同:使用${}存在安全性问题,#{}不存在安全性问题

也就是说,为了防止安全问题,所以大部分场景都要使用 #{} 替换参数,但是如果传递的是 SQL 关键字,例如order by xxx asc/desc 时(传递 asc 后 desc),一定要使用 $,因为它需要在执行时就被替换成关键字,而不能使用占位符替代(占位符不用应用于 SQL 关键字,否则会报错)。

在传递 SOL 关键字时,一定要使用 ${},但使用前,一定要进行过滤和安全检査,以防止 SQL 注入。

什么是SQL注入?如何防止SQL注入?

SQL注入就是指应用程序对用户输入的数据的合法性没有判断或过滤不严,攻击者可以在应用程序中事先定义好查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,依次来实现欺骗数据库服务器执行非授权的热议操作,从而进一步得到相应的数据信息


也就是说所谓的 SQL 注入指的是,使用某个特殊的 SQL 语句,利用 SQL 的执行特性,绕过 SQL 的安全检查,查询到本不该查询到的结果

如以下代码:

<select id="doLogin" resultType="com.example.demo.model.User">select *from userinfo where username='${name}' and password='${pwd}
</select>

sql注入以下代码 :"'or 1='1",如下图所示:

从上述结果可以看出,以上程序在应用程序不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的敏感数据

如何防止SQL注入

预编译语句与参数化查询:使用 PreparedStatement 可以有效防止 SQL 注入,因为它允许你先定义 SQL 语句的结构,然后将变量作为参数传入,数据库驱动程序会自动处理这些参数的安全性,确保它们不会干扰 SQL 语句的结构,如下代码所示:

String sql ="SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt= connection.prepareStatement(sql);
pstmt.setString(1,userInputUsername);
pstmt.setstring(2,userInputPassword);
ResultSet rs=pstmt.executeQuery();

输入验证和过滤:对用户输入的信息进行验证和过滤,确保其符合预期的类型和格式 

说一下MyBaits中的二级缓存?

MyBatis二级缓存是用于提高MyBatis查询数据库的性能和减少访问数据库的机制。MyBatis的二级缓存总共由两个缓存机制:

  • 一级缓存:SqlSession级别的,MyBatis自带的缓存功能。并且无法关闭,因此当有两个SqlSession访问相同的SQL时,一级缓存也不会生效,也需要查询两次数据库。在一个service调用两个相同的mapper方法的时候,依然是查询两次,因为他会创建那两个SqlSession进行查询(为了提高查询性能)
  • 二级缓存:Mapper级别的,只要是同一个Mapper,无论是使用多少个SqlSession来操作,数据都是共享的。也就是说,一个sessionFactory下的多个session之间是共享缓存的,它的作用范围更大,生命周期更长,可以减少数据库的查询次数,提高系统性能。但是MyBatis二级缓存时默认关闭的需要手动开启

二级缓存默认是不开启的,手动开启MyBatis步骤如下:

  1. 在mapper.xml中添加<cache/>标签
  2. 在需要缓存的标签上添加usrCache="true"(新版本可以忽略,为了兼顾老版本,保留)

完整示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIc "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.demo.mapper.StudentMapper"><cache/><select id="getStudentCount" resultType="Integer" usecache="true">select count(*)from student</select>
</mapper>

编写单元测试代码:

import org.junit.jupiter.api.Test;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.boot.test.context.SpringBootTest;  @SpringBootTest  
public class StudentMapperTest {  @Autowired  private StudentMapper studentMapper;  @Test  void testGetStudentCount() {  int count = studentMapper.getStudentCount();  System.out.println("查询结果:" + count);  int count2 = studentMapper.getStudentCount();  System.out.println("查询结果2:" + count2);  }  
}

运行结果:

从以上结果可以看出,两次查询虽然使用了不同的 SqlSession,但第二次查询使用了缓存,并未查询数据库 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/655658.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA安装MyBatisX插件

IDEA工具在开发人员中经常使用&#xff0c;从dao层到xml文件对应的查看很费劲&#xff0c;这时候就有相应的插件工具出现了MyBatisX。他的好处如下&#xff1a; mapper and xml can jump back and forth mybatis.xml,mapper.xml prompt mapper and xml support auto prompt lik…

Netty核心——Reactor下篇(十)

任务队列中的Task有3种典型使用场景 用户程序自定义的普通任务 比如有一个非常耗时长的业务 异步执行提交该Channel对应的NioEventLoop的TaskQueue中 用户自定义定时任务 该任务提交到scheduleTaskQueue中 非当前Reactor线程调用Channel的各种方法 例如在推送系统的业务线程…

大数据StarRocks(八):资源隔离实战

前言 自 2.2 版本起&#xff0c;StarRocks 支持资源组管理&#xff0c;集群可以通过设置资源组&#xff08;Resource Group&#xff09;的方式限制查询对资源的消耗&#xff0c;实现多租户之间的资源隔离与合理利用。在 2.3 版本中&#xff0c;StarRocks 支持限制大查询&#…

酒鬼酒2024年展望:稳发展动能,迈入恢复性增长轨道

文 | 琥珀酒研社 作者 | 渡过 最近几个月来&#xff0c;白酒估值回落到近十年来低位&#xff0c;反映出了整个白酒行业的市场低迷和虚弱现状。不管是头部企业五粮液、泸州老窖&#xff0c;还是区域酒企口子窖、金种子酒等&#xff0c;最近都通过“回购”或“增持”&#xff0…

常用直线检测算法

概述 在计算机视觉领域&#xff0c;我们经常需要做一些特殊的任务&#xff0c;而这些任务中经常会用到直线检测算法&#xff0c;比如车道线检测、长度测量等。– 资料 直线检测算法汇总_技术挖掘者的博客-CSDN博客_直线检测算法 直线检测算法博文中缺失的几个源码(Hough_lin…

联想乐商店更新安卓APK错误处理

当你点击“重新提交”&#xff0c;联想开放平台会卡死 其实他们的网页是有BUG的。HTTP GET appDetail请求会有个服务器内部错误 联系了联想客服&#xff0c;他们的绕过去方案是&#xff0c;你要选择“已上架” 然后再更新版本就可以了

【Python机器学习系列】建立XGBoost模型预测心脏疾病(完整实现过程)

一、引言 前文回顾&#xff1a; 一文彻底搞懂机器学习中的归一化与反归一化问题 【Python机器学习系列】一文彻底搞懂机器学习中表格数据的输入形式&#xff08;理论源码&#xff09; 【Python机器学习系列】一文带你了解机器学习中的Pipeline管道机制&#xff08;理论源码…

【Python】03快速上手爬虫案例三:搞定药师帮

文章目录 前言1、破解验证码2、获取数据 前言 提示&#xff1a;通过用户名、密码、搞定验证码&#xff0c;登录进药师帮网站&#xff0c;然后抓取想要的数据。 爬取数据&#xff0c;最终效果图&#xff1a; 1、破解验证码 使用药师帮测试系统&#xff1a;https://dianrc.ysb…

2024 高级前端面试题之 HTML 「精选篇」

该内容主要整理关于 HTML 的相关面试题&#xff0c;其他内容面试题请移步至 「最新最全的前端面试题集锦」 查看。 HTML模块精选篇 1. 如何理解HTML语义化2. H5的新特性有哪些3. 说一下 HTML5 Drag API4. iframe有那些缺点5. 如何实现浏览器内多个标签页之间的通信6. 简述一下s…

Elasticsearch:如何为 Elastic Stack 配置 AI Assistant

了解并安装 Elastic AI Assistant Elastic 推出了 Observability AI Assistant&#xff0c;这是一款利用生成式 AI 来增强你的 Observability 体验的强大工具。 该 AI 助手由 OpenAI 或 Azure OpenAI 服务的连接器提供支持&#xff0c;可带来上下文洞察和聊天功能&#xff0c;…

104.乐理基础-五线谱-中音谱号、次中音谱号

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;103.乐理基础-五线谱-低音谱号-CSDN博客 上一个内容练习答案&#xff1a; 常用的谱号就是下图所示的四个&#xff0c;其中高音谱号与低音谱号已经在上一个内容和上上一个内容中写过了&#xff0c;音乐中百分之九十…

【C++历练之路】探秘C++三大利器之一——多态

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 前言&#x1f354;: 在计算机科学的广袤领域中&#xff0c;C多态性是一门令人着迷的技术艺术&#xff0c;它赋予我们的代码更强大的灵活性和可维护性。想象一下&#xff0c;你正在构建一个程序&#xff0c;需要适应不断…

python3.8 安装缺少ssl、_ctypes模块解决办法

问题 安装pyhton3.8安装默认不依赖ssl 运行Flask项目时报错&#xff1a; Traceback (most recent call last):File "/usr/local/python3/bin/flask", line 8, in <module>sys.exit(main())File "/usr/local/python3/lib/python3.8/site-packages/flask…

电脑文件丢失怎么恢复数据?数据恢复,3个方法!

“我有一份很重要的资料文件保存在电脑上了&#xff0c;但是刚刚发现这些文件莫名其妙丢失了。电脑文件丢失应该怎么恢复数据呀&#xff1f;大家有什么比较好用的方法可以推荐吗&#xff1f;” 在日常生活中&#xff0c;我们经常都需要使用电脑&#xff0c;当然&#xff0c;也会…

虹科干货 | 如何使用nProbe Cento构建100 Gbit NetFlow 传感器

本文是一份全面的指南&#xff0c;解释了如何使用nProbe Cento构建一个高效的100 Gbit NetFlow传感器。旨在帮助大家充分利用NetFlow技术&#xff0c;以监控和分析高速网络流量。 当需要监控分布式网络&#xff0c;了解流经上行链路或关键网段的网络流量时&#xff0c;NetFlow…

Linux系列之查看cpu、内存、磁盘使用情况

查看磁盘空间 df命令用于显示磁盘分区上的可使用的磁盘空间。默认显示单位为KB。可以利用该命令来获取硬盘被占用了多少空间&#xff0c;目前还剩下多少空间等信息。使用df -h命令&#xff0c;加个-h参数是为了显示GB MB KB单位&#xff0c;这样更容易查看 Filesystem …

如何使用手机安装JuiceSSH远程连接本地Linux服务器

⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 文章目录 ⛳️ 推荐1. Linux安装cpolar2. 创建公网SSH连接地址3. JuiceSSH公网远程连接4. 固定连接SSH公网地址5. SSH固定地址…

毕设(一)——概述

文章目录 一、前言二、设计构思2.1 软件构思2.2 硬件构思 三、计划安排四、计划 一、前言 emmmm,我查了查我做的是&#xff0c;井盖侧翻&#xff0c;嗯&#xff0c;外加一些其他传感器&#xff0c;GPS等等吧。用的话&#xff0c;我这边依旧是准备是pico作为主控 二、设计构思…

猫咪不吃东西怎么办?大多猫咪都爱吃的主食冻干分享

猫咪不吃东西怎么办&#xff1f;遇到这类情况需要主人去观察猫咪的情况&#xff0c;如果猫咪除了不吃猫粮还出现了呕吐、腹泻、体温异常等其他情况就要考虑猫咪是不是生病了。如果排除疾病原因&#xff0c;那猫咪不吃东西怎么办呢&#xff1f;可能是猫粮的口感已经让猫咪感到腻…

图形绘制-仪表盘(3)

本章节我们介绍如何绘制仪表盘内的车速、单位&#xff08;Km/h&#xff09;、标题等信息。 效果如下&#xff1a; 关于通过继承重写QWidget的绘制事件paintEvent()&#xff0c;来绘制仪表盘的基础操作&#xff0c;请看 《图形绘制-仪表盘&#xff08;1&#xff09;-CSDN博客》…