MyBatis 查询结果与 MySQL 执行结果居然不一致?

玉官
• 阅读 236
添加微信BGM7756领取文末MySQL相关资料福利!

最近在业务中遇到一个问题,业务是这样的:在插入新用户时需要校验用户的某些信息是否唯一,而在程序中校验结果永远是不唯一的。然后我把 MyBatis 打印的执行 SQL 语句拿了出来在数据库中执行,发现没有数据。

然后我就奇怪了,数据库是同一个啊、SQL 是同一个啊、查询结果都没有变啊,为什么执行的结果在程序里面是 1,而在数据库中是0。

难道是因为 MyBatis 和数据库执行的结果不一样?
MyBatis 查询结果与 MySQL 执行结果居然不一致?
后来我才明白不一致的原因。

我编写了一个与实际业务类似的代码,用来模拟上述的问题。

2. 复现问题

2.1. 表结构

MySQL 数据库中创建了一张用户表,只有4个字段。

CREATE TABLE `user`  (  `user_id` varchar(36) NOT NULL COMMENT '用户主键id',  `user_name` varchar(55) NULL DEFAULT NULL COMMENT '账号',  `password` varchar(55) NULL DEFAULT NULL COMMENT '密码',  `email` varchar(55) NULL DEFAULT NULL COMMENT '邮箱',  PRIMARY KEY (`user_id`) USING BTREE);  //+V:BGM7756,免费领取资料

2.2. 项目依赖

示例项目是一个 SpringBoot 工程,pom 文件中除了 web 一来还有 mysql 的驱动、MyBatis 和 lombok。

  //+V:BGM7756,免费领取资料<dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>        <version>2.2.6.RELEASE</version>    </dependency>    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>5.1.47</version>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>1.18.8</version>    </dependency>    <dependency>        <groupId>org.mybatis.spring.boot</groupId>        <artifactId>mybatis-spring-boot-starter</artifactId>        <version>1.3.0</version>    </dependency></dependencies>  //+V:BGM7756,免费领取资料

2.2. 业务

业务流程是这样:新建一个用户,在新建用户之前首先校验邮箱是否已在数据库中存在,然后执行一些其他的业务,然后执行 insert 方法插入数据库,然后执行一些其他的业务,最后再校验 user_name 是否已存在。

  //+V:BGM7756,免费领取资料@Slf4j@RestControllerpublic class TestController {    @Resource    private UserMapper userMapper;    /**     * springboot Dao层查询结果与数据库实际执行结果不一致?     */    @GetMapping("test")    @Transactional(rollbackFor = RuntimeException.class)    public void transactionalDemo() {        // 要插入的数据        User user = new User();        user.setUserId("userId");        user.setUserName("planeswalker");        user.setPassword("password");        user.setEmail("123@gmail.com");        // 校验邮箱        if (userMapper.countByEmail(user.getEmail())>0) {            throw new RuntimeException("插入失败,user_id 重复");        }        // 执行插入用户操作        userMapper.insert(user);        // 校验 user_name        if (userMapper.countByName(user.getUserName())>0) {            throw new RuntimeException("插入失败,user_name 重复");        }        log.info("do something others...");    }}  //+V:BGM7756,免费领取资料

userMapper 接口类的代码如下:

  //+V:BGM7756,免费领取资料@Repositorypublic interface UserMapper {    /**     * 查询 email 是否重复     * @param email     * @return     */    @Select("select count(*) from user where email=#{email}")    int countByEmail(String email);    /**     * 查询 name 是否重复     * @param userName     * @return     */    @Select("select count(*) from user where user_name=#{userName}")    int countByName(String userName);}  //+V:BGM7756,免费领取资料

我承认这个方法确实可能不是特别好,比如校验重复的方法为什么有两次,比如 user_id 校验重复方法的合理性。但为了与我在项目中遇到的问题做模拟,这是很类似的,在项目中就是在插入后又校验了一次(因为公用的校验方法会查询两张表而其中一张表的数据是在校验之前插入的)。

可能很多同学已经知道这个业务的问题所在了,先不多说,执行就行了。

2.3. 测试

当我在浏览器上访问这个接口 http://127.0.0.1:8080/test 后,控制台输出了如下的内容:

  //+V:BGM7756,免费领取资料2020-05-27 14:07:09.183 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.m.UserMapper.countByEmail    : ==>  Preparing: select count(*) from user where email=? 2020-05-27 14:07:09.208 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.m.UserMapper.countByEmail    : ==> Parameters: 123@gmail.com(String)2020-05-27 14:07:09.218 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.m.UserMapper.countByEmail    : <==      Total: 12020-05-27 14:07:09.233 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.mapper.UserMapper.insert     : ==>  Preparing: INSERT INTO user ( user_id,user_name,password,email ) VALUES( ?,?,?,? ) 2020-05-27 14:07:09.234 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.mapper.UserMapper.insert     : ==> Parameters: userId(String), planeswalker(String), password(String), 123@gmail.com(String)2020-05-27 14:07:09.237 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.mapper.UserMapper.insert     : <==    Updates: 12020-05-27 14:07:09.237 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.m.UserMapper.countByName     : ==>  Preparing: select count(*) from user where user_name=? 2020-05-27 14:07:09.237 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.m.UserMapper.countByName     : ==> Parameters: planeswalker(String)2020-05-27 14:07:09.238 DEBUG 18375 --- [nio-8080-exec-6] c.b.d.m.i.a.m.UserMapper.countByName     : <==      Total: 12020-05-27 14:07:09.250 ERROR 18375 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 插入失败,user_name 重复] with root causejava.lang.RuntimeException: 插入失败,user_name 重复    at com.biosan.databasehandler.TestController.transactionalDemo(TestController.java:43) ~[classes/:na]    at com.biosan.databasehandler.TestController$$FastClassBySpringCGLIB$$dadcb476.invoke(<generated>) ~[classes/:na]    ......  //+V:BGM7756,免费领取资料

在第二个校验方法的时候抛出了错误,说明数据库中存在相同 user_name 的数据,然后我又把 SQL 拿出来单独去数据库中执行,发现没有数据!

不信邪的我又在第二个校验方法上打了断点,当程序执行到此处时,它的执行结果是:
MyBatis 查询结果与 MySQL 执行结果居然不一致?
也就是说确实这时候存在这样的数据!

而此时我又在数据库当中查询,竟然也查不到这条数据!

这就让我开始考虑到,可能不是代码或者框架的原因,而是其他的问题了,比如数据库事务。

2.4. 原因

我们知道在 SpringBoot 的接口上标注了 @Transactional 注解,就相当于开启了一个事务。

MySQL 默认的事务隔离级别是读已提交,即一个事务提交之后,它做的变更才会被其他事务看到。而在同一个事务中,如果先插入后查询,如果查询条件符合,是可以查询到插入的数据的。

当我的程序在执行完 insert 方法后,又去根据 user_name 查询,就可以查询到插入的数据,而此时我直接在数据库中查询该 user_name,相当于又开启了一个事务进行查询,由于读已提交的隔离级别,一个事务提交之后,它做的变更才会被其他事务看到,且业务方法未提交,所以在数据库中查询不到数据。

这也就是我在程序中和数据库中用同样的 SQL 进行查询,但查询结果却不相同的原因。

2.5. 修复

这个问题从业务上来说原本就是不合理的,我在查询重复数据时本就应该排除与将要插入数据相同 id 的数据,即 SQL 应该是:

select count(*) from user where user_name='planeswalker' and user_id!='userId'

篇幅有限,部分资料图片如下,有需要的朋友添加微信BGM7756 获取资料的免费领取方式吧,希望能给您找工作提供很好的帮助!
MyBatis 查询结果与 MySQL 执行结果居然不一致?

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这