spring boot+mybatis查询结果为map的时候将key转换为驼峰形式方法

本文详细解析了在SpringBoot+MyBatis项目中,如何自定义MapWrapper以实现Map集合返回结果的key值从下划线命名转换为驼峰命名,解决了官方map-underscore-to-camel-case配置仅对实体bean生效的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        前言:项目使用springboot+mybatis搭建,在一些联表查询中使用了Map集合来接收结果,不过发现返回的结果中key都是和数据库列名一样:带有下划线很不方便,记得官方文档上有个驼峰转换的配置 map-underscore-to-camel-case , 于是想当然的以为加上就ok了,测试后发现接收为对象实体的时候确实是可以转换为驼峰形式,但是map集合还是不起作用。

        于是想到从源码入手,先理清 map-underscore-to-camel-case是怎么运行的,首先在配置文件中增加如上配置,然后点击进入源码发现mybatis将配置文件中的值读取赋值给自己的属性mapUnderscoreToCamelCase

        然后点击属性mapUnderscoreToCamelCase,查看有哪些地方进行了引用,发现有个DefaultResultSetHandler.java调用MetaObject中findProperty方法使用到了这个属性:

       接着点击 findProperty 方法,发现 ObjectWrapper 是一个接口,这样我们就需要查看是哪个类进行了实现,通过 debug 发现是MapWrapper BeanWrapper 两个类进行了实现,前者没有做任何处理直接返回了name后者判断如果配置为true的话将返回的列名toUppercase后和通过反射获得的对象属性进行比对,如果比对上了就返回属性名(这也就解释了为什么结果为实体bean的时候会进行转换,map没有转换的原因):

BeanWrapper.java
@Override
  public String findProperty(String name, boolean useCamelCaseMapping) {
    return metaClass.findProperty(name, useCamelCaseMapping);
  }

MetaClass.java
public String findProperty(String name, boolean useCamelCaseMapping) {
    if (useCamelCaseMapping) {
      name = name.replace("_", "");
    }
    return findProperty(name);
  }

public String findProperty(String name) {
    StringBuilder prop = buildProperty(name, new StringBuilder());
    return prop.length() > 0 ? prop.toString() : null;
  }
private StringBuilder buildProperty(String name, StringBuilder builder) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      String propertyName = reflector.findPropertyName(prop.getName());
      if (propertyName != null) {
        builder.append(propertyName);
        builder.append(".");
        MetaClass metaProp = metaClassForProperty(propertyName);
        metaProp.buildProperty(prop.getChildren(), builder);
      }
    } else {
      String propertyName = reflector.findPropertyName(name);
      if (propertyName != null) {
        builder.append(propertyName);
      }
    }
    return builder;
  }

Reflector.java
/**
 *在初始化Reflector的时候根据反射机制将指定class的所有属性获取并全部转成大写保存到下面的caseInsensitivePropertyMap集合里,这里进行get操作并返回
 */
public String findPropertyName(String name) {
    return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
  }

        看到这里已经很清楚怎么改了:

        1、我们需要自定义 wrappe r继承 MapWrapper 类并重写它的 findProperty 方法,通过userCamelCaseMapping参数判断是否需要进行驼峰处理。

package com.sailing.yjbj.config.wrapper;

import com.google.common.base.CaseFormat;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.wrapper.MapWrapper;

import java.util.Map;

/**
 * @author baibing
 * @project: iemp-yjbj
 * @package: com.sailing.yjbj.config.wrapper
 * @Description: 自定义wrapper处理spring boot + mybatis返回结果为map时的key值转换为驼峰
 * @date 2018/11/1 11:34
 */
public class CustomWrapper extends MapWrapper {

    public CustomWrapper(MetaObject metaObject, Map<String, Object> map) {
        super(metaObject, map);
    }

    @Override
    public String findProperty(String name, boolean useCamelCaseMapping) {
        if(useCamelCaseMapping){
            return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,name);
        }
        return name;
    }
}

        2、同时也要实现 ObjectWrapperFactory 工厂接口,判断如果所传对象为Map类型的时候,才将自定义的 wrapper 处理器返回并使用。

package com.sailing.yjbj.config.wrapper;

import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.util.Map;

/**
 * @author baibing
 * @project: iemp-yjbj
 * @package: com.sailing.yjbj.config.wrapper
 * @Description: 实现接口 ObjectWrapperFactory,通过包装工厂来创建自定义的wrapper
 * @date 2018/11/1 11:38
 */
public class MapWrapperFactory implements ObjectWrapperFactory{

    @Override
    public boolean hasWrapperFor(Object object) {
        return object != null && object instanceof Map;
    }

    @Override
    public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
        return new CustomWrapper(metaObject,(Map)object);
    }
}

       3、最后需要替换原来默认的实现,mybaits文档上告诉了我们怎么做,返回一个 ConfigurationCustomizer ,通过匿名内部类将自己的实现工厂set进去即可:

package com.sailing.yjbj.config;

import com.sailing.yjbj.config.wrapper.MapWrapperFactory;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author baibing
 * @project: iemp-yjbj
 * @package: com.sailing.yjbj.config
 * @Description: mybatis配置类,将自定义的MapWrapperFactory覆盖默认的ObjectWrapperFactory
 * @date 2018/11/1 11:42
 */
@Configuration
public class MybatisConfig {

    @Bean
    public ConfigurationCustomizer mybatisConfigurationCustomizer(){
        System.out.println("initiazing ConfigurationCustomizer....");
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.setObjectWrapperFactory(new MapWrapperFactory());
            }
        };
    }
}

        4、驼峰转换的时候使用了google的guava库,maven依赖为:

<!-- 引用的 guava库,里面有转换驼峰的-->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>24.1-jre</version>
		</dependency>

        最后:通过测试返回的 Map 的时候成功进行了驼峰处理~~~~

项目下载地址:https://download.youkuaiyun.com/download/white_ice/10778916

因csdn后台会自动修改积分,导致现在积分较高,可以去下面地址免费下载:https://github.com/KingOfMonkey/springboot-mybatis ,喜欢的给个star。

<think>嗯,用户想要用Spring Boot、MySQL和MyBatis做一个学生信息管理系统,并且希望看到关键代码。首先,我得确认用户的需求是什么。学生信息管理系统通常包括对学生信息的增删改查(CRUD)操作,可能还涉及分页、条件查询等功能。用户可能是个刚开始学习Java和Spring Boot的开发者,或者是在校学生,需要完成一个课程项目。他们可能希望了解如何搭建整个项目的基本结构,以及各个层之间的代码如何编写。 首先,我需要考虑项目的结构。标准的Spring Boot项目通常会分为controller、service、mapper(或dao)、entity等包。使用MyBatis的话,可能需要配置mapper接口和对应的XML文件,或者使用注解方式。不过现在更常见的是结合MyBatis-Plus来简化开发,但用户明确提到MyBatis,所以还是按原生的来。 接下来是数据库表的创建。学生表可能需要id、姓名、学号、性别、年龄、班级等字段。需要给出建表语句,并确保字段和Java实体类对应。 然后,实体类(Entity)的编写,使用Lombok简化getter和setter。不过有些项目可能不用Lombok,所以可能需要手动添加,但为了简洁,还是建议使用Lombok,并提醒用户需要添加相关依赖。 接下来是Mapper接口,这里需要使用MyBatis的注解或者XML。如果使用注解,可以直接在接口方法上写SQL,但复杂查询可能还是用XML更方便。用户可能需要知道如何在application.properties中配置数据源和MyBatis的相关设置,比如mapper.xml的位置。 Service层需要注入Mapper,并实现具体的业务逻辑,比如添加学生时的参数校验。Controller层处理HTTP请求,返回JSON数据,同时使用@RestController注解。 此外,分页查询可能需要用到PageHelper插件,但用户的问题中没有明确提到,不过常见功能,所以可以加入作为示例。同时,全局异常处理也是一个好的实践,可以提升系统的健壮性。 用户可能还希望看到如何测试这些接口,比如使用Postman或Swagger,但关键代码部分可能不需要这些,但可以简要提及。 需要确保代码示例的正确性,比如MyBatis的配置是否正确,Mapper的扫描路径是否正确,以及事务管理是否启用。同时,提醒用户添加必要的依赖,比如Spring Boot Starter Web、MyBatis Starter、MySQL Connector、Lombok等。 可能遇到的坑包括:数据库连接配置错误、MyBatis的XML文件位置不正确导致映射失败、字段名与实体类属性名不一致等。需要在代码注释或说明中提示这些点。 最后,总结关键步骤,确保用户能够按照示例搭建起基础框架,并运行起来。可能还需要给出一些扩展建议,比如添加前端页面、权限控制等,但用户当前的问题可能只需要后端的关键代码。</think>以下是一个基于Spring Boot + MySQL + MyBatis的学生信息管理系统关键代码示例(含分层结构和核心实现): --- ### **1. 数据库表结构(SQL)** ```sql CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20) UNIQUE NOT NULL COMMENT '学号', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT DEFAULT 0 COMMENT '性别 0-男 1-女', age INT COMMENT '年龄', class_name VARCHAR(100) COMMENT '班级名称', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); ``` --- ### **2. 实体类(Entity)** ```java @Data // Lombok注解自动生成getter/setter @NoArgsConstructor @AllArgsConstructor public class Student { private Integer id; private String studentId; private String name; private Integer gender; private Integer age; private String className; private Date createTime; } ``` --- ### **3. Mapper接口(DAO层)** ```java @Mapper public interface StudentMapper { // 插入学生(返回自增主键) @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("INSERT INTO student(student_id, name, gender, age, class_name) " + "VALUES(#{studentId}, #{name}, #{gender}, #{age}, #{className})") int insert(Student student); // 分页查询(使用XML映射) List<Student> selectByPage(@Param("name") String name, @Param("className") String className); @Update("UPDATE student SET name=#{name}, gender=#{gender}, age=#{age}, " + "class_name=#{className} WHERE student_id=#{studentId}") int update(Student student); @Delete("DELETE FROM student WHERE id=#{id}") int delete(Integer id); } ``` **对应的XML映射文件(src/main/resources/mapper/StudentMapper.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.example.mapper.StudentMapper"> <select id="selectByPage" resultType="Student"> SELECT * FROM student <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="className != null and className != ''"> AND class_name = #{className} </if> </where> ORDER BY create_time DESC </select> </mapper> ``` --- ### **4. Service层** ```java @Service @RequiredArgsConstructor // Lombok自动注入依赖 public class StudentService { private final StudentMapper studentMapper; public void addStudent(Student student) { if (student.getStudentId() == null) { throw new IllegalArgumentException("学号不能为空"); } studentMapper.insert(student); } public List<Student> getStudents(String name, String className) { return studentMapper.selectByPage(name, className); } // 其他业务方法... } ``` --- ### **5. Controller层** ```java @RestController @RequestMapping("/api/students") @RequiredArgsConstructor public class StudentController { private final StudentService studentService; @PostMapping public ResultVO<String> addStudent(@RequestBody @Valid Student student) { studentService.addStudent(student); return ResultVO.success("添加成功"); } @GetMapping public ResultVO<List<Student>> getStudents( @RequestParam(required = false) String name, @RequestParam(required = false) String className) { return ResultVO.success(studentService.getStudents(name, className)); } // 统一返回格式示例 @Data public static class ResultVO<T> { private int code; private String msg; private T data; public static <T> ResultVO<T> success(T data) { ResultVO<T> result = new ResultVO<>(); result.code = 200; result.msg = "success"; result.data = data; return result; } } } ``` --- ### **6. 配置文件(application.yml)** ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/student_db?useSSL=false&characterEncoding=utf8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml # XML映射文件路径 configuration: map-underscore-to-camel-case: true # 自动驼峰命名转换 ``` --- ### **关键依赖(pom.xml)** ```xml <dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis整合 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> ``` --- ### **扩展建议** 1. **加入分页插件**:使用PageHelper实现物理分页 2. **参数校验**:在实体类字段添加`@NotBlank`等校验注解 3. **全局异常处理**:使用`@ControllerAdvice`统一处理异常 4. **接口文档**:集成Swagger生成API文档 5. **日志记录**:添加Logback/SLF4J日志 启动类添加`@MapperScan("com.example.mapper")`注解扫描Mapper接口,运行后可通过Postman测试接口: - POST http://localhost:8080/api/students - GET http://localhost:8080/api/students?name=张&className=三年二班
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值