MyBatis核心原理与Spring Boot集成实战:从JDBC痛点解析到企业级应用
如果你是一名Java开发者正在为如何高效、优雅地操作数据库而烦恼那么这篇文章就是为你准备的。你可能已经习惯了直接写JDBC代码或者被Hibernate的复杂映射和“黑盒”行为所困扰。今天我们要深入探讨的正是那个在两者之间找到了绝佳平衡点的持久层框架——MyBatis。很多人对MyBatis的第一印象是“半自动ORM”但这四个字背后隐藏的真正价值远不止于此。它解决的是Java开发中一个最核心也最繁琐的痛点如何将面向对象的Java代码与关系型数据库的SQL世界以一种可控、清晰且高效的方式连接起来。它没有试图完全隐藏SQL而是选择拥抱SQL的灵活性同时帮你处理了所有重复、机械的“脏活累活”比如参数设置、结果集映射、连接管理。这篇文章不会只告诉你MyBatis“是什么”我们会深入剖析它“为什么”能成为Java企业级开发的事实标准以及你“如何”才能真正用好它。我们将从最原始的JDBC痛点出发一步步拆解MyBatis的核心设计哲学并通过一个从零开始的Spring Boot集成示例让你亲手体验它的威力。更重要的是我们会探讨那些官方文档不会明说但在实际项目中至关重要的“坑”与最佳实践比如一级缓存引发的数据一致性问题、动态SQL的优雅写法、以及如何防止SQL注入。无论你是正在学习MyBatis的新手还是希望深化理解、排查生产问题的资深开发者这篇文章都将提供清晰的路径和可落地的代码。1. 这篇文章真正要解决的问题在深入技术细节之前我们必须先回答一个根本问题为什么我们需要MyBatis直接使用JDBC不行吗用全自动的Hibernate不是更省事吗核心痛点在于控制力与开发效率的权衡。纯JDBC控制力满分但开发效率极低。你需要手动管理连接、拼装SQL、设置参数、遍历ResultSet、处理异常、关闭资源。大量的样板代码Boilerplate Code不仅枯燥更容易出错尤其是资源泄露问题。全自动ORM如早期Hibernate开发效率高但控制力减弱。框架自动生成SQL对于复杂查询、性能优化或数据库特性利用开发者往往感到束手无策调试生成的SQL也较为困难。MyBatis精准地切入了这个空白地带。它不替你生成SQL你将SQL的掌控权牢牢握在手中可以编写任何复杂、优化的SQL语句。同时它接管了JDBC中所有重复的劳动自动将Java对象POJO属性映射到SQL参数自动将查询结果集映射回Java对象。这种“SQL与代码分离”的设计使得SQL易于维护和优化而Java代码则保持简洁。因此这篇文章要解决的正是如何从一个JDBC的“泥潭”或全ORM的“黑盒”中解脱出来掌握一种既高效又透明的数据库访问方式。它特别适合以下场景的开发者需要执行复杂SQL、存储过程或高度依赖数据库特定功能的项目。团队对SQL性能有极高要求希望直接优化SQL语句。从遗留系统迁移或需要与现有复杂SQL共存的场景。希望持久层代码清晰、易于调试和Code Review的团队。2. 基础概念与核心原理理解MyBatis需要先理清几个核心概念它们构成了MyBatis工作的骨架。2.1 核心组件SqlSessionFactory这是MyBatis的“心脏”。每个基于MyBatis的应用都以一个SqlSessionFactory实例为中心。它负责创建SqlSession对象。通常由全局唯一的一个实例通过读取配置文件如mybatis-config.xml来构建。SqlSession这是你与数据库交互的“会话”。你可以通过它执行映射的SQL语句、提交或回滚事务、获取映射器Mapper。重要SqlSession不是线程安全的因此最佳实践是在每个请求或方法作用域内创建用完后立即关闭。Mapper接口这是一个Java接口其方法对应Mapper XML文件或注解中的SQL语句。MyBatis会使用动态代理技术在运行时为你生成这个接口的实现类。这是MyBatis实现“面向接口编程”的关键。Mapper XML文件这是SQL语句的“家园”。它将SQL语句与Mapper接口的方法进行绑定并定义了详细的参数映射和结果映射规则。SQL与Java代码在此分离。2.2 工作流程简化版应用启动加载mybatis-config.xml全局配置文件创建SqlSessionFactory。当需要执行数据库操作时从SqlSessionFactory中获取一个SqlSession。通过SqlSession获取某个Mapper接口的代理对象如UserMapper userMapper sqlSession.getMapper(UserMapper.class)。调用Mapper接口的方法如userMapper.selectUserById(1)。MyBatis底层根据接口方法名找到对应的Mapper XML中的SQL语句如id为selectUserById的select标签。框架执行SQL将传入的Java参数转换为JDBC参数PreparedStatement的?占位符执行SQL获取ResultSet。核心映射根据XML中定义的resultMap规则将ResultSet中的数据自动填充到返回的Java对象或集合中。返回结果关闭SqlSession。2.3 与JDBC、Hibernate的对比为了更直观我们用下表对比三者的核心差异特性维度原生 JDBCHibernate (全自动ORM)MyBatis (半自动ORM)SQL控制完全手动编写控制力最强。框架自动生成控制力弱可写HQL。手动编写控制力强SQL可见、可优化。学习曲线较低基础API但精通难。较高需掌握Session、缓存、HQL、对象状态等复杂概念。中等核心是SQL和映射配置概念更直接。开发效率极低大量样板代码。高CRUD操作简单。高但需编写SQL和XML。性能调优直接优化SQL但需手动处理连接池等。需理解框架缓存、抓取策略优化有门槛。直接优化SQL性能透明易于定位瓶颈。适用场景极简应用、框架底层、需要绝对控制。业务模型复杂、对象关系复杂、以对象操作为主的项目。SQL复杂、需深度优化、遗留系统集成、强调DBA与开发协作的项目。MyBatis的核心原理可以概括为“用接口声明意图用XML描述实现由框架完成粘合与执行”。3. 环境准备与前置条件接下来我们通过一个最流行的方式——Spring Boot集成来快速搭建一个MyBatis环境。你将看到在现代Spring Boot生态下集成MyBatis变得异常简单。3.1 基础环境要求JDK1.8 或更高版本推荐 JDK 11 或 17。构建工具Maven 3.6 或 Gradle。IDEIntelliJ IDEA、Eclipse 或 VS Code 等。数据库MySQL 5.7 / 8.0本文以MySQL为例MyBatis支持所有主流数据库。3.2 初始化Spring Boot项目你可以通过 Spring Initializr 快速生成项目选择以下依赖Spring Web(可选用于构建Web应用演示)MyBatis FrameworkMySQL Driver生成的pom.xml核心依赖部分如下!-- pom.xml -- dependencies !-- Spring Boot Starter -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis Spring Boot Starter -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version !-- 请使用最新稳定版 -- /dependency !-- MySQL Connector -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 方便测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies3.3 数据库准备在MySQL中创建一个测试数据库和表CREATE DATABASE IF NOT EXISTS mybatis_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE mybatis_demo; CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, name varchar(30) DEFAULT NULL COMMENT 姓名, age int(11) DEFAULT NULL COMMENT 年龄, email varchar(50) DEFAULT NULL COMMENT 邮箱, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; INSERT INTO user (name, age, email) VALUES (张三, 25, zhangsanexample.com), (李四, 30, lisiexample.com);4. 核心配置与项目结构Spring Boot极大简化了配置。我们主要关注application.yml和项目结构。4.1 应用配置文件 (application.yml)# src/main/resources/application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis_demo?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai username: root # 请替换为你的数据库用户名 password: yourpassword # 请替换为你的数据库密码 # MyBatis 配置 mybatis: # 指定Mapper XML文件的位置 mapper-locations: classpath:mapper/*.xml # 指定实体类别名包这样在XML中可以直接写类名而不需要写全限定名 type-aliases-package: com.example.mybatisdemo.entity # 开启驼峰命名自动映射数据库user_name - Java属性userName configuration: map-underscore-to-camel-case: true # 其他可选配置如日志实现 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 在控制台打印执行的SQL便于调试4.2 项目目录结构一个清晰的Maven项目结构如下src/main/java ├── com.example.mybatisdemo │ ├── MybatisDemoApplication.java # Spring Boot 主类 │ ├── entity │ │ └── User.java # 实体类 (POJO) │ ├── mapper │ │ └── UserMapper.java # Mapper 接口 │ └── service │ ├── UserService.java # 业务服务接口 │ └── impl │ └── UserServiceImpl.java # 业务服务实现 src/main/resources ├── application.yml # 配置文件 └── mapper └── UserMapper.xml # Mapper XML 文件5. 从零开始实体、Mapper接口与XML映射让我们一步步创建核心组件。5.1 实体类 (Entity/Pojo)这是与数据库表对应的Java对象。// src/main/java/com/example/mybatisdemo/entity/User.java package com.example.mybatisdemo.entity; import java.time.LocalDateTime; public class User { private Long id; private String name; private Integer age; private String email; private LocalDateTime createTime; // 驼峰命名对应数据库的create_time // 无参构造器、全参构造器、Getter/Setter、toString() 方法 // 这里使用Lombok注解可以极大简化代码但为了清晰我们手动写出 public User() { } public User(Long id, String name, Integer age, String email, LocalDateTime createTime) { this.id id; this.name name; this.age age; this.email email; this.createTime createTime; } // 省略 Getter 和 Setter 方法... // 省略 toString 方法... }5.2 Mapper接口定义我们想要执行的数据库操作。// src/main/java/com/example/mybatisdemo/mapper/UserMapper.java package com.example.mybatisdemo.mapper; import com.example.mybatisdemo.entity.User; import org.apache.ibatis.annotations.*; import java.util.List; Mapper // Spring Boot 需要这个注解来扫描并注册该接口为MyBatis的Mapper public interface UserMapper { /** * 根据ID查询用户 * param id 用户ID * return 用户对象 */ User selectUserById(Long id); /** * 查询所有用户 * return 用户列表 */ ListUser selectAllUsers(); /** * 插入一个新用户 * param user 用户对象 * return 受影响的行数 */ int insertUser(User user); /** * 根据ID更新用户信息 * param user 用户对象包含ID * return 受影响的行数 */ int updateUser(User user); /** * 根据ID删除用户 * param id 用户ID * return 受影响的行数 */ int deleteUserById(Long id); /** * 根据姓名模糊查询 * param name 姓名关键词 * return 用户列表 */ ListUser selectUsersByName(String name); }5.3 Mapper XML文件这是MyBatis的灵魂SQL语句和映射规则在这里定义。?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd !-- namespace 必须对应 Mapper 接口的全限定名 -- mapper namespacecom.example.mybatisdemo.mapper.UserMapper !-- 定义可重用的结果映射。如果属性名和列名能自动映射开启驼峰则可不写。 但复杂映射如关联查询时必须定义。 -- resultMap idUserResultMap typeUser id propertyid columnid/ result propertyname columnname/ result propertyage columnage/ result propertyemail columnemail/ result propertycreateTime columncreate_time/ /resultMap !-- id 对应 Mapper 接口中的方法名 -- select idselectUserById resultMapUserResultMap parameterTypeLong SELECT id, name, age, email, create_time FROM user WHERE id #{id} /select select idselectAllUsers resultMapUserResultMap SELECT id, name, age, email, create_time FROM user /select !-- useGeneratedKeys 和 keyProperty 用于获取自增主键并回填到实体对象中 -- insert idinsertUser parameterTypeUser useGeneratedKeystrue keyPropertyid INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email}) /insert update idupdateUser parameterTypeUser UPDATE user SET name #{name}, age #{age}, email #{email} WHERE id #{id} /update delete iddeleteUserById parameterTypeLong DELETE FROM user WHERE id #{id} /delete !-- 动态SQL示例根据条件查询 -- select idselectUsersByName resultMapUserResultMap parameterTypeString SELECT id, name, age, email, create_time FROM user WHERE 11 !-- test 属性中写的是OGNL表达式判断参数是否不为空且不为空字符串 -- if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if !-- 可以添加更多条件如按年龄过滤 -- !-- if testage ! null AND age #{age} /if -- /select /mapper关键点解析namespace将此XML文件与UserMapper接口绑定。#{id}这是MyBatis的参数占位符等同于JDBC的?。MyBatis会进行预编译有效防止SQL注入。与之相对的是${}它是字符串替换有注入风险慎用。resultMap定义了查询结果列与Java对象属性的映射关系。虽然开启了驼峰映射但显式定义resultMap是更清晰、更可控的做法尤其在列名与属性名差异较大或有关联查询时。useGeneratedKeystrue keyPropertyid这是插入后获取自增主键的经典配置插入成功后传入的user对象的id属性会被自动赋值。6. 编写Service与Controller进行测试为了验证我们的配置我们创建Service层和一个简单的REST接口。6.1 Service接口与实现// src/main/java/com/example/mybatisdemo/service/UserService.java package com.example.mybatisdemo.service; import com.example.mybatisdemo.entity.User; import java.util.List; public interface UserService { User getUserById(Long id); ListUser getAllUsers(); int createUser(User user); int updateUser(User user); int deleteUser(Long id); ListUser searchUsersByName(String name); }// src/main/java/com/example/mybatisdemo/service/impl/UserServiceImpl.java package com.example.mybatisdemo.service.impl; import com.example.mybatisdemo.entity.User; import com.example.mybatisdemo.mapper.UserMapper; import com.example.mybatisdemo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; // MyBatis会自动注入代理实现 Override public User getUserById(Long id) { return userMapper.selectUserById(id); } Override public ListUser getAllUsers() { return userMapper.selectAllUsers(); } Override Transactional // 声明事务确保操作原子性 public int createUser(User user) { return userMapper.insertUser(user); // 插入后user.getId() 已经包含了数据库生成的自增ID } Override Transactional public int updateUser(User user) { return userMapper.updateUser(user); } Override Transactional public int deleteUser(Long id) { return userMapper.deleteUserById(id); } Override public ListUser searchUsersByName(String name) { return userMapper.selectUsersByName(name); } }6.2 简单的REST Controller// src/main/java/com/example/mybatisdemo/controller/UserController.java package com.example.mybatisdemo.controller; import com.example.mybatisdemo.entity.User; import com.example.mybatisdemo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/users) public class UserController { Autowired private UserService userService; GetMapping(/{id}) public User getUser(PathVariable Long id) { return userService.getUserById(id); } GetMapping public ListUser getAllUsers() { return userService.getAllUsers(); } PostMapping public String createUser(RequestBody User user) { int result userService.createUser(user); return result 0 ? 创建成功用户ID: user.getId() : 创建失败; } PutMapping public String updateUser(RequestBody User user) { int result userService.updateUser(user); return result 0 ? 更新成功 : 更新失败用户可能不存在; } DeleteMapping(/{id}) public String deleteUser(PathVariable Long id) { int result userService.deleteUser(id); return result 0 ? 删除成功 : 删除失败用户可能不存在; } GetMapping(/search) public ListUser searchUsers(RequestParam String name) { return userService.searchUsersByName(name); } }7. 运行结果与效果验证7.1 启动应用运行Spring Boot主类MybatisDemoApplication。观察控制台如果没有报错且看到Tomcat启动端口说明MyBatis与Spring Boot集成成功。7.2 测试接口使用Postman、curl或浏览器进行测试查询所有用户GET http://localhost:8080/api/users根据ID查询GET http://localhost:8080/api/users/1创建用户POST http://localhost:8080/api/usersBody (JSON):{ name: 王五, age: 28, email: wangwuexample.com }响应应包含新生成的ID。模糊搜索GET http://localhost:8080/api/users/search?name张更新用户PUT http://localhost:8080/api/usersBody (JSON):{ id: 1, name: 张三丰, age: 100, email: zhangsanfengexample.com }删除用户DELETE http://localhost:8080/api/users/37.3 查看执行的SQL如果你在配置中开启了log-impl: org.apache.ibatis.logging.stdout.StdOutImpl在控制台可以看到MyBatis执行的每一条SQL语句及其参数这对于调试和性能分析至关重要。8. 深入核心特性与高级用法掌握了基础CRUD后我们来看看MyBatis那些让开发事半功倍的核心特性。8.1 动态SQL这是MyBatis的王牌功能之一用于构建灵活的SQL语句。上面的if标签只是开始。!-- 一个更复杂的动态查询示例 -- select idselectUsersByCondition resultMapUserResultMap parameterTypemap SELECT * FROM user where !-- 避免 WHERE 关键字后直接跟 AND/OR 的语法错误 -- if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testminAge ! null AND age #{minAge} /if if testmaxAge ! null AND age lt; #{maxAge} !-- XML中需转义 -- /if if testemail ! null AND email #{email} /if /where ORDER BY create_time DESC /select对应的Mapper接口方法ListUser selectUsersByCondition(MapString, Object params);where标签会智能处理如果其内部有内容则插入WHERE关键字并且会去除内容开头多余的AND或OR。其他常用动态SQL标签choose/when/otherwise类似Java的switch-case。foreach用于IN查询或批量操作。select idselectUsersByIdList resultMapUserResultMap SELECT * FROM user WHERE id IN foreach itemid collectionlist open( separator, close) #{id} /foreach /selectset用于动态更新类似where会去除末尾的逗号。update idupdateUserSelective parameterTypeUser UPDATE user set if testname ! nullname #{name},/if if testage ! nullage #{age},/if if testemail ! nullemail #{email},/if /set WHERE id #{id} /update8.2 结果映射ResultMap高级用法处理“一对多”、“多对一”关联查询是ORM的难点。MyBatis通过association和collection优雅解决。假设我们新增一个Order订单表一个用户有多个订单。CREATE TABLE order ( id bigint PRIMARY KEY AUTO_INCREMENT, order_no varchar(64) NOT NULL, user_id bigint NOT NULL, amount decimal(10,2) NOT NULL );一对多查询查询用户及其所有订单!-- 在UserMapper.xml中 -- resultMap idUserWithOrdersResultMap typeUser extendsUserResultMap !-- 继承基础的UserResultMap -- !-- collection 标签用于映射一对多关系 -- collection propertyorderList ofTypeOrder columnPrefixorder_ id propertyid columnid/ result propertyorderNo columnorder_no/ result propertyamount columnamount/ !-- user_id 通常不需要再映射回Java对象 -- /collection /resultMap select idselectUserWithOrders resultMapUserWithOrdersResultMap SELECT u.id, u.name, u.age, u.email, u.create_time, o.id as order_id, o.order_no as order_order_no, o.amount as order_amount FROM user u LEFT JOIN order o ON u.id o.user_id WHERE u.id #{userId} /select在User实体类中需要增加ListOrder orderList属性。8.3 注解开发对于简单的SQL可以使用注解避免XML文件。但复杂SQL和动态SQL仍推荐XML。Mapper public interface UserMapper { Select(SELECT * FROM user WHERE id #{id}) Results(id userMap, value { Result(property id, column id, id true), Result(property createTime, column create_time) }) User selectUserByIdAnnotation(Long id); Insert(INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})) Options(useGeneratedKeys true, keyProperty id) int insertUserAnnotation(User user); // 动态SQL用注解比较麻烦可以使用SelectProvider等但可读性不如XML }注解 vs XML 选择简单、固定的SQL用注解更简洁复杂、动态的SQL用XML更清晰、更强大。很多项目采用混合模式。9. 必须警惕的“坑”与最佳实践MyBatis很强大但使用不当也会带来问题。以下是实际项目中高频出现的注意事项。9.1 一级缓存导致的“脏读”MyBatis的一级缓存Local Cache默认开启其生命周期与SqlSession相同。在同一个SqlSession内执行两次相同的查询第二次会直接返回缓存结果不会访问数据库。坑点在开启事务例如使用Transactional的场景下Spring会保证整个方法使用同一个SqlSession。如果你在方法内先更新了某条数据紧接着又查询同一条数据你查到的可能还是缓存中的旧数据解决方案在查询语句上设置flushCachetrue强制清空缓存并查询数据库。select idselectUserById resultMapUserResultMap flushCachetrue SELECT * FROM user WHERE id #{id} /select在需要最新数据的方法中手动控制SqlSession或避免在同一个事务方法内进行“写后读”操作。理解并合理使用缓存对于实时性要求高的数据可以考虑关闭一级缓存localCacheScopeSTATEMENT或使用二级缓存。9.2#{}与${}的正确使用#{}是预编译处理MyBatis会将其替换为?然后使用PreparedStatement的set方法安全赋值。能有效防止SQL注入绝大多数情况都应使用它。${}是字符串替换MyBatis会直接将参数值替换到SQL语句中。存在SQL注入风险。仅用于动态指定表名、列名等非值参数。!-- 错误示例存在注入风险 -- select idselectByOrder resultMapUserResultMap SELECT * FROM user ORDER BY ${orderByColumn} /select !-- 如果orderByColumn传入 id; DROP TABLE user--后果严重 -- !-- 相对安全的用法动态表名但仍需在业务层严格校验参数 -- select idselectFromTable resultTypemap SELECT * FROM ${tableName} WHERE id #{id} !-- #{id} 仍然是安全的 -- /select最佳实践永远将用户输入或外部参数用#{}。使用${}时必须确保参数值在代码中硬编码或经过严格的白名单校验。9.3 参数传递与Param注解当Mapper接口方法有多个参数时MyBatis默认无法识别它们的名字。// 错误XML中无法识别 #{name} 和 #{age} 对应哪个参数 ListUser selectUsers(String name, Integer age);解决方案使用Param注解ListUser selectUsers(Param(name) String name, Param(age) Integer age);XML中即可使用#{name}和#{age}。将参数封装为Map或一个POJO对象传递。9.4 分页查询MyBatis本身不提供物理分页但可以通过LIMIT语句实现。更推荐使用成熟的分页插件如PageHelper它能无缝集成极大简化分页逻辑。!-- 使用PageHelper后Mapper接口方法无需任何改变 -- select idselectAllUsers resultMapUserResultMap SELECT * FROM user /select在Service层// 关键代码 PageHelper.startPage(pageNum, pageSize); // 紧跟在查询方法前 ListUser userList userMapper.selectAllUsers(); PageInfoUser pageInfo new PageInfo(userList); // pageInfo包含了分页的所有信息总记录数、总页数、当前页数据等9.5 日志与调试开启SQL日志如配置StdOutImpl是调试的利器。可以使用MyBatis Log Plugin等IDE插件将MyBatis执行的SQL还原成完整的、带参数的语句直接复制到数据库客户端执行非常方便。9.6 生产环境最佳实践连接池务必使用生产级连接池如HikariCPSpring Boot默认集成。XML管理将SQL集中放在XML中便于DBA评审和优化。代码生成器对于简单的单表CRUD可以使用MyBatis Generator或MyBatis-Plus的代码生成器避免手动编写重复代码。MyBatis-Plus如果你是MyBatis的重度用户强烈建议了解MyBatis-Plus。它在MyBatis基础上做了大量增强提供了通用的Mapper、Service封装、强大的条件构造器、分页插件等能进一步提升开发效率同时保持对原生MyBatis的兼容。监控集成Micrometer等监控组件观察数据库连接池状态、慢SQL等。MyBatis的成功在于它做出了一个明智的取舍将SQL的灵活性和控制权交还给开发者同时通过自动映射和会话管理消除了JDBC的样板代码。它不是一个试图解决所有问题的“魔法”框架而是一个强大且透明的“助手”。理解它的核心原理——接口绑定、SQL映射、动态SQL和缓存机制——是高效使用它的关键。从今天搭建的这个Spring Boot项目开始尝试在你的下一个需要复杂SQL或高性能数据访问的项目中应用它亲自体会那种“SQL尽在掌握”的畅快感。对于更进阶的需求深入探索动态SQL、插件开发如分页、数据权限以及MyBatis-Plus生态你的持久层代码将会更加优雅和强大。

相关新闻

芯参谋(2): 查找本地文件问题,你是否有不知道文件放在那里找不到而烦恼!

芯参谋(2): 查找本地文件问题,你是否有不知道文件放在那里找不到而烦恼!

芯参谋 链接: https://pan.baidu.com/s/1kOwSLhm5CvAFqkNwy89BEw?pwd1788你记得有这个文件,但就是怎么都找不到,是否有在为这个烦恼。用芯参谋的 EveryFile查找 就可以帮忙 秒速找到。由于软件没有数字签名,就会被杀毒软件误拦截。所以安装时…

2026/10/15 2:42:20 阅读更多 →
66.ABAP ALV 双击跳转 VA03 事务码功能开发详解

66.ABAP ALV 双击跳转 VA03 事务码功能开发详解

摘要 SAP系统作为企业级ERP的行业标准,其技术体系复杂度极高。本文不讨论SAP业务模块操作,而是聚焦于ABAP开发工程师必须掌握的技术主线:从SAP系统架构基础,到ABAP面向对象编程,再到核心数据操作与ALV报表开发,最后深入到代码质量管控与性能调优。全文以严谨的逻辑链条,…

2026/9/28 12:38:32 阅读更多 →
MicroPython嵌入式开发实战:从架构解析到物联网应用

MicroPython嵌入式开发实战:从架构解析到物联网应用

1. 从“玩具”到“利器”:重新认识 MicroPython 如果你对嵌入式开发感兴趣,或者想用单片机做一些好玩的小项目,但又被 C 语言的指针、内存管理和复杂的编译环境劝退,那么 MicroPython 很可能就是你一直在找的那把钥匙。我第一次接…

2026/10/12 4:46:02 阅读更多 →

最新新闻

嵌入式验证性实验:从跑通代码到写出真正的实验体会

嵌入式验证性实验:从跑通代码到写出真正的实验体会

简介:苏州大学金葫芦平台下的《嵌入式系统》六个验证性实验,以一份Word实验报告完整呈现:实验一熟悉AHL-GEC-IDE环境、GPIO构件调用及输入输出控制,实验二串行接收中断、多字节组帧与C#串口调试,实验三PWM信号生成及占…

2026/10/15 5:26:11 阅读更多 →
STM32嵌入式实验全解析:RTOS调度机制与调试避坑指南

STM32嵌入式实验全解析:RTOS调度机制与调试避坑指南

简介:基于苏州大学金葫芦平台(STM32L431)的《嵌入式系统》验证性实验报告合集,面向高校嵌入式课程学习者、准备实验考核的学生及开发者。资源完整覆盖GPIO、串口中断、PWM、Flash、SPI及独立看门狗六个由浅入深的实验,…

2026/10/15 5:26:11 阅读更多 →
两串电池主动均衡设计与实现:从最小系统到系统联动

两串电池主动均衡设计与实现:从最小系统到系统联动

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 5:26:11 阅读更多 →
概率论第九章:参数估计与假设检验的学习方法与解题套路

概率论第九章:参数估计与假设检验的学习方法与解题套路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 5:26:11 阅读更多 →
wangEditor图片视频上传配置实践与常见坑点全解析

wangEditor图片视频上传配置实践与常见坑点全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 5:26:11 阅读更多 →
有界与冻结:对话系统跨会话记忆双闸设计实战

有界与冻结:对话系统跨会话记忆双闸设计实战

在对话系统和大模型应用里摸爬滚打久了,你迟早会撞上一个尴尬场景:用户上周明明跟你聊过“家里小孩对花生过敏”,这周回来你热情推荐了一款含花生碎的曲奇配方。用户没发火,但你心里清楚——这个系统失忆了。跨会话记忆不是什么新…

2026/10/15 5:25:11 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →