太坑了,我就用 Lombok + MyBatis 写个查询也能异常?

大家好,我是松柏。时隔多日,我再次被 Lombok 坑了一手,还是跟 MyBatis 联手干的。今天通过我的心路历程来给大家分享下我解决这个 bug 的思路。

情景复现

事情是这样的,那天我正在快乐的增删改查,写下了这样一段代码:

@Resource
BookService bookService;

@Test
void contextLoads() {
    bookService.lambdaQuery()
            .select(Book::getId, Book::getName)
            .list();
}

这能有啥问题?哥们直接执行,然后:

Caused by: java.lang.NumberFormatException: For input string: "eeee"

我:???

看了一眼 Book 实体类:

@TableName(value ="book")
@Builder
@Data
public class Book implements Serializable {
    /**
     * 
     */
    @TableId
    private Long id;

    /**
     * 价格
     */
    private Integer price;

    /**
     * 书名
     */
    private String name;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

没毛病吧?没毛病吗?应该吧,都快给我整不自信了。

难道是玄学?我得重新执行试下,还是报错。

难道是缓存?clean 一下重新执行,还是报错。

好吧,看来是真摊上事了。

解决问题

突然,我看到了实体类上的 @Builder 注解:

让我想起了 @Builder 会生成一个全参构造并且导致无参构造丢失,难道是这个问题?

加个无参构造 @NoArgsConstructor 试试:

好吧,@Builder 需要一个适当的构造器,再加一个全参构造好了:

@TableName(value ="book")
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
    ...
}

执行一下,成功了!!!

这里问题的解决凭的是直觉,不太可取,接下来我们尝试分析下原因。

定位原因

找到异常抛出前的位置,打个断点:

ok,运行,看看方法栈里有没有可疑方法。测试类前面不用管,都没执行到我们的代码,问题肯定不在那,直接往后看。这时候大概看一眼方法名,这里有个 getInt 肯定是不对劲的,因为我这里是 String 类型,所以问题基本就锁定在测试类到 getInt 这个方法之间了:

那我们从 getInt 往前看,看看是哪里的问题让它走到了这个方法。然后就能找到 applyColumnOrderBasedConstructorAutomapping这个方法,因为它里面的内容太显眼了,给我 String 类型的 name 字段匹配成了 Integer 类型,这不搞笑吗:

而且我们看这个方法名:基于列顺序构造器的自动映射(原谅我的蹩脚英语),有点拗口,反正大致意思就是说:诶,我这里是根据构造器的参数顺序进行自动映射。而且前面我们提到,@Builder 会生成一个全参构造,其实目前我们的 Book 类也只有一个构造器:

Book(Long id, Integer price, String name) {
    this.id = id;
    this.price = price;
    this.name = name;
}

还记得我的查询语句吗:

bookService.lambdaQuery()
        .select(Book::getId, Book::getName)
        .list();

我指定了只查 id 和 name 字段,但是使用的是全参构造的参数顺序进行映射,那就把 price 的 Integer 类型映射到 name 上了,这就是这个问题的根源所在。

那为什么我们加了无参构造就好用了呢?再往前看一点,找到createResultObject方法就明白了:

有无参构造的时候,通过工厂直接创建了对象;没有无参构造则会尝试自动映射,但是在我们这种情况下又会映射失败,导致了异常。

最后补一句,如果name字段的值不包含字母 e ,则会抛出另一个异常:

Caused by: com.mysql.cj.exceptions.DataConversionException: Cannot determine value type from string 'aaaa'

原因在这里:

结语

收工,这样就搞明白这个问题的始末了。又转念一想,难道我是第一个发现这个问题的人?搜一下看看:

好吧,白忙活了,早有前人栽树了,寻根问底之前应该先搜一下的,不过自己找问题的过程还是挺有成就感的,哈哈。

本篇就到这里了,有帮助的话点个关注和赞吧,拜拜👋🏻!

<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、付费专栏及课程。

余额充值