Mybatis
什么是MyBatis?
- MyBatis是一款优秀的持久化框架,用于简化JDBC开发。
- MyBatis源于Apache的一个开源项目iBatis,2010年iBatis项目由Apache Software Foundation迁移到Google Code,并更名为MyBatis。
iBatis
- iBatis一词源于
internet
和abatis
组合,是一个基于Java的持久层框架 - iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)
持久层
- 持久层负责将数据保存到数据库这一层
- JavaEE三层架构:表现层、业务层、持久层
数据持久化是将内存中的数据模型转换为存储模型,并将存储模型转换为内存中数据模型的统称。比如:文件的存储,数据的读取、数据表的增删改查等都是数据持久化操作。
框架
- 框架是一个半成品软件,是一套可重用、通用的、软件基础代码模型。
- 在框架的基础上构建软件时,编码会更加高效、规范、通用、可扩展。
MyBatis是一个半自动映射的框架,需手动匹配POJO和SQL的映射关系。MyBatis需手动编写SQL,因此灵活多变,支持动态SQL、处理列表、动态生成表名、支持存储过程,工作量相对较大。
JDBC缺点
- 硬编码:注册驱动、获取连接、SQL语句...
- 操作繁琐:手动设置参数、手动封装结果集...
资料
- https://mybatis.org/mybatis-3/zh/
- https://blog.mybatis.org/
- https://github.com/mybatis/mybatis-3/releases
MyBatis是一个开源、轻量级的数据持久化框架,是JDBC和Hibernate的替代方案。
- MyBatis避免了JDBC代码和手动设置参数、获取结果集。
- MyBatis内部封装了JDBC,简化了加载驱动、创建连接、创建
Statement
等繁杂的过程,开发者只需关注SQL语句本身。 - MyBatis使用XML或注解来配置和映射原生信息,将接口和Java的POJOs(Plain Ordinay Java Object,普通的Java对象)映射成数据库中的记录。
MyBatis支持定制化SQL、存储过程、高级映射,可以在实体类和SQL语句之间建立映射关系,是一种自动化的ORM实现。
MyBatis的核心思想是将程序中大量的SQL语句剥离出来,使用XML文件或注解的方式实现SQL的灵活配置,将SQL语句与程序代码分离,在不修改程序代码的情况下,直接在配置文件中修改SQL语句。
安装
- 使用MyBatis前只需将
mybatis.jar
文件置于类路径classpath
中即可 - 若使用Maven构建项目则需在
pom.xml
文件中添加MyBatis的依赖代码。
快速入门
创建Maven模块导入MyBatis坐标
$ vim pom.xml
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
添加MySQL数据库驱动的依赖包
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
添加Lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
添加日志相关依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
导入JUnit测试依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
编写MyBatis核心配置文件
$ vim resources/mybatis-config.xml
替换JDBC连接信息参数,解决硬编码问题。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/fw"/>
<property name="username" value="root"/>
<property name="password" value="q7tkI4QvegrjUTCm"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="SysUserMapper.xml"/>
</mappers>
</configuration>
创建数据表
CREATE TABLE `sys_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '唯一编号',
`created_at` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人',
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
`updated_by` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '更新人',
`username` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '账户',
`password` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '密码',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户';
定义POJO类
$ vim pojo/SysUser.java
package com.jc.pojo;
import lombok.Data;
import java.util.Date;
@Data
public class SysUser {
private Long id;
private String code;
private Date createdAt;
private Date updatedAt;
private String createdBy;
private String updatedBy;
private String username;
private String password;
}
创建SQL映射文件,统一管理SQL语句,解决硬编码问题。
$ vim resources/SysUserMapper.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="sysUser">
<select id="selectList" resultType="com.jc.pojo.SysUser">
SELECT * FROM sys_user WHERE 1=1
</select>
</mapper>
- 加载核心配置文件,获取SqlSessionFactory对象。
- 获取SqlSession对象执行SQL语句
- 释放资源
$ vim app.java
public static void mybatis(){
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
SqlSession ss = ssf.openSession();
List<SysUser> ll = ss.selectList("sysUser.selectList");
System.out.println(ll);
ss.close();
}
MyBatis包含一个名为Resources
的工具类,它包含一些实用方法可从类路径或其它位置中加载资源文件。
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
每个MyBatis应用程序都使用了SqlSessionFactory
实例,SqlSessionFactory
实例可通过SqlSessionFactoryBuilder
获得,SqlSessionFactoryBuilder
可从XML配置文件或预定义配置类Configuration
实例中获取。使用MyBatis后只需提供SQL语句,关注点可集中在SQL语句上。
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
IDEA中解决SQL映射文件的警告提示信息,由于IDEA和数据库没有建立连接,因此在XML中无法是被表信息,解决方案只需在IDEA中配置并连接数据库即可。
配置数据库连接Mapper代理开发
MyBatis开发中会编写大量SQL到XML文件中,除了特殊的业务逻辑外,存在大量结构相似的增删改查的SQL。
使用Mapper代理开发的目的是为了解决原生方式中的硬编码,同时为了简化后期执行SQL。
Mapper的目的是为了解决单表增删改查,基于MyBatis插件开发者无需编写SQL,无需再DAO中增加方法,只需要编写好实体类,就能够支持相应的操作。
定义与SQL映射文件同名的Mapper接口
$ vim java/com/jc/mapper/SysUserMapper.java
package com.jc.mapper;
import com.jc.pojo.SysUser;
import java.util.List;
public interface SysUserMapper {
List<SysUser> list();
}
将Mapper接口与SQL映射文件放置在同一目录下
$ vim resources/jc/com/mapper/SysUserMapper.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.jc.mapper.SysUserMapper">
<select id="list" resultType="com.jc.pojo.SysUser">
SELECT * FROM sys_user WHERE 1=1
</select>
</mapper>
设置SQL映射文件的namespace
属性值为Mapper接口全限定名
namespace="com.jc.mapper.SysUserMapper"
在Mapper接口中定义方法,方法名为SQL映射文件中SQL语句的id属性,同时保持参数类型和返回值一致。
List<SysUser> list();
如果Mapper接口名与SQL映射文件名相同,并在同一目录下则可使用包扫描方式来简化SQL映射文件的加载。
$ vim resources/mybatis-config.xml
<mappers>
<!-- <mapper resource="com/jc/mapper/SysUserMapper.xml"/>-->
<package name="com.jc.mapper" />
</mappers>
编码流程
- 通过SqlSession的
getMapper()
方法获取Mapper接口中的代理对象
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
SqlSession ss = ssf.openSession();
SysUserMapper m = ss.getMapper(SysUserMapper.class);
List<SysUser> l = m.list();
System.out.println(l);
ss.close();
openSession()
默认接收名为autoCommit
的布尔值用于设置自动提交事务,默认不填为false
,表示关闭事务自动提交,因此在DML语句执行时需手工提交事务。
- 调用对应方法完成SQL的执行
SysUserMapper m = ss.getMapper(SysUserMapper.class);
List<SysUser> l = m.list();
System.out.println(l);
核心配置
- 配置标签时需遵循前后顺序
$ vim resources/mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/fw"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- <mapper resource="com/jc/mapper/SysUserMapper.xml"/>-->
<package name="com.jc.mapper" />
</mappers>
</configuration>
类型别名typeAliases
添加类型别名可为Java类型设置一个缩写名字,仅用于XML配置,目的在于降低冗余的全限定类名的书写。
$ vim resources/mybatis-config.xml
<configuration>
<typeAliases>
<package name="com.jc.pojo" />
</typeAliases>
</configuration>
配置类型别名后,可直接简化SQL映射文件中resultType
中的之前全限定类名。
$ vim resources\com\jc\mapper\SysUserMapper.xml
<mapper namespace="com.jc.mapper.SysUserMapper">
<select id="list" resultType="SysUser">
SELECT * FROM sys_user WHERE 1=1
</select>
</mapper>
需要注意的是如果数据库表的名称和POJO实体类的属性名称不一致则不能自动封装数据,解决的方法有两种:
- 起别名:对不一样的列名起别名,让别名和实体类的属性名保持一致。
<mapper namespace="com.jc.mapper.SysUserMapper">
<select id="list" resultType="SysUser">
SELECT id, created_at AS createdAt, created_by AS createdBy FROM sys_user WHERE 1=1
</select>
</mapper>
起别名的缺点在于每次查询都要定义一次别名,解决方案可采用SQL片段的方式,但SQL片段的方式也不够灵活。
<mapper namespace="com.jc.mapper.SysUserMapper">
<sql id="column">
id, created_at AS createdAt, created_by AS createdBy
</sql>
<select id="list" resultType="SysUser">
SELECT
<include refid="column" />
FROM sys_user WHERE 1=1;
</select>
</mapper>
- resultMap:定义
<resultMap>
标签完成不一致的属性名和列名的映射
<mapper namespace="com.jc.mapper.SysUserMapper">
<resultMap id="resultMap" type="SysUser">
<id column="id" property="id" />
<result column="code" property="code" />
<result column="created_at" property="createdAt" />
<result column="created_by" property="createdBy" />
</resultMap>
<sql id="column">
id, code, created_at, created_by
</sql>
<select id="list" resultMap="resultMap">
SELECT
<include refid="column" />
FROM sys_user WHERE 1=1;
</select>
</mapper>
MyBatisX
MyBatisX是一款基于IDEA的快速开发插件,为效率而生。
- MyBatisX - IntelliJ IDEA Plugin | Marketplace (jetbrains.com)
- MybatisX快速开发插件 | MyBatis-Plus (baomidou.com)
主要功能
- 实现XML与接口方法相互跳转
- 根据接口方法生成Statement
增删改查
主键查询
public interface SysUserMapper {
SysUser getById(long id);
}
<mapper namespace="com.jc.mapper.SysUserMapper">
<select id="getById" resultType="SysUser" parameterType="long">
SELECT * FROM sys_user WHERE 1=1 AND id=#{id}
</select>
</mapper>
参数占位符 | 说明 |
---|---|
#{} | 将所占据位置替换为? 号,为防止SQL注入而设置的预处理语句。 |
${} | 直接替换,有SQL注入风险。当表名或列名不固定的情况下可使用。 |
参数类型:可省略
parameterType="long"
特殊字符处理
- 使用转义字符
<select id="getById" resultType="SysUser" parameterType="long">
SELECT * FROM sys_user WHERE 1=1 AND id < #{id}
</select>
- 使用CDATA区
<select id="getById" resultType="SysUser" parameterType="long">
SELECT * FROM sys_user WHERE 1=1 AND id <![CDATA[<]]> #{id}
</select>
条件查询
接收参数三种方式
设置参数 | 说明 |
---|---|
散装参数 | 使用@Param("SQL中的参数占位符名称")
|
实体类封装参数 | 只需保证SQL中参数名和实体类属性名对应即可 |
Map集合 | 只需保证SQL中参数名和Map集合键名对应即可 |
例如:多条件查询
List<SysUser> list(Map m);
List<SysUser> list(SysUser d);
List<SysUser> list(@Param("id") Long id, @Param("username") String username);
SQL语句需要随着用户输入或外部条件的变化而变化,即动态SQL。
<select id="list" resultMap="resultMap">
SELECT * FROM sys_user WHERE 1=1
<if test="id != null">AND id = #{id}</if>
<if test="username != null">AND username LIKE #{username}</if>
</select>
注意LIKE
模糊匹配时需预先对参数值username
处理
username = “%“+username+”%”
MyBatis对动态SQL提供支持
标签 | 说明 |
---|---|
if | 条件判断 |
choose(when, otherwise) | 从多个条件选择一个 |
trim(where, set) | 截取 |
foreach | 遍历循环 |
添加数据
- 提交事务
- 主键返回
int add(SysUser d);
<insert id="add" useGeneratedKeys="true" keyProperty="id">
INSERT INTO sys_user(
created_at
<if test="username != null">,username</if>
<if test="password != null">,password</if>
) VALUES (
NOW()
<if test="username != null">,#{username}</if>
<if test="password != null">,MD5(#{password})</if>
)
</insert>
测试获取添加后的主键
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
SqlSession ss = ssf.openSession();
SysUserMapper m = ss.getMapper(SysUserMapper.class);
SysUser d = new SysUser();
d.setUsername("root");
if(m.add(d) > 0){
System.out.println(d.getId());
}
ss.commit();
ss.close();
修改数据
- 修改全部字段
- 修改全部字段
int edit(SysUser d);
<update id="edit">
UPDATE sys_user
<set>
updated_at=NOW()
<if test="username != null">,username = #{username}</if>
<if test="password != null">,password = MD5(#{password})</if>
</set>
<where>
<if test="id != null">AND id = #{id}</if>
<if test="username != null">AND username = #{username}</if>
</where>
</update>
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
SqlSession ss = ssf.openSession();
SysUserMapper m = ss.getMapper(SysUserMapper.class);
SysUser d = new SysUser();
d.setId(1L);
d.setPassword("root");
if(m.edit(d) > 0){
System.out.println(d.getId());
}
ss.commit();
ss.close();
删除数据
- 删除单条
int delById(long id);
<delete id="delById">
DELETE FROM sys_user
<where>
<if test="id != null">id=#{id}</if>
</where>
</delete>
- 批量删除
int delByIds(@Param("ids") long[] ids);
<delete id="delByIds">
DELETE FROM sys_user
<where>
id IN
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</where>
</delete>
参数传递
MyBatis接口方法可接收各种类型的参数,底层对这些参数进行不同的封装处理。
- MyBatis接口接收单个参数的类型包括POJO、Map、Collection、List、Array...
单个参数 | 说明 |
---|---|
POJO | 直接使用,属性名和参数占位符名称必须保持一致。 |
Map | 直接使用,键名和参数占位符名称必须保持一致。 |
Collection | 会封装成为Map,可使用@Param 注解。 |
- MyBatis接口接收多个参数时需使用
@Param
注解
当使用多个参数传递时,MyBatis会将多个参数封装成为一个Map集合,MyBatis底层提供了ParamNameResolver
类进行参数封装,使用@Param
注解时会替换Map集合中默认的键名。
建议参数传递时使用@Param
注解来修改Map集合中默认的键名,并使用修改后的名称来获取值,这样可读性更高。
分层架构
分层 | 描述 |
---|---|
API接口层 | 提供给外部使用的接口API |
数据处理层 | 负责具体的SQL查询、解析、执行、结果映射处理,目的是根据调用的请求完成依次数据库操作。 |
基础支撑层 | 负责最基础的功能支撑,包括连接管理、事务管理、配置加载、缓存处理。 |
框架架构
步骤 | 名称 | 描述 |
---|---|---|
1 | 加载配置 | 将SQL配置信息加载成一个个的MappedStatement 对象并存储到内存 |
2 | SQL解析 | API接口成接收到调用请求时,接收到传入SQL的ID和对象,根据ID找到对应的MapperStatement,在根据入参对象对MapperStatement解析,解析后获得最终要执行的SQL语句和参数。 |
3 | SQL执行 | 将最终得到的SQL和参数拿到数据库进行执行,获得操作数据库的结果。 |
4 | 结果映射 | 将操作数据库的结果按映射配置进行转换 |
执行流程
处理操作请求过程
- 根据SQL的ID查找对应的
MappedStatement
对象 - 根据传入参数对象解析
MappedStatement
对象,最终得到要执行的SQL和执行传入参数。 - 获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
- 根据
MappedStatement
对象中的结果映射配置,对得到的执行结果进行转换处理,并得到最终的处理结果。 - 释放连接资源
操作流程
- 加载配置并初始化
项目 | 说明 |
---|---|
触发条件 | 加载配置文件 |
处理过程 | 将SQL配置信息加载成为一个一个的MapperStatement 对象(包括传入参数映射配置、执行的SQL语句、结果映射配置),存储到内存中。 |
配置来源两个地方:配置文件、Java代码注解
- 接收调用请求
项目 | 说明 |
---|---|
触发条件 | 调用MyBatis提供的API |
传入参数 | 为SQL的ID和传入参数对象 |
处理过程 | 将请求传递给下层(请求处理层)进行处理 |
- 处理操作请求
项目 | 说明 |
---|---|
触发条件 | API接口层传递请求过来 |
传入参数 | 为SQL的ID和传入参数对象 |
- 返回处理结果
注解开发
使用注解开发会比配置文件更加方便,注解适用于简单查询,对于复杂查询依然使用XML配置文件的方式。
注解 | 说明 |
---|---|
@Select | 查询 |
@Insert | 添加 |
@Update | 修改 |
@Delete | 删除 |
@Mapper
@Mapper
注解是由MyBatis框架定义的一个描述数据层接口的注解(描述性作用)。
@Mapper
用于告知SpringBoot框架此接口的实现类是由MyBatis负责创建,并将其实现类对象存储到Spring容器中进行管理。
@Mapper
public interface SysDictTypeMapper
SSM项目中大量配置文件的读写,可通过注解的方式来简化操作。对于DAO接口层的配置可通过@mapper
注解来实现。
若直接在Mapper
接口类中添加@Mapper
注解,需在每个Mapper接口类都添加@Mapper
注解,相对较为繁琐。
@Mapper
的作用是给Mapper接口自动生成一个实现类,让Spring对Mapper接口的Bean进行管理,省略较为复杂的XML文件。
@Mapper
注解是MyBatis自动配置时,默认扫描的注解类。
@MapperScan
通过@MapperScan
可指定需要扫描的Mapper接口类的包路径,在路径中可使用*
作为通配符对包名进行匹配。也可使用@MapperScan
注解对多个包进行扫描。
Spring集成
当MyBatis与依赖注入框架注入Spring、Guice等同时使用时,SqlSession
将会被依赖注入框架所创建,因此无需使用SqlSessionFactoryBuilder
或SqlSessionFactory
。