引言
在Java企业级应用开发领域,ORM框架无疑是构建高性能数据访问层的关键工具之一。MyBatis作为一款轻量级、易于学习且高度可定制化的持久层框架,以其简洁的设计理念、卓越的灵活性和高效的SQL处理能力,赢得了广大开发者的青睐。本文将系统全面地探讨MyBatis的基础架构、核心特性和应用场景,并结合实际开发经验,引导读者深入了解和高效使用MyBatis。本文偏向基础应用讲解,深入可查阅官方文档。
一、MyBatis入门与基础概念
1. MyBatis简介
MyBatis是一款基于Java的持久层框架,它通过映射配置文件或注解方式将SQL语句与Java对象关联起来,极大降低了传统JDBC编程的复杂度。MyBatis通过SqlSession API提供CRUD操作,并支持事务管理和结果映射等功能,使得开发者能够集中精力于业务逻辑开发,而非繁琐的数据库交互细节。
Mybatis框架的简单应用架构,如图所示:
2. 基本架构与核心组件
- Configuration(配置):MyBatis的核心配置文件,用于设置数据库连接信息、映射文件位置、插件、缓存策略等全局配置项。
- SqlSessionFactoryBuilder(工厂构建器):用于根据Configuration对象构建SqlSessionFactory实例。
- SqlSessionFactory(工厂):负责创建SqlSession实例,是数据库会话的生产者。
- SqlSession(会话):MyBatis的工作单元,提供了执行SQL、获取映射结果、提交或回滚事务等操作接口,每个SqlSession都会维护自己的一级缓存。
- Mapper接口与映射文件:通过XML或注解方式定义SQL语句和结果映射规则,MyBatis通过动态代理机制将接口方法与映射文件中的SQL绑定,实现面向对象的数据库操作。
二、MyBatis核心特性详解
1. 映射配置与动态SQL
- Mapper XML配置:在XML映射文件中定义SQL查询语句、插入、更新、删除等操作,并通过
<resultMap>
标签精细化地配置结果集到Java对象的映射规则,支持嵌套结果集、集合、联合查询等多种复杂映射场景。 - 动态SQL:MyBatis提供了一系列动态标签如
<if>
,<choose>
,<when>
,<otherwise>
,<where>
,<set>
等,使得SQL语句可以根据Java变量的值动态生成,极大地增强了SQL表达力和灵活性。
1.1 xml与注解比较
1.1.1 xml定义
XML是一种可扩展性语言,用户可以自己定义标签,用来描述特定类型的数据;
XML的语法严格,每个标签都必须有一个结束标签,标签的嵌套关系也必须合法;
1.1.2 和SQL注解比较
- xml配置SQL,可以将SQL语句和JAVA代码分离开
- xml配置SQL,支持动态SQL语句
- xml配置SQL,支持SQL语句的复用
1.2 环境初始化
- SpringBoot版本:2.7.12
- 依赖项
1.MyBatis Framework
2.MySQL Driver
1.3 使用流程
1.在resources目录下创建 mappers目录,用来存放xml配置文件
2.下载映射文件模板
3. application.properties中添加配置:mybatis框架映射配置文件的位置
# 设置MyBatis框架的映射(Mapper)配置文件的位置
mybatis.mapper-locations=classpath:mappers/*.xml
1.4 xml配置SQL标签
说明
在 Mybatis 的 XML 文件中,SQL 语句都是使用 SQL 标签来定义的。
常用的SQL标签
select
用于查询操作,包括多表查询、条件查询等。可以使用 resultType 来指定返回结果的类型。
insert
用于插入操作,并将其自动注入实体类中。
update
用于更新操作,包括更新一条记录或者批量更新。
delete
用于删除操作,包括删除一条记录或者批量删除。
if、foreach、set
用于条件控制,可以根据不同的条件进行查询、插入、更新和删除操作。if 标签用于指定可以 为空的查询条件,foreach 标签用于循环查询,set 标签用于指定更新操作的字段值。
sql:用于定义可重用的 SQL 片段,通常是一些较为复杂的 SQL 片段。可以在其它 SQL 语句 中使用 include 标签来引用 SQL 片段。
include:用于引入外部的 SQL 片段。可以在 include 标签的 refid 属性中指定外部 SQL 片段 的名字,然后在当前 SQL 中使用它。
这些 SQL 标签可以随意组合,可以使 SQL 语句变得很灵活和强大。通常需要根据实际业务场 景选择合适的标签来实现相应的 SQL 操作。
1.5 整合MyBatis完成用户数据操作
1.5.1 知识点设计
基于本业务实现MyBatis基本操作,掌握MyBatis中xml配置SQL的应用。
1.5.2 Dao接口设计
UserMapper.java
@Mapper
public interface UserMapper {
/**在User表中插入一条表记录*/
int insert(User user);
}
1.5.3 定义映射文件
UserMapper.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">
<!-- 1.填写namespace,填写UserMapper的绝对路径 -->
<mapper namespace="cn.qiang._04mybatis.mapper.UserMapper">
<!-- id的属性值要和UserMapper中定义的方法名一致 -->
<insert id="insert">
INSERT INTO user VALUES (NULL,#{username},#{password},#{nickname},#
{created})
</insert>
</mapper>
1.5.4 Dao接口单元测试
// 自动装配
@Autowired
private UserMapper userMapper;
// 测试插入数据
@Test
void testInsert(){
User user = new User();
user.setUsername("熊三");
user.setPassword("123456");
user.setNickname("只手遮天");
user.setCreated(new Date());
// 调用接口方法
System.out.println(userMapper.insert(user));
}
1.6 整合MyBatis完成标签业务操作
1.6.1 业务描述
基于SpringBoot脚手架工程对MyBatis框架的整合,实现对微博内容weibo表进行操作。
1.6.2 Dao接口设计
WeiboMapper.java
package cn.qiang._04mybatis.mapper;
import cn.qiang._04mybatis.pojo.Weibo;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface WeiboMapper {
/**在微博表中插入数据*/
int insert(Weibo weibo);
/**根据微博id查询数据*/
Weibo selectByWeiboId(int id);
/**查询所有微博信息*/
List<Weibo> selectWeibo();
/**更新微博表数据*/
int updateById(Weibo weibo);
/**删除微博表数据*/
int deleteById(int id);
}
1.6.3 定义映射文件WeiboMapper.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">
<!-- 1.填写namespace,填写WeiboMapper的绝对路径 -->
<mapper namespace="cn.qiang._04mybatis.mapper.WeiboMapper">
<!--在微博表中插入数据-->
<insert id="insert">
INSERT INTO weibo
VALUES (NULL, #{content}, #{created}, #{userId})
</insert>
<!--根据微博id查询数据-->
<select id="selectByWeiboId" resultType="cn.tedu._04mybatis.pojo.Weibo">
SELECT *
FROM weibo
WHERE id = #{id}
</select>
<!--查询所有微博信息-->
<select id="selectWeibo" resultType="cn.tedu._04mybatis.pojo.Weibo">
SELECT *
FROM weibo
</select>
<!--更新微博表数据-->
<update id="updateById">
UPDATE weibo
SET content=#{content},
created=#{created},
user_id=#{userId}
WHERE id = #{id}
</update>
<!--删除微博表数据-->
<delete id="deleteById">
DELETE
FROM weibo
WHERE id = 2
</delete>
</mapper>
1.6.4 Dao接口单元测试
/**自动装配*/
@Autowired
private WeiboMapper weiboMapper;
/**在微博表中插入数据-测试方法*/
@Test
void InsertWeibo(){
Weibo weibo = new Weibo();
weibo.setContent("今天天气真不错呀");
weibo.setCreated(new Date());
weibo.setUserId(1);
weiboMapper.insert(weibo);
}
/**查询所有微博信息-测试方法*/
@Test
void selectWeiboTest(){
System.out.println(weiboMapper.selectWeibo());
}
/**根据微博id查询数据*/
@Test
void selectByWeiboIdTest(){
System.out.println(weiboMapper.selectByWeiboId(2));
}
/**更新微博表数据-测试*/
@Test
void updateById(){
Weibo weibo = new Weibo();
weibo.setId(2);
weibo.setContent("人生得意须尽欢");
weibo.setCreated(new Date());
weibo.setUserId(2);
System.out.println(weiboMapper.updateById(weibo));
}
/**删除微博表数据-测试*/
@Test
void deleteByIdTest(){
System.out.println(weiboMapper.deleteById(2));
}
1.7 动态SQL语句
1.7.1 Dao接口设计
CommentMapper
/**1.第一种批量删除: 传递Integer的数组*/
int deleteByIds1(Integer[] ids);
/**2.第二种批量删除:传递集合参数*/
int deleteByIds3(List<Integer> ids);
1.7.2 定义映射文件UserMapper.xml
<!-- 批量删除-数组格式:
collection用来设置遍历对象的类型,
item设置遍历出每一个变量的名称
separator设置分隔符
注意:注释一定要放在delete标签的外面,不能放在里面,放在里面会被当做sql语句执行!
-->
<delete id="deleteByIds1">
DELETE FROM comment WHERE id IN(
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
<!-- 集合传参,需要把collection改为 list -->
<delete id="deleteByIds2">
DELETE FROM comment WHERE id IN(
<foreach collection="list" item="id" separator=",">
#{id}
</foreach>
)
</delete>
1.7.3 Dao接口单元测
/**1.第一种批量删除: 传递Integer的数组-测试*/
@Test
void delete1(){
// 测试数组方式删除
Integer[] ids = {2, 8};
commentMapper.deleteByIds1(ids);
}
/**2.第二种批量删除:传递集合参数-测试*/
@Test
void delete3(){
// 测试集合方式删除
ArrayList<Integer> ids = new ArrayList<>();
ids.add(18);
ids.add(20);
ids.add(22);
commentMapper.deleteByIds2(ids);
}
1.7.4 动态修改数据
说明
如果表中字段很多,但是只改部分字段数据,比如只修改部分字段的值,不修改其他字段的值,如 果使用对象作为参数则会将其他字段的值也修改为null,如果使用传参方式解决,参数过多也会很 麻烦,所以可以使用动态修改。
实现
使用xml中的 <set></set>和 <if></if>标签组合
语法示例:
<update id="dynamicUpdate">
UPDATE product
<set>
<if test="title!=null">title=#{title},</if>
<if test="price!=null">price=#{price},</if>
<if test="num!=null">num=#{num}</if>
</set>
WHERE id=#{id};
</update>
1.7.5 Dao接口设计
CommentMapper
/**动态修改数据*/
int dynamicUpdate(Comment comment);
1.7.6 定义映射文件UserMapper.xml
<!-- 动态修改-->
<update id="dynamicUpdate">
UPDATE comment
<set>
<if test="content!=null">content=#{content},</if>
<if test="created!=null">created=#{created},</if>
<if test="userId!=null">user_id=#{userId},</if>
<if test="weiboId!=null">weibo_id=#{weiboId}</if>
</set>
WHERE id=#{id};
</update>
1.7.7 Dao接口单元测试
/**动态修改数据-测试*/
@Test
void dynamicUpdateTest(){
Comment comment = new Comment();
comment.setId(27);
comment.setContent("莫使金樽空对月");
comment.setUserId(666);
commentMapper.dynamicUpdate(comment);
}
1.8 SQL语句重用
1.8.1 说明
SQL语句重用是指在数据库应用程序中,多次执行相同或类似的SQL语句时,通过重用这些语句来提高 性能,减少系统消耗的资源。
1.8.2 实现
使用<sql></sql>和<include></include>标签组合
<sql></sql>标签中存放重复的SQL语句,使用<include></include>标签获取重复的SQL
1.8.3 示例
在三种动态删除的SQL语句中,都有重复的SQL语句: DELETE FROM comment WHERE id IN ,可以将 重复的语句抽取出来,来简化SQL。
1. mappers.CommentMapper.xml 将删除语句重复的SQL抽取出来
<!-- 1.重复SQL抽取-sql标签 -->
<sql id="deleteSql">
DELETE FROM comment WHERE id in
</sql>
<delete id="deleteByIds1">
<!--2.通过include标签复用-include标签-->
<include refid="deleteSql"></include>(
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
<delete id="deleteByIds2">
<!--2.通过include标签复用-include标签-->
<include refid="deleteSql"></include>(
<foreach collection="list" item="id" separator=",">
#{id}
</foreach>
)
</delete>
2. 执行对应的测试用例测试
1.9 多表联查
1.9.1 多表联查
- 展示内容
用户昵称: 微博内容
花千骨说:今天天气不错挺风和日丽的
- 查询内容
微博id、微博内容、用户昵
- 实现
1. mapper.WeiboMapper
// 首页微博列表数据
List<WeiboIndexVO> selectIndex();
2. mappers.WeiboMapper.xml
<select id="selectIndex"
resultType="cn.qiang.weibo.pojo.vo.WeiboIndexVO">
SELECT w.id, w.content, u.nickname
FROM weibo w JOIN user u ON w.user_id=u.id;
</select>
3. pojo.vo.WeiboIndexVO
public class WeiboIndexVO {
// 显示微博的id content , 再显示一个nickname
private Integer id;
private String content;
private String nickname;
}
4. 测试方法
@Test
void weiboIndexTest(){
System.out.println(weiboMapper.selectIndex());
}
2. 事务管理
MyBatis支持两种事务管理模式:自动提交和手动控制。在开启手动控制的情况下,开发者可通过SqlSession的commit()
和rollback()
方法管理事务边界。
3. 缓存机制
- 一级缓存(本地缓存):在同一SqlSession生命周期内,对于相同的查询请求,MyBatis会优先从缓存中获取数据,减少不必要的数据库访问。
- 二级缓存(全局缓存):跨越SqlSession的全局缓存,需在Mapper XML中明确开启,适合读多写少且数据变化不频繁的场景。二级缓存支持自定义缓存插件,比如Ehcache、Redis等。
三、MyBatis与Spring集成
1. 整合Spring Boot
通过引入相关依赖和配置,将MyBatis无缝整合到Spring Boot项目中,利用Spring的依赖注入和事务管理能力,简化配置和增强整体架构的协调性。
2. MyBatis-Spring模块
MyBatis-Spring模块提供了SqlSessionTemplate和MapperFactoryBean等组件,使得MyBatis可以更好地配合Spring容器进行事务管理和Mapper接口注入。
3. MyBatis与Spring Data JPA比较
虽然Spring Data JPA提供了更为便捷的repository接口,但对于复杂查询和个性化需求,MyBatis的灵活性和可控性优势显著。
四、MyBatis最佳实践与性能优化
1. 合理设计Mapper接口和映射文件
遵循单一职责原则,避免在一个Mapper接口中堆积过多SQL操作,保证映射文件结构清晰,提高代码可读性和可维护性。
2.SQL优化与缓存策略调整
结合业务特点,对SQL语句进行优化,合理利用MyBatis的一级缓存和二级缓存,降低数据库压力,提升系统性能。
3.日志与调试
配置合适的日志级别和日志框架(如Log4j、SLF4J),有助于在开发阶段快速定位问题,监控SQL执行情况和性能瓶颈。
结语
MyBatis作为一款优秀的Java持久层框架,它的广泛应用得益于其简单直观的API设计、丰富的SQL定制能力以及与主流Java生态的深度融合。通过深入理解和掌握MyBatis的各项特性,我们可以在实际开发过程中更加游刃有余地应对各种复杂的数据库操作需求,构建出高效稳定的持久层解决方案。