mysql执行SQL语句能查询出来结果,mybatis查不出来

在自动化测试中,使用MyBatis查询MySQL数据库出现查询结果异常。经过一系列排查,包括验证SQL语句、修改配置文件编码、尝试不同MyBatis版本、检查SQLSession的线程安全性等,最终发现问题是由于静态SQLSession导致的事务问题。解决方案是为每个查询创建新的SQLSession,从而确保事务的正确性,最终解决了问题。

一.问题描述

在做自动化测试断言验证时,更新完用户信息后,拿着这个updateUserCase信息去数据库user表查询,如果返回不为空说明更新成功。
但是测试时发现断言执行失败

10:35:06.645 [main] DEBUG com.course.model.getUpdateUserInfo - ==>  Preparing: select * from user where id=? and username=? and sex=? and age=? 
10:35:06.646 [main] DEBUG com.course.model.getUpdateUserInfo - ==> Parameters: 12(Integer), 小兰1(String), 1(String), 1(String)
10:35:06.647 [main] DEBUG com.course.model.getUpdateUserInfo - <==      Total: 0
user is:null



java.lang.AssertionError: expected object to not be null

	at org.testng.Assert.fail(Assert.java:94)
	at org.testng.Assert.assertNotNull(Assert.java:423)
	at org.testng.Assert.assertNotNull(Assert.java:408)
	at com.course.cases.UpdateUserInfoTest.updateUserInfo(UpdateUserInfoTest.java:46)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:104)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:645)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:851)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1177)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
	at org.testng.TestRunner.privateRun(TestRunner.java:756)
	at org.testng.TestRunner.run(TestRunner.java:610)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:387)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:382)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:340)
	at org.testng.SuiteRunner.run(SuiteRunner.java:289)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1293)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1218)
	at org.testng.TestNG.runSuites(TestNG.java:1133)
	at org.testng.TestNG.run(TestNG.java:1104)
	at com.intellij.rt.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:66)
	at com.intellij.rt.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:110)


二.问题解决

1.拿着这个SQL语句去sqlyog查询发现是能查询出结果的;
在这里插入图片描述

2.尝试在配置文件修改编码为utf-8

 <!-- 2.数据库连接地址 -->
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/java_auto_test?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai"/>
                <!-- 数据库用户... -->

再次运行仍然是失败的;

3.在网上看到

用字段拼接的方式,把#号改成
用#是走了预编译,你传过去的对于数据库来说都是参数值还不是字段,你这个需要拼接sql,所以用$,底层就是jdbc的statement

尝试将下面查询语句对应的#都改成$(会有SQL注入的风险,还是更建议用#)
在这里插入图片描述
再次运行发现还是失败。

4.想到检查pom文件,

<!--        5.mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.4</version>
        </dependency>

  <!--        8.springboot依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>

    </dependencies>

怀疑是不是我的mybatis-spring-boot-starter版本太低了,
尝试修改版本为2.1.0,

再次运行发现还是失败

5.偶然发现第一次运行测试代码断言会执行失败,第二次及以后就会成功了,也是很神奇,但是一直没找到原因

6.修改测试代码,传入userId,不传updateUserCase对象了;
在这里插入图片描述

在这里插入图片描述
再次运行发现断言执行成功了

11:10:40.206 [main] INFO com.course.cases.UpdateUserInfoTest - 请求http:localhost/v1/updateUserInfo的结果是------>1
11:10:40.206 [main] DEBUG com.course.model.getUpdateUserInfo - ==>  Preparing: select * from user where id=? 
11:10:40.206 [main] DEBUG com.course.model.getUpdateUserInfo - ==> Parameters: 12(Integer)
11:10:40.207 [main] DEBUG com.course.model.getUpdateUserInfo - <==      Total: 1
user is:User(id=12, userName=小兰3, password=111, age=13, sex=1, permission=1, isDelete=0)
db is:User(id=12, userName=小兰3, password=111, age=13, sex=1, permission=1, isDelete=0)
11:10:40.207 [main] INFO com.course.cases.UpdateUserInfoTest - ********updateUserInfo api 验证通过*********

但是结果不太对…显示的是修改之前的结果,这也就解释了为什么前面根据修改之后的结果去查询user得到的是空;

7.在服务端增加log发现服务端能查询出正确的更新后的结果


2021-06-11 11:22:55.417 [http-nio-8080-exec-9] INFO  com.course.controller.UserManger - 调用SQL更新用户信息成功
2021-06-11 11:22:55.417 [http-nio-8080-exec-9] INFO  com.course.controller.UserManger - 调用SQL更新的用户是:12
2021-06-11 11:22:55.418 [http-nio-8080-exec-9] INFO  com.course.controller.UserManger - 更新后查到的user是:User(i
d=12, userName=小兰6, password=111, age=16, sex=1, permission=14, isDelete=0)


但是对测试代码debug发现还是旧值
在这里插入图片描述

8.检查代码发现我的sqlSession定义在TestConfig里且为静态的,而且被所有的测试方法公用;
SQLSession本身不是线程安全的,共用一个SQLsession的话,导致了事务问题

每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。

打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。
如下:
    SqlSession session = sqlSessionFactory.openSession();
    try {
          // do work
    } finally {
          session.close();
    }

修改为:在每次执行查询的时候新建一个SQLsession

        User resFromDatabase = MybatisUtils.openSession().selectOne(updateCase.getExpected(),userId);


9.再次运行,测试代码已能获得最新的记录信息
在这里插入图片描述


2021-06-11 11:33:35.345 [http-nio-8080-exec-9] INFO  com.course.controller.UserManger - 调用SQL更新用户信息成功
2021-06-11 11:33:35.345 [http-nio-8080-exec-9] INFO  com.course.controller.UserManger - 调用SQL更新的用户是:12
2021-06-11 11:33:35.346 [http-nio-8080-exec-9] INFO  com.course.controller.UserManger - 更新后查到的user是:User(i
d=12, userName=小兰8, password=111, age=18, sex=1, permission=14, isDelete=0)


10.再把测试代码改回用updateCase查询,更新后的记录;

        User resFromDatabase = MybatisUtils.openSession().selectOne(updateCase.getExpected(),updateCase);


<!--    获取更新/删除后的用户信息-->
    <select id="getUpdateUserInfo" parameterType="com.course.model.UpdateUserInfoCase" resultMap="UserMap">
        select * from user
        <trim prefix="where" prefixOverrides="and">
            <if test="userId!=null and userId!=''">
                and id=#{userId}
            </if>
            <if test="userName!=null and userName!=''">
                and username=#{userName}
            </if>
            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
            <if test="age!=null and age!=''">
                and age=#{age}
            </if>
            <if test="permission!=null and permission!=''">
                and permission=#{permission}
            </if>
            <if test="isDelete!=null and isDelete!=''">
                and is_delete=#{isDelete}
            </if>
        </trim>
    </select>

再次运行测试代码,终于成功了
在这里插入图片描述

### MyBatis Debug 看具体分表 SQL 执行语句日志配置 要在 MyBatis 中调试并看具体的分表 SQL 语句执行情况,可以通过配置日志来实现。以下是详细的说明: #### 配置 Log4j 或 SLF4J 日志框架 MyBatis 支持多种日志框架,其中最常用的是 Log4j 和 SLF4J。通过这些框架可以记录 SQL执行细节。 1. **Log4j 配置** 使用 Log4j 记录 MyBatisSQL 查询日志时,需在 `log4j.properties` 文件中添加如下内容: ```properties log4j.logger.org.mybatis=DEBUG log4j.logger.com.ibatis=DEBUG log4j.logger.java.sql.Connection=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG ``` 上述配置会启用 MyBatis 及其底层 JDBC 连接的日志功能[^1]。 2. **SLF4J 配置** 如果项目使用 SLF4J,则可以在 `application.yml` 或其他配置文件中设置类似的日志级别: ```yaml logging: level: org.mybatis: DEBUG com.ibatis: DEBUG java.sql.Connection: DEBUG java.sql.Statement: DEBUG java.sql.PreparedStatement: DEBUG ``` 此外,还需确保引入了相应的依赖库,例如 `slf4j-api` 和绑定的具体实现(如 `logback-classic`)[^3]。 #### 启用 PageHelper 插件的分页日志 当使用 PageHelper 实现分页查询时,可能会涉及数据库特定的语法转换(如 MySQL 的 `LIMIT` 和 SQL Server 的 `OFFSET FETCH`)。为了更好地理解实际生成的 SQL,可开启 PageHelper 的日志支持。 PageHelper 提供了一个内置方法用于打印最终生成的 SQL 语句。如果需要手动验证分页逻辑,可通过以下方式获取完整的 SQL: ```java String pageSql = PageInterceptor.buildPageSql(page, sql); System.out.println("Generated SQL: " + pageSql); ``` 需要注意的是,同版本的 PageHelper 对于某些数据库方言的支持可能存在差异,因此建议阅对应版本的文档以确认兼容性[^2]。 #### 动态 SQL 解析工具的应用 对于复杂的动态 SQL 场景,推荐借助第三方解析器辅助分析。例如,在 MyBatis 中集成 JSqlParser 工具可以帮助开发者更直观地观察到 SQL 结构的变化过程。下面是一个简单的例子展示如何利用该工具解析 SQL: ```java import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; public class SqlDebugger { public static void main(String[] args) throws Exception { String sql = "SELECT * FROM user WHERE id > ?"; Statement statement = CCJSqlParserUtil.parse(sql); System.out.println(statement.toString()); } } ``` 上述代码片段展示了如何加载外部 SQL 并将其转化为结构化对象以便进一步操作。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值