前言
处理做项目的问题,其中不乏奇奇怪怪的问题,其中mybatis plus的问题感觉有点隐蔽,有些是运行时出现,有些是运行到具体的逻辑触发,对于应用的状态监控提出了极大的挑战,应用的状态由健康检查接口提供,或者TCP或者HTTP,那么健康检查要怎么写呢,比如K8S,一般使用HTTP GET方式要定时监控POD状态。
准备
准备SpringBoot与mybatis的demo,这个参考SpringBoot官网即可,数据库使用MySQL docker部署,毕竟docker部署方便:Basic Steps for MySQL Server Deployment with Docker
docker run --name mysql_8.0 -e MYSQL_ROOT_PASSWORD=123456i -d -i -p 3306:3306 container-registry.oracle.com/mysql/community-server:8.0
就可以使用client连接上去,然后建库表和demo数据,需要修改非本地登录mysql的能力
更新root登录限制开放
确认OK
造数据
mybatis plus的Id相同
依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
</dependencies>
dao与配置、main类
@SpringBootApplication
@MapperScan(basePackages = "com.feng.boot.mybatis.demo.dao")
public class MybatisMain {
public static void main(String[] args) {
SpringApplication.run(MybatisMain.class, args);
}
}
@Mapper
public interface DemoDao {
@Select("select * from user")
List<User> selectUser();
@Select("select * from user where name = #{name}")
List<User> selectUser(String name);
}
@RestController
public class DemoController {
@Lazy
@Autowired
private DemoDao demoDao;
@RequestMapping("/user")
public List<User> listUser(String name){
return demoDao.selectUser(name);
}
SpringBoot数据源配置自行处理,启动后日志
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.3.2
2024-01-09 21:27:34.043 ERROR 3340 --- [ main] c.b.m.core.MybatisConfiguration : mapper[com.feng.boot.mybatis.demo.dao.DemoDao.selectUser] is ignored, because it exists, maybe from xml file
但是 启动不会保错,如果是mybatis的starter则启动直接保错
让我们访问http://localhost:8080/user?name=demo
理论上只会有一条结果,然而:
2条,什么原因呢,开启trace日志
为啥不是我方法的执行结果,SQL都不对,这其实是mybatis plus留下的坑
源码分析
启动过程就埋了坑,在
com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder
通过generateResultMapName生成mybatis的Id时,mybatis plus做了定制
private String generateResultMapName(Method method) {
Results results = method.getAnnotation(Results.class);
if (results != null && !results.id().isEmpty()) {
return type.getName() + "." + results.id();
}
StringBuilder suffix = new StringBuilder();
//参数拼接
for (Class<?> c : method.getParameterTypes()) {
suffix.append("-");
suffix.append(c.getSimpleName());
}
//如果没有参数,使用-void
if (suffix.length() < 1) {
suffix.append("-void");
}
//类名+方法名+参数类型拼接
return type.getName() + "." + method.getName() + suffix;
}
所以对于mybatis plus,对于方法重载不存在id冲突的可能性,在
com.baomidou.mybatisplus.core.MybatisConfiguration
所以出现启动时的error日志,然后直接return了,所以下面的第2条是没有载入mappedstatement
所以SQL永远是第1条,无论我们怎么调用
mybatis-starter
原生的mybatis-starter的org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
实际上id也差不多,也是通过-void方式实现无参数id
但是在org.apache.ibatis.builder.MapperBuilderAssistant
会使用不带参数的Id来做校验,在org.apache.ibatis.session.Configuration
而不是像mybatis plus那样直接return了。
解决方法
无非是id冲突,只要安装方法名唯一的情况即可,不使用方法重载
结果OK
mybatis的xml配置错误的情况
如果使用xml配置,那么也会存在出现错误的情况
<?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.feng.boot.mybatis.demo.dao.DemoDao">
<!--<select id="selectUser" parameterType="java.lang.String" resultType="com.feng.boot.mybatis.demo.entity.User">
select * from user where name = #{name}
</select>-->
<select id="selectUser" parameterType="java.lang.String" resultMap="com.feng.boot.mybatis.demo.entity.User">
select * from user where name = #{name}
</select>
<select id="selectUsers" resultType="com.feng.boot.mybatis.demo.entity.User">
select * from user
</select>
</mapper>
笔者故意写错selectUser的xml,但是笔者在调用
@RequestMapping("/users")
public List<User> listUser(){
return demoDao.selectUsers();
}
跟第1个xml毫无关系,但是调用报错
java.lang.IllegalArgumentException: Result Maps collection does not contain value for com.feng.boot.mybatis.demo.entity.User
at org.apache.ibatis.session.Configuration$StrictMap.get(Configuration.java:1031) ~[mybatis-3.5.6.jar:3.5.6]
其实这个都还好,关键是启动毫无错误,然而执行任意一条正确的SQL却报错了,莫非mybatis绝得还可以抢救一下。而且错误信息可以跟正确执行的逻辑毫无相关,如果启动后仅仅看启动日志,就会埋下极大隐患,需要健康检查覆盖mybatis才行。
源码分析
在mybatis的源码中,org.apache.ibatis.session.Configuration
mybatis的设计是,在任意statement执行前,执行未完成态的statement,执行快速失败,既然知道未完成,为什么启动不报错,也不任何日志提示呢
根源来自org.apache.ibatis.builder.xml.XMLMapperBuilder
xml解析失败,并不是报错,也没有日志,直接放在了未完成的statement,这里好歹给个日志啊,估计mybatis是准备支持抢救一下,不对,是支持statement在运行态注入一些数据实现完成态。
总结
mybatis plus这个是直接丢弃相同Id的statement,安装先后顺序,会造成执行过程的误解,不过有日志可以查看,而且mybatis原生的starter是有校验的,直接报错了。mybatis的xml如果写错,那么启动居然不报错,执行任意statement前需要执行未完成的statement,来达到快速失败的情况,这种情况对健康检查提出了新挑战,否则可能出现启动OK,但是mybatis失败的情况。