相关类

mp相关接口、相关类均已正确配置,单元测试时使用了IService的getById方法

User类

package com.itheima.mp.domain.po;

import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.itheima.mp.enums.UserStatus;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDateTime;

@Data
@TableName(value = "user",autoResultMap = true)
public class User {

    /**
     * 用户id
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    @TableField(value = "username")
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 注册手机号
     */
    private String phone;

    /**
     * 详细信息
     */
    @TableField(value = "info", typeHandler = JacksonTypeHandler.class)
    private UserInfo info;

    /**
     * 使用状态(1正常 2冻结)
     */
    private UserStatus status;

    /**
     * 账户余额
     */

    private Integer balance;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;

    @TableLogic
    private int deleted;


}

测试类

 @Test
    void test(){
        long id = 1L;
        User user = iuserService.getById(id);
        System.out.println(user);
    }

问题概述

今天我在学习mybatis-plus的Json处理器的时候进行一个根据id查询用户简单测试,报出了java.lang.IllegalStateException: Failed to load ApplicationContext问题根源如下

Caused by: java.lang.IllegalStateException: 
Type handler was null on parameter mapping for property 'info'. 
It was either not specified and/or could not be found for the javaType (com.itheima.mp.domain.po.UserInfo) : jdbcType (null) combination. 

说是无法找到typehandler映射给info,但我已经给info正确加上typehandler注解,mp理应会给我自动映射并转换。

解决思路

于是我查看我的xml文件,里面有一些手写sql

<?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.itheima.mp.mapper.UserMapper">

<!--    显示指定typeHandler-->
    <insert id="saveUser" parameterType="com.itheima.mp.domain.po.User">
        INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `balance`)
        VALUES
            (#{id}, #{username}, #{password}, #{phone}, #{info}, #{balance});
    </insert>
    <update id="updateUser" parameterType="com.itheima.mp.domain.po.User">
        UPDATE `user`
        <set>
            <if test="username != null">
                `username`=#{username}
            </if>
            <if test="password != null">
                `password`=#{password}
            </if>
            <if test="phone != null">
                `phone`=#{phone}
            </if>
            <if test="info != null">
                `info`=#{info}
            </if>
            <if test="status != null">
                `status`=#{status}
            </if>
            <if test="balance != null">
                `balance`=#{balance}
            </if>
        </set>
        WHERE `id`=#{id};
    </update>

    <delete id="deleteUser" parameterType="com.itheima.mp.domain.po.User">
        DELETE FROM user WHERE id = #{id}
    </delete>

    <select id="queryUserById" resultType="com.itheima.mp.domain.po.User">
        SELECT *
        FROM user
        WHERE id = #{id}
    </select>

    <select id="queryUserByIds" resultType="com.itheima.mp.domain.po.User">
        SELECT *
        FROM user
        <if test="ids != null">
            WHERE id IN
            <foreach collection="ids" open="(" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </if>
        LIMIT 10
    </select>
    <update id="customUpdate">
        update user set balance = balance-#{amount} ${ew.customSqlSegment}
    </update>
</mapper>

因为我是进行数据库查询的时候报出了错误,所以我以为是我xml文件中的select语句和IService的方法产生了映射冲突,于是我把select语句都注释掉,但报错仍然没有解决。于是我把整个xml文件的sql语句都注释掉了,居然通过了!!

2025-04-15 17:24:44.495  INFO 20704 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2025-04-15 17:24:44.822  INFO 20704 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2025-04-15 17:24:44.830 DEBUG 20704 --- [           main] c.i.mp.mapper.UserMapper.selectById      : ==>  Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time,deleted FROM user WHERE id=? AND deleted=0
2025-04-15 17:24:44.850 DEBUG 20704 --- [           main] c.i.mp.mapper.UserMapper.selectById      : ==> Parameters: 1(Long)
2025-04-15 17:24:44.911 DEBUG 20704 --- [           main] c.i.mp.mapper.UserMapper.selectById      : <==      Total: 1
User(id=1, username=Jack, password=123456, phone=13234123521, info=UserInfo(age=20, intro=佛系青年, gender=male), status=Normal, balance=1300, createTime=2023-05-19T20:50:21, updateTime=2025-04-15T01:44:09, deleted=0)

但是我仍然很疑惑,不知道问题出现在哪,于是我每个语句都注释一遍看究竟是哪个sql产生了影响。

找到原因

<insert id="saveUser" parameterType="com.itheima.mp.domain.po.User">
        INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `balance`)
        VALUES
            (#{id}, #{username}, #{password}, #{phone}, #{info}, #{balance});
    </insert>

当我把这个sql注释掉后报错就没有了,原因如下:

  • ​MyBatis Mapper XML解析机制​
    MyBatis在启动时会解析所有Mapper XML文件中的SQL语句。如果在解析过程中发现某个字段没有正确配置类型处理(TypeHandler),整个Mapper的初始化会失败,导致该Mapper中的所有方法都无法正常使用。
  • ​Mapper初始化失败的影响​​
    由于saveUser语句解析失败,整个UserMapper的初始化过程被中断,导致UserMapper中的所有方法(包括MyBatis-Plus自动生成的selectById方法)无法正确初始化。因此,在调用getById(id)时,虽然该方法本身配置了类型处理器,但由于Mapper初始化失败,类型处理器无法被正确应用,从而抛出类型处理器找不到的异常。

在sql中显式指定typeHandler类型即可解决

  <insert id="saveUser" parameterType="com.itheima.mp.domain.po.User">
        INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `balance`)
        VALUES
            (#{id}, #{username}, #{password}, #{phone}, #{info, typeHandler=com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler}, #{balance});
    </insert>

但我发现我的一条update语句也包含对于info字段的处理,但是没有报错

 <update id="updateUser" parameterType="com.itheima.mp.domain.po.User">
        UPDATE `user`
        <set>
            <if test="username != null">
                `username`=#{username}
            </if>
            <if test="password != null">
                `password`=#{password}
            </if>
            <if test="phone != null">
                `phone`=#{phone}
            </if>
            <if test="info != null">
                `info`=#{info}
            </if>
            <if test="status != null">
                `status`=#{status}
            </if>
            <if test="balance != null">
                `balance`=#{balance}
            </if>
        </set>
        WHERE `id`=#{id};
    </update>

原因如下:

  • 动态SQL的条件过滤​​
    updateUser语句中使用了动态标签<if>,只有当info字段非空时,才会将该字段包含在更新的SQL语句中。​​如果实际业务中未修改info字段(即传入的User对象中info为null),则对应的<if>条件不成立,生成的SQL不会包含info字段​​,因此不会触发类型处理器需求。
  • 更新与插入的机制差异​​
    ​​插入操作​​:必须处理所有非空字段的值,因此saveUser语句中的info字段必然被解析。
    ​​更新操作​​:只处理实际传入的非空字段。如果info未被修改,则不会出现在SQL中,避免了类型处理器的调用。
    运行时确保不报错还是需要手动显式加上typeHandler

总结

机制特性 具体表现
全量校验 任意SQL语句解析失败会导致整个Mapper初始化失败
类型处理器继承限制 手动编写SQL需显式指定类型处理器,无法继承实体类注解配置
失败传播性 单个SQL解析失败会导致同Mapper所有方法不可用

使用json处理器时需要在sql的json字段中显式加typeHandler

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐