Mybatis返回数据条数正确但具体数据为null

文章讲述了作者帮助朋友解决前端获取数据列表显示为null的问题,发现原因是Mybatis在封装结果集时,由于SQL查询结果中的字段名tp.province_name被转换成了setProvince_name,导致无法找到正确的setter方法,从而所有数据都为null。
省流:因为select出来的字段没取别名

一个平静的下午,朋友让我帮他看个问题,前端获取数据列表显示查询成功,但是所有数据都是null
错误信息

解决过程:

首先考虑通过整个数据的传输链条,跟着数据传播路径逐步缩小范围

  • 第一步在后端controller打断点,查看前端传入的参数和后端此处的返回是否正常
    此处查看发现前端传的查询参数正常,后端返回结果也为null,因此判定问题出在后端内部
  • 走个过场查一下service层,与controller一样;(我好像没见service单独出过错)因此判断是与数据库交互的部分出现了问题
  • mapper.xml中对应的语句放入MySQL执行,发现没问题,因此问题应该出在Mybatis的部分
  • 仔细考虑Mybatis的运行过程,从命名空间到语句id,再到结果封装与映射
  • 最后在封装返回结果集处发现问题

大火可以看看自己能不能看出来,很简单的问题,但是也值得深入去了解

<?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.ruoyi.test.mapper.TestProvinceMapper">

    <resultMap id="testProvinceResultMap" type="com.ruoyi.test.domain.TestProvince" autoMapping="true" />
    <resultMap id="testProvinceVoResultMap" type="com.ruoyi.test.domain.TestProvinceVo" />

    <select id="list" resultMap="testProvinceVoResultMap">

        select
        tp.province_name,tc.country_name 
        from
        test_province tp
        left join
        test_country tc
        on
        tp.country_id = tc.county_id
        <where>
            <if test="provinceName != null and provinceName != ''">
                and province_name = #{provinceName}
            </if>
        </where>

    </select>

</mapper>

以下为本人手写的Mybatis简单实现对应部分,以此为基础讲一下错误的根源(懒得找Mybatis自己的)

//        6、封装返回结果集
        while(resultSet.next()){
            Object o = resultTypeClass.newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            for(int i=1;i<=metaData.getColumnCount();i++){
                //                字段名字
                String columnName = metaData.getColumnName(i);
                //                字段的值
                Object value = resultSet.getObject(columnName);
                //                使用反射,根据数据库表和实体的对应关系,完成封装
                // 根据列名和结果集对象类型构造PropertyDescriptor 
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                //获取写入属性的方法,此处的getWriteMethod是根据刚刚传入的columnName进行的
                Method writeMethod = propertyDescriptor.getWriteMethod();
                //执行写入方法,向o对象中写入value值
                writeMethod.invoke(o,value);
            }
            objects.add(o);
        }

Mybatis封装结果集时设置属性值的执行流程

首先是会根据传入字段的名字构造一个PropertyDescriptor对象

随后根据getWriteMethod()获取写入对应属性的方法,注意,此处底层是根据刚刚传入的columnName进行获取write方法的,具体代码如下:

//第一步使用的构造方法
public PropertyDescriptor(String propertyName, Class<?> beanClass)
                throws IntrospectionException {
        this(propertyName, beanClass,
                Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),
                Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));//capitalize会转化为大驼峰
                //此处为获取枚举常量值,其实就是'set',然后加上变量名,这也是为什么我们平时一贯要求'set'方法如此命名的原因
    }
//上述构造方法调用的另一个构造方法    
public PropertyDescriptor(String propertyName, Class<?> beanClass,
                String readMethodName, String writeMethodName)
                throws IntrospectionException {
        ·······
        setName(propertyName);
        setClass0(beanClass);

        this.readMethodName = readMethodName;
        ·······
        this.writeMethodName = writeMethodName;
        ·······
    }
//capitalize
public static String capitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
    }

可以看出,传入的 columnName通过内部处理转化为了propertyName,并且经过转化和拼接构造了writeMethodName,也就是会根据传入的 columnName跟set拼接成为为方法名,以同事的错误为例,tp.province_name字段select出来之后列名会是province_name,也就是说最后生成的方法名会是setProvince_name,那么在利用反射寻找方法的时候就会找不到对应的set方法,以至于无法赋值,所以导致最后获取到的元素个数正确但均为null的问题

只能说太久没手写确实很容易犯这种错误,写到这应该算是解释清楚了

但是我又产生新的疑问了,为什么根据错误的方法名去找方法执行,居然不会报错???

等有时间仔细查看一下这中间的执行过程吧

在使用 Spring Boot 和 MyBatis 连接达梦数据库时,如果 `getDatabaseId` 返回 `null`,通常意味着 MyBatis 无法正确识别当前连接的数据库类型。这种情况可能由多种原因引起,以下是常见问题及其解决方案: ### 数据库驱动兼容性 达梦数据库虽然兼容 JDBC 标准,但其驱动实现可能与主流数据库(如 MySQL、Oracle)存在差异。MyBatis 内部通过 `DatabaseMetaData.getDatabaseProductName()` 方法获取数据库厂商名称,并将其作为 `databaseId` 的值。如果达梦数据返回的名称与 MyBatis 预期不一致,可能导致 `getDatabaseId` 为 `null`。 解决办法是手动指定 `databaseId`,而不是依赖自动检测。例如,在配置文件中显式设置: ```xml <configuration> <databaseIdProvider type="DB_VENDOR"> <property name="DM" value="dm"/> </databaseIdProvider> </configuration> ``` 这样可以确保即使自动检测失败,也能使用预定义的 `databaseId` 值 [^1]。 ### 数据库元数据获取失败 MyBatis 在启动时会尝试从数据库连接中获取元数据信息,以确定当前使用的数据库类型。如果由于网络问题、权限限制或驱动问题导致元数据获取失败,则 `getDatabaseId` 可能返回 `null`。 检查数据库连接是否正常,并确保用户具有足够的权限访问系统表和元数据。可以通过简单的 SQL 查询验证连接状态: ```sql SELECT * FROM DUAL; ``` ### 自定义 `DatabaseIdProvider` 如果默认的 `DB_VENDOR` 提供者无法正确识别达梦数据库,可以考虑自定义 `DatabaseIdProvider` 实现类,重写其逻辑以明确返回预期的 `databaseId` 值。 以下是一个简单的自定义 `DatabaseIdProvider` 示例: ```java public class CustomDatabaseIdProvider implements DatabaseIdProvider { @Override public String getDatabaseId(DataSource dataSource) throws SQLException { try (Connection connection = dataSource.getConnection()) { DatabaseMetaData metaData = connection.getMetaData(); String productName = metaData.getDatabaseProductName(); if ("DM".equalsIgnoreCase(productName)) { return "dm"; } return null; } } @Override public void setProperties(Properties properties) { // 可选属性设置 } } ``` 然后在配置文件中注册该提供者: ```xml <bean id="databaseIdProvider" class="com.example.CustomDatabaseIdProvider"/> ``` ### 检查依赖版本 确保使用的 MyBatis 和 Spring Boot 版本支持达梦数据库。某些旧版本的 MyBatis 可能在处理非标准数据库时存在问题。建议升级到最新稳定版,并确认达梦数据库驱动版本与当前环境兼容。 ### 日志调试 启用 MyBatis 的详细日志输出,查看其在初始化过程中如何处理 `databaseId`。这有助于进一步定位问题根源。可以在 `application.properties` 中开启日志: ```properties logging.level.org.apache.ibatis=DEBUG ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值