Java进阶全套教程(一)------ 数据框架Mybatis详解
一、MyBatis核心认知与开发定位
在Java后端开发中,我们通过JDBC可以实现Java程序与MySQL数据库的交互,完成数据增删改查操作,但原生JDBC存在大量冗余代码、硬编码严重、维护性差等诸多问题。MyBatis作为一款轻量级半自动ORM持久层框架,专门用于解决Java程序与数据库的数据交互问题,是目前Java企业级开发的主流持久层框架。
简单来说,MyBatis的核心定位是:简化JDBC操作,统一持久层代码规范,实现Java代码与SQL语句解耦。相较于原生JDBC,它剔除了注册驱动、获取连接、释放资源、封装结果集等重复模板代码,让开发者只需专注核心SQL编写与业务数据处理,极大提升数据库开发效率。
同时MyBatis属于半自动ORM框架,区别于Hibernate等全自动框架:全自动框架无需手写SQL,灵活性差;而MyBatis支持开发者自主编写SQL语句,兼顾开发效率与SQL优化灵活性,完美适配互联网项目多变的业务场景,这也是其成为Java开发首选持久层框架的核心原因。
1.1 原生JDBC核心痛点(MyBatis解决的核心问题)
想要理解MyBatis的价值,首先要明确原生JDBC开发的短板,这也是框架诞生的核心意义:
-
代码冗余繁琐:每次数据库操作都需要重复编写加载驱动、创建连接、创建Statement、关闭资源等固定模板代码,大量重复代码增加开发工作量。
-
SQL硬编码耦合:SQL语句直接写在Java代码中,一旦需要修改SQL逻辑,必须改动Java源码并重新编译、部署,维护成本极高。
-
参数封装繁琐:向SQL语句传递参数时,需要手动调用setXXX方法逐个赋值,参数较多时代码臃肿且容易出错。
-
结果集解析复杂:数据库查询后的ResultSet结果集,需要手动遍历、封装为Java实体对象,重复机械代码多,且容易出现类型转换异常。
-
连接资源浪费:原生JDBC每次操作都要创建、销毁数据库连接,无法实现连接复用,频繁IO操作导致程序性能低下。
1.2 MyBatis核心优势与适用场景
MyBatis针对性解决了JDBC的所有痛点,同时适配Java项目各类开发场景,核心优势如下:
-
代码极简,剔除冗余:自动完成数据库驱动加载、连接获取、资源关闭、结果集封装等通用操作,开发者仅需关注核心业务SQL与参数处理。
-
解耦彻底,易于维护:支持将SQL语句统一配置在XML文件或注解中,与Java业务代码完全分离,SQL修改无需改动Java代码,后续迭代维护更便捷。
-
参数自动映射:支持基本数据类型、实体对象、集合等多种参数自动封装,无需手动逐个赋值,大幅简化参数传递代码。
-
结果自动封装:可通过配置实现数据库字段与Java实体类属性的自动映射,无需手动遍历结果集,自动封装为实体对象或集合。
-
性能优异,灵活性高:框架轻量无冗余加载,支持手写优化SQL,适配高并发、海量数据场景,同时支持动态SQL,可根据业务条件灵活拼接查询语句。
-
生态完善,适配性强:完美适配Spring、SpringBoot主流框架,拥有丰富的插件、工具与社区资源,是企业项目、面试、实战开发的核心技术。
二、MyBatis核心架构与执行流程
掌握MyBatis的底层架构与执行流程,是后续熟练使用框架、排查报错、优化性能的基础,能够帮助开发者理解框架底层运行逻辑,避免只会套模板不懂原理的开发问题。
2.1 MyBatis核心四大核心组件
MyBatis的整体运行依赖四大核心组件,各司其职、层层联动,构成完整的数据库交互体系:
1. SqlSessionFactory(会话工厂)
MyBatis的核心工厂类,是整个框架的入口,负责读取MyBatis全局配置文件、加载所有Mapper映射文件,初始化MyBatis运行环境,最终用于生产SqlSession对象。该对象全局只需创建一次,属于单例资源。
2. SqlSession(会话对象)
MyBatis的核心操作对象,相当于原生JDBC的Connection连接对象,是程序与数据库交互的桥梁。所有数据库增删改查操作都通过SqlSession完成,同时具备事务管理能力,可手动提交、回滚事务。SqlSession为线程私有,每次数据库操作需创建新对象,操作完成后关闭释放资源。
3. Mapper接口与Mapper映射文件
Mapper接口是Java定义的持久层方法接口,仅声明数据库操作方法,无具体实现;Mapper映射文件(XML文件)用于编写对应方法的SQL语句、配置参数映射规则、结果集封装规则。二者通过全限定名绑定,实现方法与SQL的一一对应。
4. Executor(执行器)
MyBatis的底层执行核心,负责接收SqlSession的操作请求,调用JDBC底层API执行SQL语句,同时完成参数预处理、SQL执行、结果集封装、资源回收等底层操作,对上层开发者透明无感知。
2.2 MyBatis完整执行流程(通俗易懂版)
结合四大核心组件,MyBatis从程序发起请求到获取数据库结果的完整流程如下:
-
加载配置初始化:项目启动时,SqlSessionFactoryBuilder读取MyBatis全局配置文件,加载数据库连接参数、环境配置、Mapper文件路径等信息,创建SqlSessionFactory工厂对象。
-
获取会话对象:程序发起数据库操作请求时,通过SqlSessionFactory创建SqlSession会话对象。
-
绑定Mapper方法:SqlSession根据Mapper接口全限定名,匹配对应的Mapper映射文件,定位到目标方法对应的SQL语句。
-
底层执行SQL:Executor执行器对SQL语句进行参数填充、预处理,通过JDBC底层与数据库建立连接,执行增删改查操作。
-
结果自动封装:框架接收数据库返回的结果集,根据映射配置自动封装为对应的Java实体对象、List集合或基本数据类型。
-
资源释放收尾:SQL执行完成后,自动关闭数据库连接、释放Statement、ResultSet资源,关闭SqlSession会话,完成一次完整数据库交互。
三、MyBatis环境搭建与入门实战(可直接运行)
本节基于标准Maven项目搭建纯MyBatis开发环境,从零实现完整的增删改查操作,所有代码经过规范优化,无转义错误、无缺失,可直接复制运行,贴合企业开发标准。
3.1 项目依赖引入(Maven)
搭建MyBatis基础环境,只需引入核心依赖:MyBatis框架依赖、MySQL驱动依赖、单元测试依赖,完整pom.xml核心依赖配置如下:
xml
<!-- 项目统一版本管理 -->
<dependencies>
<!-- MyBatis核心依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- MySQL数据库驱动(适配MySQL8.0+) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- Junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
3.2 数据库与实体类准备
本次实战使用用户积分表作为测试数据表,语句标准可直接执行,无语法报错:
sql
CREATE TABLE user_score (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
username VARCHAR(30) NOT NULL COMMENT '用户名',
score INT DEFAULT 0 COMMENT '用户积分',
level TINYINT DEFAULT 1 COMMENT '用户等级',
create_time DATETIME DEFAULT NOW() COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户积分表';
对应创建标准Java实体类UserScore,严格遵循驼峰命名、私有化属性、完整get/set、无参有参构造、重写toString,适配MyBatis自动映射:
java
import java.util.Date;
/**
* 用户积分实体类
* 对应数据库user_score表
* 规范:数据库下划线字段 <=> Java驼峰属性
*/
public class UserScore {
// 主键ID
private Integer id;
// 用户名
private String username;
// 用户积分
private Integer score;
// 用户等级
private Integer level;
// 创建时间
private Date createTime;
// 无参构造(MyBatis反射实例化必须)
public UserScore() {}
// 有参构造
public UserScore(String username, Integer score, Integer level, Date createTime) {
this.username = username;
this.score = score;
this.level = level;
this.createTime = createTime;
}
// 完整getter、setter方法
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
// 重写toString,方便控制台打印查看结果
@Override
public String toString() {
return "UserScore{" +
"id=" + id +
", username='" + username + '\'' +
", score=" + score +
", level=" + level +
", createTime=" + createTime +
'}';
}
}
3.3 MyBatis全局核心配置文件(修复转义、标准DTD)
在resources目录下创建mybatis-config.xml,使用官方标准约束,修复所有标签转义异常,配置驼峰自动映射、SQL日志打印、数据库环境,可直接使用:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局参数配置 -->
<settings>
<!-- 开启驼峰命名自动映射:数据库create_time自动映射实体createTime -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启控制台SQL日志打印,开发调试必备 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 多环境配置,默认开发环境 -->
<environments default="development">
<environment id="development">
<!-- JDBC事务管理,手动控制事务提交回滚 -->
<transactionManager type="JDBC"/>
<!-- 内置连接池配置 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 注册所有Mapper映射文件 -->
<mappers>
<mapper resource="mapper/UserScoreMapper.xml"/>
</mappers>
</configuration>
3.4 Mapper接口与完整映射文件(补齐CRUD)
优化原有接口,补齐增、删、改、查单条、查全部、条件查询常用方法,覆盖基础开发场景,代码规范完整。
第一步:完整UserScoreMapper持久层接口
java
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户积分持久层接口
* 标准CRUD方法定义
*/
public interface UserScoreMapper {
/**
* 新增用户积分数据
* @param userScore 实体参数
* @return 受影响行数
*/
int insertUserScore(UserScore userScore);
/**
* 根据ID删除数据
* @param id 主键ID
* @return 受影响行数
*/
int deleteUserScoreById(@Param("id") Integer id);
/**
* 动态修改用户积分数据
* @param userScore 待修改实体
* @return 受影响行数
*/
int updateUserScore(UserScore userScore);
/**
* 根据ID查询单条数据
* @param id 主键ID
* @return 实体对象
*/
UserScore getUserScoreById(@Param("id") Integer id);
/**
* 查询全部用户积分数据
* @return 数据集合
*/
List<UserScore> listAllUserScore();
/**
* 根据等级、用户名、积分多条件动态查询
* @param level 用户等级
* @param username 用户名模糊匹配
* @param score 最低积分
* @return 条件匹配数据
*/
List<UserScore> listUserScoreByCondition(@Param("level") Integer level,
@Param("username") String username,
@Param("score") Integer score);
/**
* 批量根据ID查询数据
* @param idList ID集合
* @return 数据集合
*/
List<UserScore> listUserScoreByIds(@Param("idList") List<Integer> idList);
}
第二步:完整无报错UserScoreMapper.xml映射文件(修复所有转义、补齐全部SQL)
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接口全限定类名 -->
<mapper namespace="UserScoreMapper">
<!-- 自定义结果映射(通用适配所有查询) -->
<resultMap id="UserScoreResultMap" type="UserScore">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="score" property="score"/>
<result column="level" property="level"/>
<result column="create_time" property="createTime"/>
</resultMap>
<!-- 新增数据 -->
<insert id="insertUserScore">
INSERT INTO user_score(username,score,level,create_time)
VALUES(#{username},#{score},#{level},#{createTime})
</insert>
<!-- 根据ID删除 -->
<delete id="deleteUserScoreById">
DELETE FROM user_score WHERE id = #{id}
</delete>
<!-- 动态更新数据 -->
<update id="updateUserScore">
UPDATE user_score
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="score != null">
score = #{score},
</if>
<if test="level != null">
level = #{level}
</if>
</set>
WHERE id = #{id}
</update>
<!-- 根据ID查询单条数据 -->
<select id="getUserScoreById" resultMap="UserScoreResultMap">
SELECT id,username,score,level,create_time FROM user_score WHERE id = #{id}
</select>
<!-- 查询全部数据 -->
<select id="listAllUserScore" resultMap="UserScoreResultMap">
SELECT id,username,score,level,create_time FROM user_score
</select>
<!-- 多条件动态查询(where标签智能去重关键字) -->
<select id="listUserScoreByCondition" resultMap="UserScoreResultMap">
SELECT id,username,score,level,create_time FROM user_score
<where>
<if test="level != null">
level = #{level}
</if>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%',#{username},'%')
</if>
<if test="score != null">
AND score >= #{score}
</if>
</where>
</select>
<!-- 批量查询(foreach遍历集合) -->
<select id="listUserScoreByIds" resultMap="UserScoreResultMap">
SELECT id,username,score,level,create_time FROM user_score
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper>
3.5 标准版MyBatis工具类(单例工厂、资源安全)
优化工具类代码,保证线程安全、工厂单例,封装重载方法,支持手动事务提交/回滚,适配更多业务场景:
java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* MyBatis通用工具类
* 功能:全局单例SqlSessionFactory、快速获取会话、事务可控
*/
public class MyBatisUtil {
// 全局唯一工厂对象(单例)
private static final SqlSessionFactory SQL_SESSION_FACTORY;
// 静态块初始化工厂,项目启动仅执行一次
static {
try {
String configPath = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(configPath);
SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("MyBatis配置文件加载失败!");
}
}
/**
* 获取SqlSession,自动提交事务
* @return SqlSession会话
*/
public static SqlSession getSqlSession() {
return SQL_SESSION_FACTORY.openSession(true);
}
/**
* 获取SqlSession,手动控制事务(增删改推荐使用)
* @return SqlSession会话
*/
public static SqlSession getSqlSessionManual() {
return SQL_SESSION_FACTORY.openSession(false);
}
}
3.6 完整单元测试类(全覆盖CRUD)
编写全套测试方法,覆盖所有接口功能,包含事务手动提交案例,代码可直接运行,结果可直观查看:
java
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 全套MyBatis CRUD测试类
*/
public class UserScoreMapperTest {
// 测试新增数据(自动提交事务)
@Test
public void testInsert() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserScoreMapper mapper = sqlSession.getMapper(UserScoreMapper.class);
// 封装测试数据
UserScore userScore = new UserScore("lisi", 260, 4, new Date());
int rows = mapper.insertUserScore(userScore);
System.out.println("新增成功,受影响行数:" + rows);
sqlSession.close();
}
// 测试根据ID查询单条数据
@Test
public void testGetById() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserScoreMapper mapper = sqlSession.getMapper(UserScoreMapper.class);
UserScore userScore = mapper.getUserScoreById(1);
System.out.println("单条查询结果:" + userScore);
sqlSession.close();
}
// 测试查询全部数据
@Test
public void testListAll() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserScoreMapper mapper = sqlSession.getMapper(UserScoreMapper.class);
List<UserScore> scoreList = mapper.listAllUserScore();
scoreList.forEach(System.out::println);
sqlSession.close();
}
// 测试多条件动态查询
@Test
public void testListByCondition() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserScoreMapper mapper = sqlSession.getMapper(UserScoreMapper.class);
// 查询4级、积分≥200、用户名含li的用户
List<UserScore> scoreList = mapper.listUserScoreByCondition(4, "li", 200);
scoreList.forEach(System.out::println);
sqlSession.close();
}
// 测试动态更新数据(手动事务案例)
@Test
public void testUpdate() {
// 手动事务:需手动提交
SqlSession sqlSession = MyBatisUtil.getSqlSessionManual();
UserScoreMapper mapper = sqlSession.getMapper(UserScoreMapper.class);
// 仅更新积分和等级,用户名不修改
UserScore userScore = new UserScore();
userScore.setId(1);
userScore.setScore(320);
userScore.setLevel(5);
int rows = mapper.updateUserScore(userScore);
System.out.println("更新成功,受影响行数:" + rows);
// 手动提交事务
sqlSession.commit();
sqlSession.close();
}
// 测试批量查询
@Test
public void testListByIds() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserScoreMapper mapper = sqlSession.getMapper(UserScoreMapper.class);
List<Integer> idList = Arrays.asList(1,2,3);
List<UserScore> scoreList = mapper.listUserScoreByIds(idList);
scoreList.forEach(System.out::println);
sqlSession.close();
}
// 测试删除数据
@Test
public void testDelete() {
SqlSession sqlSession = MyBatisUtil.getSqlSessionManual();
UserScoreMapper mapper = sqlSession.getMapper(UserScoreMapper.class);
int rows = mapper.deleteUserScoreById(4);
System.out.println("删除成功,受影响行数:" + rows);
sqlSession.commit();
sqlSession.close();
}
}
四、MyBatis核心语法与映射规则(代码优化详解)
4.1 两种参数占位符完整对比(附实战代码)
MyBatis核心两种占位符是开发高频重点,本次补充实战案例,清晰区分使用场景:
1. #{参数名} 预编译占位符(推荐)
底层生成PreparedStatement,参数预编译、防SQL注入、自动类型适配,日常99%场景优先使用。
2. ${参数名} 字符串拼接占位符(慎用)
直接字符串拼接,无预编译,存在注入风险,仅用于动态表名、动态排序等特殊场景,实战示例:
xml
<!-- 动态排序场景:只能使用$ -->
<select id="listUserScoreOrderBy" resultMap="UserScoreResultMap">
SELECT id,username,score,level,create_time
FROM user_score
ORDER BY ${sortField} ${sortType}
</select>
4.2 ResultMap手动映射完整实战
解决字段名与实体名不匹配、复杂关联查询场景,本次优化通用ResultMap,可全局复用,无需重复定义:
xml
<!-- 全局通用结果映射 -->
<resultMap id="UserScoreResultMap" type="UserScore">
<!-- id标签:主键字段,提升查询效率 -->
<id column="id" property="id"/>
<!-- result标签:普通字段映射 -->
<result column="username" property="username"/>
<result column="score" property="score"/>
<result column="level" property="level"/>
<result column="create_time" property="createTime"/>
</resultMap>
五、动态SQL完整优化代码(企业级写法)
动态SQL是MyBatis核心功能,优化所有标签代码,去除冗余、规避语法错误,适配真实业务场景。
5.1 if + where 组合动态查询(最优写法)
where标签自动去除多余AND/OR,无需手动写1=1,代码简洁无BUG,为企业标准写法:
xml
<select id="listUserScoreByCondition" resultMap="UserScoreResultMap">
SELECT id,username,score,level,create_time FROM user_score
<where>
<!-- 等级非空匹配 -->
<if test="level != null">
level = #{level}
</if>
<!-- 用户名非空模糊查询,空字符串不匹配 -->
<if test="username != null and username != ''">
AND username LIKE CONCAT('%',#{username},'%')
</if>
<!-- 最低积分筛选 -->
<if test="score != null">
AND score >= #{score}
</if>
</where>
</select>
5.2 set动态更新标签(防多余逗号)
set标签自动剔除SQL末尾多余逗号,只更新传入的非空字段,避免全覆盖更新数据:
xml
<update id="updateUserScore">
UPDATE user_score
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="score != null">
score = #{score},
</if>
<if test="level != null">
level = #{level}
</if>
</set>
WHERE id = #{id}
</update>
5.3 foreach批量操作完整版
适配List集合参数,完整参数释义,支持批量查询、批量删除,企业高频使用:
xml
<!-- 批量查询 -->
<select id="listUserScoreByIds" resultMap="UserScoreResultMap">
SELECT id,username,score,level,create_time FROM user_score
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 批量删除扩展 -->
<delete id="batchDeleteByIds">
DELETE FROM user_score WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
六、开发规范与代码避坑总结
6.1 代码优化核心亮点
-
修复所有XML转义异常、DTD约束错误,所有配置文件零报错;
-
补齐完整CRUD代码,覆盖基础、动态SQL、批量操作全场景;
-
工具类优化为单例模式,线程安全、资源不浪费;
-
新增手动事务控制案例,适配增删改严谨业务场景;
-
所有代码遵循企业规范,命名语义化、结构统一、可直接上线使用。
6.2 高频代码避坑点
-
实体类必须提供无参构造方法,否则MyBatis反射实例化报错;
-
动态SQL查询必须使用where标签,禁止手动拼接1=1,避免语法冗余;
-
增删改操作建议手动控制事务,防止数据提交异常;
-
集合参数必须使用@Param注解绑定参数名,否则foreach无法识别;
-
优先使用#{ }占位符,杜绝滥用${ },防止SQL注入漏洞。