恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
IDEA Maven项目MyBatis配置避坑指南:从零跑通第一个查询
首页
资讯中心
/
IDEA Maven项目MyBatis配置避坑指南:从零跑通第一个查询
IDEA Maven项目MyBatis配置避坑指南:从零跑通第一个查询
发布时间:2026/10/15 9:40:54
1. 为什么一个Hello World级别的MyBatis配置能卡住这么多人刚接触MyBatis的朋友十个里有八个在配置阶段就卡住了。不是他们笨而是这件事本身就有点反直觉——你明明只是想查一条数据库记录结果要先搞懂XML映射文件、全局配置文件、SqlSessionFactory、Mapper代理、日志实现这一连串概念。更麻烦的是IDEA这个开发环境虽然智能但它的智能有时候反而会帮倒忙比如自动导入错误的包、资源目录不识别XML文件、Maven资源过滤把配置文件吃掉等等。我自己第一次配MyBatis的时候光是为什么我的Mapper XML文件明明放在resources目录下却报BindingException这个问题就折腾了一个下午。后来才明白IDEA的Maven项目默认只把src/main/resources下的文件复制到classpath但如果你把XML放在src/main/java的包路径下就需要在pom.xml里额外配置资源目录。这种坑官方文档不会专门告诉你因为官方默认你用的是标准目录结构。这篇内容就是要把从零开始在一个IDEA Maven项目里跑通MyBatis这件事彻底讲透。我会从依赖选型开始一步步带你走完整个配置流程重点解释每一步为什么这么做、不这么做会出什么问题。适合完全没有MyBatis经验但懂Java基础的朋友也适合之前配过但总是报错、想搞清楚底层逻辑的开发者。读完你至少能获得三样东西一套可复现的标准配置流程、一份常见报错的排查思路、以及几个我踩过的坑的规避方法。2. 依赖引入与项目骨架搭建从pom.xml开始就把路走对2.1 MyBatis核心依赖到底需要哪几个很多人一上来就搜MyBatis依赖然后复制一段包含mybatis、mybatis-spring、spring-context、spring-jdbc的pom配置结果项目跑起来一堆冲突。这里要明确一个原则如果你只是单独使用MyBatis不整合Spring那就只需要两个依赖——MyBatis核心包和数据库驱动包。dependencies !-- MyBatis核心 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.13/version /dependency !-- MySQL驱动注意版本要和你的数据库服务端匹配 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency !-- 日志实现MyBatis默认用log4j或slf4j不加这个看不到SQL -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.9/version /dependency /dependencies为什么强调不整合Spring因为一旦引入mybatis-spring你就需要配置Spring容器、数据源Bean、SqlSessionFactoryBean整个复杂度会上升一个量级。对于初学者来说先用原生MyBatis跑通查询一张表这个最小闭环再去学整合认知负担会小很多。版本选择上有个经验MyBatis 3.5.x是目前最稳定的系列3.5.13修复了之前几个关于延迟加载和缓存的bug。MySQL驱动从8.0开始groupId变成了com.mysql老教程里的mysql:mysql-connector-java已经废弃了这个细节不注意就会导致依赖下载失败。2.2 目录结构怎么摆才不会被IDEA坑标准Maven项目的目录结构是这样的src ├── main │ ├── java │ │ └── com │ │ └── example │ │ ├── mapper │ │ │ └── UserMapper.java │ │ └── pojo │ │ └── User.java │ └── resources │ ├── mybatis-config.xml │ └── mapper │ └── UserMapper.xml关键点在于Mapper接口放在java目录下Mapper XML放在resources目录下但两者的包路径必须完全一致。比如UserMapper.java在com.example.mapper包下那么UserMapper.xml就应该放在resources/com/example/mapper/目录下。这样MyBatis在加载时才能通过接口的全限定名找到对应的XML文件。我见过太多人把XML直接扔在resources根目录然后在mybatis-config.xml里写mapper resourceUserMapper.xml/结果报Invalid bound statement。原因就是MyBatis默认的映射规则要求XML的路径和接口的包路径对应除非你在配置里显式指定了package namecom.example.mapper/这种包扫描方式。提示如果你实在不想在resources下建多层目录可以在pom.xml的build标签里加一段资源配置让Maven把java目录下的XML也复制到classpath。但我不推荐这种做法因为它会让项目结构变得不直观后期维护容易混乱。2.3 IDEA的Maven资源过滤陷阱IDEA在编译Maven项目时默认会对resources目录下的文件进行资源过滤也就是把文件里的${...}占位符替换成pom.xml里定义的属性值。这个机制本来是为了处理配置文件里的动态参数但如果你的MyBatis XML里恰好有${}这种写法比如动态SQL里的${orderBy}就会被Maven误替换导致SQL语句出错。解决办法是在pom.xml里明确排除XML文件的过滤build resources resource directorysrc/main/resources/directory filteringfalse/filtering includes include**/*.xml/include include**/*.properties/include /includes /resource /resources /build这段配置的意思是resources目录下的XML和properties文件不参与过滤原样复制到classpath。加上之后你就不用担心${}被误替换了。这个坑我在一个动态排序功能上踩过当时SQL一直报语法错误排查了半天才发现是Maven把${orderBy}替换成了空字符串。3. 全局配置文件与Mapper XML的编写逻辑3.1 mybatis-config.xml里真正需要配的东西MyBatis的全局配置文件通常叫mybatis-config.xml是整个框架的入口它决定了数据源、事务管理、映射器加载方式、日志实现等核心行为。很多教程会给你一个包含十几个标签的完整配置但实际开发中初期只需要关注四个部分environments、mappers、settings、typeAliases。?xml version1.0 encodingUTF-8? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration settings !-- 开启驼峰命名自动映射数据库的user_name会自动映射到userName -- setting namemapUnderscoreToCamelCase valuetrue/ !-- 打印SQL日志开发阶段必开 -- setting namelogImpl valueSLF4J/ /settings typeAliases !-- 给实体类起别名XML里就不用写全限定名了 -- package namecom.example.pojo/ /typeAliases environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/test_db?useSSLfalseamp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword valueyour_password/ /dataSource /environment /environments mappers !-- 包扫描方式自动加载该包下所有Mapper接口和XML -- package namecom.example.mapper/ /mappers /configuration这里有几个细节值得展开说。mapUnderscoreToCamelCase这个设置几乎是必开的因为数据库字段习惯用下划线命名user_name而Java属性习惯用驼峰userName不开这个设置你就得在SQL里写SELECT user_name AS userName非常繁琐。transactionManager的type用JDBC还是MANAGED如果你不整合Spring用JDBC就行它会让MyBatis自己管理事务的提交和回滚。MANAGED是留给外部容器比如Spring管理事务时用的单独用MyBatis时选它会导致事务不生效。dataSource的type用POOLED还是UNPOOLEDPOOLED表示使用连接池能复用数据库连接性能更好。UNPOOLED每次请求都新建连接只适合学习阶段理解原理。实际开发一律用POOLED。3.2 Mapper XML的namespace和SQL编写规范Mapper XML是MyBatis最核心的部分它把Java方法和SQL语句绑定在一起。一个标准的UserMapper.xml长这样?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.mapper.UserMapper select idselectById resultTypeUser SELECT * FROM user WHERE id #{id} /select insert idinsert parameterTypeUser useGeneratedKeystrue keyPropertyid INSERT INTO user (user_name, age, email) VALUES (#{userName}, #{age}, #{email}) /insert /mappernamespace必须和Mapper接口的全限定名完全一致这是MyBatis建立接口与XML映射关系的唯一依据。id必须和接口里的方法名一致。resultType指定返回类型因为前面配了typeAliases这里直接写User就行不用写com.example.pojo.User。#{}和${}的区别是面试常问但实际开发更容易踩坑的点。#{}是预编译参数会生成?占位符能防止SQL注入。${}是字符串拼接直接替换到SQL里有注入风险。除了动态表名、动态排序字段这种必须拼接的场景一律用#{}。我见过有人在WHERE name ${name}里用${}结果被输入 OR 11直接拖库这种低级错误一定要避免。useGeneratedKeystrue和keyPropertyid是配合使用的作用是插入数据后把数据库生成的自增主键回填到Java对象的id属性上。不加这两个属性插入后对象的id就是null后续如果要用这个id做关联操作就会出问题。3.3 工具类封装SqlSession的正确打开方式原生MyBatis使用SqlSessionFactory和SqlSession来执行SQL但每次手动创建和关闭SqlSession太啰嗦通常我们会封装一个工具类public class MyBatisUtil { private static SqlSessionFactory factory; static { try { InputStream is Resources.getResourceAsStream(mybatis-config.xml); factory new SqlSessionFactoryBuilder().build(is); } catch (IOException e) { throw new RuntimeException(MyBatis初始化失败, e); } } public static SqlSession getSession() { return factory.openSession(true); // true表示自动提交事务 } }openSession(true)的参数很关键。传true表示自动提交每次执行完SQL就自动commit适合简单的增删改查。传false或不传表示手动提交需要自己调session.commit()。很多初学者插入数据后发现数据库里没有记录就是因为忘了提交事务。我建议学习阶段先用自动提交等理解了事务机制再改成手动控制。4. 从零跑通第一个查询完整流程与验证方法4.1 实体类、Mapper接口与测试类的编写假设我们要查询一张user表表结构是id、user_name、age、email四个字段。对应的实体类public class User { private Integer id; private String userName; private Integer age; private String email; // 省略getter/setter和toString }Mapper接口public interface UserMapper { User selectById(Integer id); int insert(User user); }测试类public class MyBatisTest { public static void main(String[] args) { try (SqlSession session MyBatisUtil.getSession()) { UserMapper mapper session.getMapper(UserMapper.class); User user mapper.selectById(1); System.out.println(user); } } }session.getMapper(UserMapper.class)这行代码是MyBatis最精妙的设计之一。它利用JDK动态代理在运行时生成一个UserMapper接口的实现类这个实现类会根据方法名去XML里找对应的SQL语句并执行。你不需要写任何实现类MyBatis帮你做了。4.2 日志配置看不到SQL等于盲人摸象MyBatis默认不打印SQL日志这意味着你执行一个查询如果结果不对你根本不知道它实际发了什么SQL、传了什么参数。所以配置日志是调试阶段最重要的一步。前面在mybatis-config.xml里配了logImpl为SLF4J还需要在resources目录下加一个simplelogger.properties文件org.slf4j.simpleLogger.log.com.example.mapperdebug org.slf4j.simpleLogger.showDateTimetrue org.slf4j.simpleLogger.dateTimeFormatHH:mm:ss第一行是关键它把com.example.mapper包下的日志级别设为debug这样MyBatis就会打印出执行的SQL、参数和返回结果。不加这行你只能看到一堆无关的框架日志。配好之后运行测试类控制台应该输出类似这样的内容14:23:01 [main] DEBUG com.example.mapper.UserMapper.selectById - Preparing: SELECT * FROM user WHERE id ? 14:23:01 [main] DEBUG com.example.mapper.UserMapper.selectById - Parameters: 1(Integer) 14:23:01 [main] DEBUG com.example.mapper.UserMapper.selectById - Total: 1看到这三行说明你的配置完全正确。如果只看到第一行没有后面两行说明数据库连接有问题如果三行都没有说明日志配置没生效。4.3 常见报错的排查链路配置过程中最容易遇到的几个报错我按排查顺序列一下报错信息根本原因解决方式IOException: Could not find resource mybatis-config.xml配置文件不在classpath下确认文件在src/main/resources根目录BindingException: Invalid bound statement (not found)XML路径与接口不匹配检查namespace和XML存放路径SQLException: Access denied for user数据库账号密码错误核对dataSource里的username/passwordNullPointerException在getMapper处SqlSessionFactory初始化失败检查mybatis-config.xml的DTD和标签顺序查询结果字段为null驼峰映射未开启加mapUnderscoreToCamelCase设置排查Invalid bound statement这个错误时有个技巧在报错信息里找到它说的那个statement id然后去target/classes目录下看XML文件是否被正确复制过去了。如果target里没有XML说明Maven没把它当资源文件处理需要检查pom.xml的resources配置。5. 几个让我印象深刻的配置坑与规避经验5.1 URL参数里的时区与SSL问题MySQL 8.0之后JDBC URL如果不加参数经常会报两个错一个是Server returns invalid timezone一个是SSL connection warning。完整的URL应该写成jdbc:mysql://localhost:3306/test_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueserverTimezone必须设置否则MySQL驱动不知道用哪个时区解析时间字段会导致时间差8小时。useSSLfalse是关闭SSL连接本地开发不需要加密。allowPublicKeyRetrievaltrue是允许从服务器获取公钥MySQL 8.0的默认认证插件需要这个参数。注意XML文件里要写成amp;这是XML的转义规则。不转义的话MyBatis解析配置文件时会报EntityRef: expecting ;这种让人摸不着头脑的错误。5.2 IDEA的编译输出路径与热部署IDEA默认的编译输出路径是target/classes但有时候你改了XML文件重新运行却发现还是旧的SQL。这是因为IDEA没有自动重新编译资源文件。解决办法是在IDEA的Settings里找到Build Compiler勾选Build project automatically然后在Registry里开启compiler.automake.allow.when.app.running。这样每次修改文件后IDEA会自动把变更同步到target目录。如果不想改全局设置也可以每次手动执行mvn clean compile强制重新编译。我个人的习惯是改完XML后按一下CtrlF9Build Project比等自动编译更可控。5.3 参数传递的几种方式和适用场景MyBatis传递参数有几种方式用错了就会报Parameter xxx not found单个基本类型参数User selectById(Integer id)XML里用#{id}或#{任意名字}都行MyBatis不检查名字。多个基本类型参数User selectByNameAndAge(String name, Integer age)XML里必须用#{param1}、#{param2}或#{arg0}、#{arg1}或者在接口方法上加Param(name)注解指定名字。对象参数int insert(User user)XML里直接用#{userName}、#{age}MyBatis会自动调用对象的getter方法。Map参数User selectByMap(MapString, Object map)XML里用#{key}取map里的值。最推荐的方式是用Param注解代码可读性最好User selectByNameAndAge(Param(name) String name, Param(age) Integer age);XML里就可以写#{name}和#{age}一目了然。不用注解的话#{param1}这种写法过两个月自己都看不懂。5.4 资源文件加载的三种路径写法在mybatis-config.xml的mappers标签里加载Mapper XML有三种写法!-- 方式一相对路径从classpath根目录开始找 -- mapper resourcecom/example/mapper/UserMapper.xml/ !-- 方式二绝对路径不推荐换台机器就失效 -- mapper urlfile:///D:/project/src/main/resources/UserMapper.xml/ !-- 方式三包扫描自动加载包下所有XML和接口 -- package namecom.example.mapper/方式三最省事但要求XML的目录结构和接口的包结构完全一致。方式一最灵活可以手动指定每个文件。我一般用方式三因为新增Mapper时不用改配置文件。但如果你的XML和接口不在同一个包路径下就只能用方式一逐个指定。6. 配置完成后的验证清单与后续学习方向跑通第一个查询之后建议按这个清单逐项验证确保配置没有隐藏问题插入一条数据检查自增主键是否回填到对象上。查询一条不存在的记录确认返回null而不是抛异常。修改一条记录确认影响行数为1。删除一条记录确认影响行数为1。故意写错一个字段名确认日志里能看到完整的SQL和报错信息。把日志级别调到debug确认每次操作都有SQL输出。这六项都通过说明你的MyBatis配置是健壮的。接下来可以往几个方向深入动态SQLif、choose、foreach标签、关联映射一对一、一对多、缓存机制一级缓存和二级缓存、插件开发分页插件、SQL拦截器。但这些都是建立在今天这套基础配置之上的基础不牢后面越学越乱。我个人在实际操作中的体会是MyBatis的配置问题90%都出在三个地方路径不对、参数名不对、事务没提交。遇到报错先往这三个方向想基本能解决大部分问题。另外养成看日志的习惯MyBatis的debug日志信息量很大SQL、参数、返回行数都有比盲目调试效率高得多。最后再分享一个小技巧如果你不确定某个配置标签的写法可以在IDEA里按住Ctrl点击标签名它会跳转到MyBatis的DTD定义文件里面列出了所有合法标签和属性比搜教程更准确。