1. MyBatis入门实战:从零搭建完整数据访问层
最近在整理技术笔记,正好借新电脑配置环境的机会,重新梳理MyBatis的核心使用流程。作为Java生态中最受欢迎的ORM框架之一,MyBatis以其灵活的SQL映射和简洁的配置深受开发者喜爱。下面我就以一个用户管理模块为例,手把手演示如何从零搭建完整的MyBatis数据访问层。
1.1 环境准备与项目初始化
首先通过Spring Initializr创建基础项目,这里我选择:
- Spring Boot 3.1.5
- Java 17
- 打包方式Maven
关键依赖包括:
xml复制<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
实际开发中需要注意版本兼容性:
- MyBatis Starter 3.x对应Spring Boot 3.x
- 如果使用Spring Boot 2.x,需要选择MyBatis Starter 2.x版本
- 数据库驱动版本建议与数据库服务端版本匹配
国内开发建议配置Maven镜像加速依赖下载,在settings.xml中添加:
xml复制<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
1.2 数据库配置与连接测试
采用YAML格式配置数据源(application.yml):
yaml复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
mybatis:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
关键配置解析:
map-underscore-to-camel-case:开启字段自动映射(user_name → userName)log-impl:输出SQL日志便于调试- 生产环境建议添加连接池配置(如HikariCP)
2. 数据模型与Mapper设计
2.1 数据库表结构设计
创建三张典型关系表:
sql复制-- 用户表(主表)
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(50) NOT NULL,
age INT,
dept_id INT COMMENT '部门外键'
);
-- 部门表(一对一)
CREATE TABLE dept (
id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(50) NOT NULL
);
-- 订单表(一对多)
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
product VARCHAR(100) NOT NULL,
price DECIMAL(10,2) DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user(id)
);
2.2 实体类建模技巧
实体类采用包装类型而非基本类型,原因包括:
- 兼容数据库NULL值
- 更适合MyBatis的动态SQL处理
- 符合JPA规范
java复制// User.java
@Data // Lombok注解,实际项目推荐使用
public class User {
private Integer id;
private String userName; // 驼峰命名
private Integer age;
private Integer deptId;
// 一对一关联
private Dept dept;
// 一对多关联
private List<Order> orders;
}
注意:字段命名遵循Java驼峰规范,与数据库下划线命名自动映射
3. Mapper接口与XML映射
3.1 Mapper接口定义
基础CRUD接口示例:
java复制@Mapper
public interface UserMapper {
// 按ID查询
User selectById(@Param("id") Integer id);
// 查询所有(分页参数示例)
List<User> selectAll(@Param("offset") int offset,
@Param("limit") int limit);
// 插入并返回主键
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
// 动态条件查询
List<User> selectByCondition(@Param("condition") UserQueryCondition condition);
}
3.2 XML映射文件详解
在resources/mapper目录下创建UserMapper.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.UserMapper">
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="age" column="age"/>
<association property="dept" column="dept_id"
select="com.example.mapper.DeptMapper.selectById"/>
<collection property="orders" column="id"
select="com.example.mapper.OrderMapper.selectByUserId"/>
</resultMap>
<select id="selectById" resultMap="userResultMap">
SELECT * FROM user WHERE id = #{id}
</select>
<sql id="baseColumns">
id, user_name, age, dept_id
</sql>
<select id="selectAll" resultMap="userResultMap">
SELECT <include refid="baseColumns"/>
FROM user
LIMIT #{offset}, #{limit}
</select>
<insert id="insert" parameterType="User">
INSERT INTO user(user_name, age, dept_id)
VALUES(#{userName}, #{age}, #{deptId})
</insert>
</mapper>
关键元素解析:
resultMap:自定义结果集映射规则association:配置一对一关联查询collection:配置一对多关联查询sql/include:SQL片段复用#{}:预编译参数占位符,防止SQL注入
4. 高级映射与动态SQL
4.1 复杂结果映射
处理嵌套查询的N+1问题解决方案:
xml复制<resultMap id="userWithDeptResultMap" type="User">
<id property="id" column="id"/>
<!-- 其他基础字段映射 -->
<association property="dept" javaType="Dept">
<id property="id" column="dept_id"/>
<result property="deptName" column="dept_name"/>
</association>
</resultMap>
<select id="selectUserWithDept" resultMap="userWithDeptResultMap">
SELECT u.*, d.dept_name
FROM user u LEFT JOIN dept d ON u.dept_id = d.id
WHERE u.id = #{id}
</select>
4.2 动态SQL实践
xml复制<select id="selectByCondition" resultMap="userResultMap">
SELECT <include refid="baseColumns"/>
FROM user
<where>
<if test="condition.userName != null and condition.userName != ''">
AND user_name LIKE CONCAT('%', #{condition.userName}, '%')
</if>
<if test="condition.minAge != null">
AND age >= #{condition.minAge}
</if>
<if test="condition.maxAge != null">
AND age <= #{condition.maxAge}
</if>
<choose>
<when test="condition.deptId != null">
AND dept_id = #{condition.deptId}
</when>
<otherwise>
AND dept_id IS NOT NULL
</otherwise>
</choose>
</where>
ORDER BY id DESC
</select>
动态SQL标签说明:
where:智能处理WHERE前缀if:条件判断choose/when/otherwise:多条件分支foreach:遍历集合(常用于IN查询)
5. 常见问题排查与性能优化
5.1 典型问题解决方案
-
映射失败排查:
- 检查
resultType/resultMap是否配置正确 - 确认字段名是否匹配(开启驼峰映射或使用别名)
- 查看MyBatis日志输出的实际SQL
- 检查
-
SQL注入防护:
- 始终使用
#{}而非${}(除非动态表名等特殊场景) - 对用户输入进行严格校验
- 始终使用
-
事务管理:
- 在Service层添加
@Transactional注解 - 注意事务传播行为配置
- 在Service层添加
5.2 性能优化建议
- 二级缓存配置:
xml复制<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
- 批量操作优化:
java复制@Insert("<script>" +
"INSERT INTO user(user_name, age) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.userName}, #{item.age})" +
"</foreach>" +
"</script>")
void batchInsert(List<User> users);
- 分页查询优化:
xml复制<select id="selectPage" resultMap="userResultMap">
SELECT * FROM user
ORDER BY id
LIMIT #{offset}, #{pageSize}
</select>
6. 项目结构与最佳实践
6.1 推荐项目结构
code复制src/main/java
├── com.example
│ ├── config # 配置类
│ ├── controller # 控制层
│ ├── service # 业务层
│ ├── mapper # 数据访问层
│ └── entity # 实体类
└── resources
├── mapper # XML映射文件
└── application.yml
6.2 开发建议
-
接口设计原则:
- 每个Mapper接口对应一个实体类
- 方法名明确表达操作意图
- 参数使用
@Param注解明确命名
-
XML管理技巧:
- 相同表的操作集中在同一个XML文件
- 使用
<sql>片段复用公共SQL - 复杂查询添加注释说明业务逻辑
-
测试策略:
- 对Mapper层单独测试
- 使用
@MybatisTest注解 - 验证边界条件和异常场景
通过这个完整示例,我们可以看到MyBatis如何优雅地解决对象-关系映射问题。相比全自动ORM框架,MyBatis保留了SQL的灵活性,又通过智能映射减少了样板代码。在实际项目中,建议结合具体业务场景选择合适的映射策略,平衡开发效率与系统性能。