SpringBoot+MyBatis報(bào)錯(cuò):Invalid bean definition的原因排查與解決
前言
一段非常常見又讓人頭大的問題:Spring Boot 項(xiàng)目啟動(dòng)時(shí)報(bào) Invalid bean definition with name 'userMapper' ... Invalid value type for attribute 'factoryBeanObjectType': java.lang.String。你已經(jīng)把網(wǎng)上的各種注解檢查過了,最后發(fā)現(xiàn)把 mybatis-spring-boot-starter 升級(jí)到 3.0.3 后問題消失——這是對(duì)的。下面把問題的本質(zhì)、常見根因、排查流程和徹底可運(yùn)行的 Demo(可直接跑)整理成一篇易讀的技術(shù)博客,方便你理解和復(fù)用。
摘要
這個(gè)錯(cuò)誤通常不是因?yàn)閷戝e(cuò) @Mapper 注解或 Mapper 接口方法,而是運(yùn)行時(shí)類路徑上的 MyBatis / MyBatis Spring Boot Starter 與當(dāng)前 Spring Boot 版本不兼容,導(dǎo)致 Spring 在處理 MapperFactoryBean 的 factoryBeanObjectType 屬性時(shí)拿到了錯(cuò)誤的類型(字符串),從而觸發(fā)校驗(yàn)失敗。解決辦法主要是:對(duì)齊依賴版本(升級(jí)/降級(jí) mybatis starter)或移除沖突的舊版本,并用 mvn dependency:tree 檢查沖突。
為什么會(huì)發(fā)生
Spring 在處理 Mapper 接口時(shí)會(huì)為每個(gè)接口注冊(cè)一個(gè) MapperFactoryBean(或者某種 FactoryBean),Spring 會(huì)讀取該 FactoryBean 的元信息(factoryBeanObjectType)來做 bean 定義校驗(yàn)。如果類路徑中某個(gè)庫版本不對(duì),或 MapperFactoryBean 的實(shí)現(xiàn)不同(返回了字符串而非 Class<?>),Spring 會(huì)在啟動(dòng)階段判斷類型不符合而拋出異常。
常見導(dǎo)致這種問題的根源包括:
mybatis-spring-boot-starter的版本與 Spring Boot 版本不匹配(例如 starter 包里對(duì) Spring API 的使用方式與當(dāng)前 Spring 版本期望不一致)。- 項(xiàng)目中同時(shí)存在多個(gè)不同版本的 MyBatis / MyBatis-Spring / mybatis-spring-boot-starter(jar 沖突)。
- 誤把 mapper 的實(shí)現(xiàn)類或注解寫得奇怪,導(dǎo)致生成的 bean 元數(shù)據(jù)異常(不常見,相對(duì)概率?。?。
你遇到的場(chǎng)景:把 mybatis-spring-boot-starter 從 3.0.0 改為 3.0.3(starter 內(nèi)含 MyBatis 3.5.13)后問題解決,說明確實(shí)是版本兼容/bug 問題。
排查步驟
當(dāng)你看到類似錯(cuò)誤時(shí),推薦按下面步驟排查,能快速定位問題:
查看完整異常棧,定位是哪個(gè) Bean(userMapper)和哪個(gè) class file。
執(zhí)行 mvn dependency:tree,查找 mybatis / mybatis-spring / mybatis-spring-boot-starter 是否出現(xiàn)多個(gè)版本(特別是傳遞依賴沖突)。
mvn dependency:tree | grep -i mybatis
檢查 pom.xml 中是否顯式或隱式引入了舊版 mybatis(通過其他 starter/庫引入)。
嘗試把 mybatis starter 升級(jí)到兼容版本(比如 3.0.3),或使用 Spring Boot 推薦的 starter 版本。
清理本地倉庫并重建(有時(shí)候舊 jar 被緩存):
mvn -U clean package mvn dependency:purge-local-repository -DmanualInclude="org.mybatis.spring.boot:mybatis-spring-boot-starter"
如果仍有問題,臨時(shí)把 @Mapper 換成 @MapperScan + @Mapper 或者用 XML 配置試試,以確認(rèn)是自動(dòng)裝配器的問題還是 Mapper 本身。
看是否有重復(fù)掃描:多個(gè) @MapperScan 或者同時(shí)在 spring.factories 中被重復(fù)注冊(cè)也會(huì)出問題。
可運(yùn)行 Demo
基于 Spring Boot + MyBatis 注解方式,使用 H2 內(nèi)存數(shù)據(jù)庫,保證“開箱即跑”
下面是一個(gè)最小可運(yùn)行的示例,確認(rèn)環(huán)境是兼容的:Spring Boot(3.x 系列)+ mybatis-spring-boot-starter:3.0.3 + Java 17。
把下面目錄結(jié)構(gòu)放到一個(gè) Maven 項(xiàng)目里即可運(yùn)行。
demo-mybatis/
├─ pom.xml
├─ src/
├─ main/
├─ java/com/example/demo/
│ ├─ DemoApplication.java
│ ├─ mapper/UserMapper.java
│ └─ model/User.java
└─ resources/
├─ application.properties
└─ schema.sql
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.6</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo-mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- MyBatis Spring Boot Starter (注意版本) -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- Spring Web(方便啟動(dòng)并觀察) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- H2 內(nèi)存數(shù)據(jù)庫,方便 demo -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 支持 JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 測(cè)試 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties(src/main/resources/application.properties)
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MYSQL spring.datasource.username=sa spring.datasource.password= spring.datasource.driver-class-name=org.h2.Driver # MyBatis 配置(若使用 XML mapper,可在這里配置 locations) mybatis.mapper-locations=classpath*:mapper/*.xml mybatis.type-aliases-package=com.example.demo.model
這里我們用注解方式的 Mapper,因此 mapper-locations 可以不用配置,但保留也沒問題。
schema.sql(src/main/resources/schema.sql)
Spring Boot 會(huì)自動(dòng)在運(yùn)行時(shí)執(zhí)行這個(gè) SQL(H2 數(shù)據(jù)庫),創(chuàng)建表。
CREATE TABLE users ( id BIGINT PRIMARY KEY, name VARCHAR(100) );
實(shí)體類User.java(src/main/java/com/example/demo/model/User.java)
package com.example.demo.model;
public class User {
private Long id;
private String name;
public User() {}
public User(Long id, String name) { this.id = id; this.name = name; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "'}";
}
}
Mapper 接口UserMapper.java(src/main/java/com/example/demo/mapper/UserMapper.java)
package com.example.demo.mapper;
import com.example.demo.model.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Insert("INSERT INTO users(id, name) VALUES(#{id}, #{name})")
void insert(User user);
@Select("SELECT id, name FROM users WHERE id = #{id}")
User findById(Long id);
}
注意:使用 @Mapper 注解或在啟動(dòng)類上使用 @MapperScan("...mapper") 都可以。這里用了 @Mapper,簡單明了。
啟動(dòng)類DemoApplication.java(src/main/java/com/example/demo/DemoApplication.java)
package com.example.demo;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
CommandLineRunner runner(UserMapper userMapper) {
return args -> {
System.out.println("Inserting a user...");
userMapper.insert(new User(1L, "Alice"));
User u = userMapper.findById(1L);
System.out.println("Found user: " + u);
};
}
}
運(yùn)行方式
把項(xiàng)目代碼放好,執(zhí)行:
mvn -U clean package mvn spring-boot:run
啟動(dòng)日志中你應(yīng)該看到類似:
Inserting a user...
Found user: User{id=1, name='Alice'}
如果能成功運(yùn)行,說明 mybatis-spring-boot-starter:3.0.3 與 Spring Boot 3.1.6 + Java 17 是兼容的(demo 用 H2 避免外部數(shù)據(jù)庫問題)。
其他可能的坑與補(bǔ)充建議
JAR 沖突:如果你有別的依賴(例如舊版 mybatis-spring、shaded 的 mybatis),請(qǐng)通過 mvn dependency:tree 找出并 exclusion 掉。
Mapper 被重復(fù)掃描:不要同時(shí)用 @Mapper 和 @MapperScan 再加上自動(dòng)裝配的掃描路徑導(dǎo)致重復(fù)注冊(cè)。
Spring 版本與 starter 版本不匹配:使用 Spring Boot 的 dependencyManagement 推薦版本,盡量不要手動(dòng)隨意指定各個(gè)庫版本,除非你很確定兼容性。
IDE 的編譯緩存:遇到奇怪的 Class/Bean 問題,建議 mvn clean、Invalidate Caches & Restart(IDEA),并再次打包運(yùn)行。
查看 Version.class 來源:如果懷疑加載了錯(cuò)誤版本的 MyBatis,可以在啟動(dòng)代碼中打印實(shí)現(xiàn)類的 jar 來源:
System.out.println(org.apache.ibatis.session.SqlSessionFactory.class.getProtectionDomain().getCodeSource().getLocation());
這樣能直觀看到哪個(gè) jar 被加載。
總結(jié)
- 錯(cuò)誤
Invalid value type for attribute 'factoryBeanObjectType': java.lang.String很大概率是依賴版本/兼容性問題導(dǎo)致 Spring 在解析 Mapper FactoryBean 時(shí)讀到不符合預(yù)期的元數(shù)據(jù)格式。 - 最常見、也最有效的解決辦法是:把
mybatis-spring-boot-starter升級(jí)到與 Spring Boot 兼容的版本(比如 3.0.3),或者清理掉沖突的舊版本。 - 通過
mvn dependency:tree、打印 class 來源、清理緩存、對(duì)齊 starter 版本可以快速定位并解決問題。 - 我在文章里給出一個(gè)可運(yùn)行 Demo(Spring Boot + MyBatis 注解 + H2),可以拿來驗(yàn)證你的本地環(huán)境是否配置正確。
到此這篇關(guān)于SpringBoot+MyBatis報(bào)錯(cuò):Invalid bean definition的原因排查與解決的文章就介紹到這了,更多相關(guān)SpringBoot報(bào)錯(cuò)Invalid bean definition內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot3匹配Mybatis3的錯(cuò)誤與解決方案
- 解決springboot3:mybatis-plus依賴錯(cuò)誤:org.springframework.beans.factory.UnsatisfiedDependencyException
- 解決SpringBoot搭建MyBatisPlus中selectList遇到LambdaQueryWrapper報(bào)錯(cuò)問題
- 解決springboot3.2集成mybatis-plus3.5.4.1報(bào)錯(cuò)的問題
- Springboot3整合Mybatis-plus3.5.3報(bào)錯(cuò)問題解決
- springboot+mybatis報(bào)錯(cuò)找不到實(shí)體類的問題
- SpringBoot集成MybatisPlus報(bào)錯(cuò)的解決方案
相關(guān)文章
Java如何使用multipartFile對(duì)象解析Execl
本文介紹了如何使用Spring的MultipartFile類解析Excel文件(.xls和.xlsx),包括文件上傳、數(shù)據(jù)校驗(yàn)、輸入流獲取、文件解析、數(shù)據(jù)保存和異常處理的詳細(xì)步驟2025-02-02
Java實(shí)現(xiàn)添加,讀取和刪除Excel圖片的方法詳解
本文介紹在Java程序中如何添加圖片到excel表格,以及如何讀取、刪除excel表格中已有的圖片。文中的示例代碼講解詳細(xì),感興趣的可以學(xué)習(xí)一下2022-05-05
java實(shí)現(xiàn)切割wav音頻文件的方法詳解【附外部jar包下載】
這篇文章主要介紹了java實(shí)現(xiàn)切割wav音頻文件的方法,結(jié)合實(shí)例形式詳細(xì)分析了java切割wav音頻文件的相關(guān)原理、操作技巧與注意事項(xiàng),并附帶外部jar包供讀者下載,需要的朋友可以參考下2019-05-05
mybatis插入數(shù)據(jù)后如何返回新增數(shù)據(jù)的id值
當(dāng)往mysql數(shù)據(jù)庫插入一條數(shù)據(jù)時(shí),有時(shí)候需要知道剛插入的信息,下面這篇文章主要給大家介紹了關(guān)于mybatis插入數(shù)據(jù)后如何返回新增數(shù)據(jù)id值的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
idea運(yùn)行vue項(xiàng)目設(shè)置自定義瀏覽器方式
這篇文章主要介紹了idea運(yùn)行vue項(xiàng)目設(shè)置自定義瀏覽器方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08

