前言
1. 本文主要记录mybatis的使用及核心原理、mybatis集成springboot项目的相关内容、mybatis-plus概述和核心功能的使用。
2. 相关官网文档链接:(1)mybatis:https://mybatis.net.cn/index.html (2)mybatis-spring:https://mybatis.org/spring/zh/ (3)MyBatis-Spring-Boot-Starter:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/zh/index.html (4)mybatis-plus:https://baomidou.com/pages/24112f/
Mybatis概述
- Mybatis是什么:MyBatis 是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
Mybatis入门
Mybatis配置说明
Mybatis映射文件
- 官网说明:https://mybatis.net.cn/sqlmap-xml.html
- 文件头
<?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="org.mybatis.example.BlogMapper"> </mapper>
- 映射规则:命名空间namespace + 方法名(id)
- 映射标签:
select、insert、update、delete、sql、cache、cache-ref、resultMap
- select – 映射查询语句。
- insert – 映射插入语句;update 映射更新语句;delete – 映射删除语句;
- resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素;
- id & result:将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段(set注入)
<!-- column:数据库中的列名; property:映射到列结果的字段或属性; javaType:java类型,映射到的是HashMap,应该明确地指定javaType; jdbcType:JDBC类型; typeHandler:类型处理器 --> <id column="bid" property="bid" javaType="Long" jdbcType="BIGINT"/> <result column="blog_name" property="blogName" jdbcType="String" javaType="VARCHAR"/> <result column="blog_desc" property="blogDesc" jdbcType="String" javaType="VARCHAR"/>
- constructor:构造方法。将一个列的值映射到构造方法的参数中(构造器注入)
<!-- idArg & arg:传参 column:数据库中的列名 name:参数名 其他与id&result一致 --> <constructor> <idArg column="bid" javaType="int" name="bid" /> <arg column="blog_name" javaType="String" name="blogName" /> <arg column="blog_desc" javaType="_int" name="blogDesc" /> </constructor>
- association:关联,适用于一个对象中关联另一个对象的情况。
<select id="selectAllOrder" resultMap="orderMap"> SELECT * FROM OrderInfo oi,ShopInfo si WHERE oi.shop_id =si.shop_id </select> <resultMap id="orderMap" type="org.example.alan.Order"> <id column="order_id" property="orderId"/> <result column="order_name" property="orderName"/> <result column="order_desc" property="orderDesc"/> <association property="shopInfo" resultMap="shopMap"/> </resultMap> <resultMap id="shopMap" type="org.example.alan.ShopInfo"> <id column="shop_id" property="shopId"/> <result column="shop_name" property="shopName"/> <result column="shop_desc" property="shopDesc"/> </resultMap>
- collection:集合,与association使用方法一致,适用于一个对象中关联另一个集合对象的情况。
- discriminator:选择器,根据返回的结果选择映射不同的结果集
<select id="selectAllShop" resultMap="shopMap02"> SELECT shop_id,shop_name,shop_desc FROM ShopInfo si </select> <resultMap id="shopMap02" type="org.example.alan.ShopInfo"> <id column="shop_id" property="shopId"/> <discriminator javaType="string" column="shop_id"> <case value="10001"> <result column="shop_name" property="shopName"/> <result column="shop_desc" property="shopDesc"/> </case> <case value="10002"> <result column="shop_name" property="shopDesc"/> <result column="shop_desc" property="shopName"/> </case> </discriminator> </resultMap>
- 自动映射:设置
mapUnderscoreToCamelCase
为true(数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java 属性一般遵循驼峰命名法约定)
- id & result:将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段(set注入)
- sql – 可被其它语句引用的可重用语句块。
- cache – 该命名空间的缓存配置、cache-ref – 引用其它命名空间的缓存配置;
- 默认情况下,只启用了本地的会话缓存,仅对一个会话中的数据进行缓存。 通过配置
<cache/>
启用全局的二级缓存。mybatis一级和二级缓存均存在脏读问题,一般情况下不推荐使用<!-- eviction:清除策略,默认LRU,可选LRU、FIFO、SOFT(软引用)、WEAK(弱引用) flushInterval:刷新间隔,默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新 size:缓存空间,默认值是1024 readOnly:只读,只读的缓存会给所有调用者返回缓存对象的相同实例,不能被修改 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
- 默认情况下,只启用了本地的会话缓存,仅对一个会话中的数据进行缓存。 通过配置
- 传参
- “#{}”用作“参数传递”(使用#{}参数语法时,MyBatis会创建PreparedStatement参数占位符,并通过占位符安全地设置参数,就像使用 ? 一样)。对于可能为NULL的列JDBC规定需要指定JDBC类型
#{middleInitial,jdbcType=VARCHAR}
- “${}”用作“字符串替换”。
select * from ${blog} where bid = #{bid}
(用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击) - 方法中的传参可用@Param("") 如
Blog selectBlog(@Param("bid") long bid);
- “#{}”用作“参数传递”(使用#{}参数语法时,MyBatis会创建PreparedStatement参数占位符,并通过占位符安全地设置参数,就像使用 ? 一样)。对于可能为NULL的列JDBC规定需要指定JDBC类型
MyBatis 动态sql
- 官网说明:https://mybatis.net.cn/dynamic-sql.html
- 涉及标签
if、choose (when, otherwise)、trim (where, set)、foreach
- if:根据条件判断。
- choose、when、otherwise:从多个条件选择一个。
- where:在子元素返回时才插入WHERE语句,若子句的开头为 “AND” 或 “OR”,会将它们去除
- set:在子元素返回时才插入SET语句,并会删掉额外的逗号
- trim:与set元素等价(一般都用set吧?)
- foreach:对集合进行遍历。
- item:集合中元素迭代时的别名
- index:在list和数组中,index是元素的序号,在map中,index是元素的key
- open:foreach代码的开始符号,与close一起使用,常用在in(),values()
- separator:元素之间的分隔符,如in(1,2)
- close: foreach代码的关闭符号,与open一起使用,常用在in(),values()
- collection: 要做foreach的对象,作为入参时,List对象默认用"list"代替作为键,数组对象有"array"代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param("keyName")来设置键,设置keyName后,list,array将会失效。
<!-- 批量条件 传参:List<Long> --> <select id="selectBlogAll" resultType="org.example.alan.Blog"> select * from Blog where bid in <foreach collection="list" item="id" index="" open="(" separator="," close=")"> #{id} </foreach> </select> <!-- 批量insert 传参:List<Blog> --> <insert id="insertAll"> insert into Blog(bid,blog_name,blog_desc) values <foreach collection="list" item="blog" separator=","> (#{blog.bid},#{blog.blog_name},#{blog.blog_desc}) </foreach> </insert>
- script:在映射器接口类上使用动态SQL时使用。
- bind:bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文(不是很懂,也没见用过)
Mybatis缓存
一级缓存
说明:本地缓存,SqlSession级别,默认开启
- 在同一个SqlSession中进行相同的 SQL 语句查询时,第二次以后的查询直接从缓存中获取;
- 在一次数据库会话中,执行修改、删除操作时,一级缓存会失效。等下次再查询sql语句时再加入到一级缓存中
- 会产生脏读情况。 如:当开启两个SqlSession去操作,一个sqlSession查询使一级缓存生效,另一个sqlSession执行修改操作,第一个sqlSession再去查询的时候查到的还是之前的数据。
二级缓存
说明:Mapper级别,配置<cache/>
开启
- 开启后请求会先到二级缓存、再进行一级缓存、再到数据库
- 会产生脏读情况。 如:多表查询的情况(涉及多个namespace)(可通过cache-ref让多个namespace共用一个缓存,但一般不推荐)
Mybatis实现原理
-
Mybatis的实现原理是:JDK动态代理。Mybatis运行时通过JDK动态代理为每个Mapper接口生成代理对象MapperProxy(MapperProxyFactory#newInstance),代理对象会拦截接口方法(invoke),转而执行MappedStatement所代表的sql,然后将sql执行结果转化为Java对象返回(MapperMethod#execute、Statement#execute)
-
主要类说明
-
SqlSessionFactoryBuilder:用来构建SqlSessionFactory,构建完成后即可丢弃
-
SqlSessionFactory:负责管理SqlSession生命周期,创建成功后一直存在
-
SqlSession:每次请求都会开启一次会话,请求结束时需要关闭会话。SqlSession包含操作SQL的所有方法。
-
DefaultSqlSessionFactory:SqlSessionFactory接口的具体实现类,包含Configuration对象
-
XMLConfigBuilder:负责解析mybatis配置文件
-
Configuration:包含Mybatis所有的配置内容(如:环境变量、映射器资源、设置、类型处理器等)
-
MapperRegistry:Mapper操作相关类,负责添加MapperProxyFactory对象,以及获取MapperProxy
-
MapperProxyFactory:Mapper代理工厂类,在加载配置时按资源类型缓存本地,负责创建MapperProxy
-
MapperProxy:Mapper接口的代理对象类,实现InvocationHandler,重写invoke方法。所有调用Mapper接口方法的请求都会转到这里执行
-
MapperMethod:Mapper代理对象方法处理类,负责向SqlSession发起请求并处理响应的结果
-
MappedStatement:Mapped.xml中SQL语句的声明对象
-
StatementHandler:SQL声明对象的处理类
-
Statement:SQL声明对象,负责调用数据库连接池中的SQL声明对象方法,请求数据库驱动连接数据库,执行静态SQL语句并返回它产生的结果。
-
-
基础源码解析:可参考 https://cloud.tencent.com/developer/article/1772213
Mybatis搭配Spring
Spring项目使用Mybatis-Spring集成mybatis,升级为SpringBoot项目后可使用MyBatis-Spring-Boot-Starter进行集成
Mybatis-Spring
- 官网说明:https://mybatis.org/spring/zh/index.html
- 概述:MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
- 关键类说明:
- SqlSessionFactoryBean:代替SqlSessionFactoryBuilder,负责在spring中创建SqlSessionFactory
- SqlSessionFactory:Spring在应用启动时创建。通常在 MyBatis-Spring 中,你不需要直接使用 SqlSessionFactoryBean 或对应的 SqlSessionFactory。 相反,session 的工厂 bean 将会被注入到 MapperFactoryBean 或其它继承于 SqlSessionDaoSupport 的 DAO(Data Access Object,数据访问对象)中。
- SqlSession:bean 可以被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。
- SqlSessionTemplate:MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。 SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。
MyBatis-Spring-Boot-Starter
-
概述:MyBatis-Spring-Boot-Starter可以帮助你更快地在SpringBoot之上构建MyBatis应用。你将通过使用这个模块实现以下目的:(1)构建单体应用程序;(2)将几乎不需要样板配置;(3)使用更少的XML配置。
-
官网说明:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/zh/index.html
-
使用MyBatis-Spring-Boot-Starter的好处:
- 自动探测存在的DataSource
- 将使用 SqlSessionFactoryBean 创建并注册一个 SqlSessionFactory 的实例,并将探测到的 DataSource 作为数据源
- 将创建并注册一个从 SqlSessionFactory 中得到的 SqlSessionTemplate 的实例
- 自动扫描你的 mapper,将它们与 SqlSessionTemplate 相关联,并将它们注册到Spring 的环境(context)中去,这样它们就可以被注入到你的bean中
-
MyBatis-Spring-Boot-Starter安装和入门使用
<!--引入依赖jar包即可--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.1</version> </dependency>
// Mapper接口 @Mapper public interface CityMapper { @Select("SELECT * FROM CITY WHERE state = #{state}") City findByState(@Param("state") String state); } @SpringBootApplication public class SampleMybatisApplication implements CommandLineRunner { //依赖注入Mapper接口 Bean private final CityMapper cityMapper; public SampleMybatisApplication(CityMapper cityMapper) { this.cityMapper = cityMapper; } //Mapper接口的使用 @Override public void run(String... args) throws Exception { System.out.println(this.cityMapper.findByState("CA")); } public static void main(String[] args) { SpringApplication.run(SampleMybatisApplication.class, args); } }
-
发现mapper的方式
- 默认自动搜寻带有 @Mapper 注解的 mapper 接口
- 使用@MapperScan,指定一个自定义的注解或接口来扫描mapper接口
@MapperScan("org.mybatis.spring.sample.mapper")
- xml配置方式:https://mybatis.org/spring/zh/mappers.html#scan
-
SpringBoot中Mybatis的相关配置
- configuration配置项:参考 https://mybatis.org/mybatis-3/zh/configuration.html#settings
- mybatis通用配置项
mybatis: # XML 映射文件的路径 mapper-locations: - classpath:mapper/*.xml # 设置 configuration: # 是否开启驼峰命名自动映射 map-underscore-to-camel-case: true # 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存 cache-enabled: false # 本地缓存机制 SESSION缓存一个会话中执行的所有查询 STATEMENT本地缓存将仅用于执行语句 local-cache-scope: STATEMENT # 指定 MyBatis 所用日志的具体实现,未指定时将自动查找 log-impl: SLF4J
Mybatis-Plus
概述
- 是什么:MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- 特性:无侵入、损耗小、强大的 CRUD 操作、支持Lambda形式调用 等
- 支持的数据库:MySQL,Oracle,DB2,H2、PostgreSQL等
- 框架结构
快速入门
代码生成器(新)
- 适用3.5.1以上版本
- 作用:根据连接的数据源,自动生成数据表对应的模版代码文件,使得能更加方便地使用Mybatis
- 效果
- 自用demo项目:https://gitee.com/jiangfa/mybatis-plus-generator-my-demo.git
Entity类注解说明
- @TableName:表名注解,标识实体类对应的表
- @TableId:主键注解(value:主键字段名;type:指定主键类型,默认IdType.NONE)
- IdType说明
- 可选值:
AUTO-自增、NONE-无状态、INPUT-自动生成、ASSIGN_ID-自定义ID、ASSIGN_UUID-自定义ID
- AUTO:按照数据库中主键设定的自增规则进行自增;
- NONE:无任何操作
- INPUT:使用内置“主键策略”生成主键id,需要注入IKeyGenerator接口配合使用。https://baomidou.com/pages/e131bd/
- ASSIGN_ID:使用“自定义ID生成器”生成主键id,默认使用“雪花算法”,需要注入IdentifierGenerator接口并实现其nextId方法。https://baomidou.com/pages/568eb2/
- ASSIGN_UUID:使用“自定义ID生成器”生成主键id,默认使用“雪花算法+UUID(不含中划线)”,需要注入IdentifierGenerator接口并实现其nextUUID方法
- 可选值:
- 示例
//1. 定义Entity类 @TableName("TEST_TABLE") public class TestEntity { //2. 定义主键,并设置主键生成类型,当设置类型为INPUT、ASSIGN_ID、ASSIGN_UUID时,需要注入对应接口 @TableId(value = "test_id", type = IdType.AUTO) private String testId; } //3. 类型为INPUT @Bean public IKeyGenerator keyGenerator() { return new H2KeyGenerator(); } //3. 类型为ASSIGN_ID @Component public class CustomIdGenerator implements IdentifierGenerator { private final AtomicLong al = new AtomicLong(1); @Override public Number nextId(Object entity) { //可以将当前传入的class全类名来作为bizKey,或者提取参数来生成bizKey进行分布式Id调用生成. String bizKey = entity.getClass().getName(); log.info("bizKey:{}", bizKey); MetaObject metaObject = SystemMetaObject.forObject(entity); String name = (String) metaObject.getValue("name"); final long id = al.getAndAdd(1); log.info("为{}生成主键值->:{}", name, id); return id; } }
- IdType说明
- @TableField:字段注解(非主键)
- @Version:乐观锁注解、标记 @Version 在字段上
- @EnumValue:描述:普通枚举类注解(注解在枚举字段上)
- @TableLogic:表字段逻辑处理注解(逻辑删除)
- @KeySequence:序列主键策略 oracle(value:序列名;dbType:数据库类型,未配置默认使用注入 IKeyGenerator 实现,多个实现必须指定。默认DbType.OTHER)
- @InterceptorIgnore:value值为 1 | yes | on 视为忽略。
@InterceptorIgnore(tenantLine = "1")
- @SqlParser:不知道有啥用
- @OrderBy:内置 SQL 默认指定排序,优先级低于 wrapper 条件查询(isDesc:是否倒序查询,默认true;sort:数字越小越靠前,默认Short.MAX_VALUE)
CRUD 接口
-
Service CRUD接口
- 通用 Service CRUD 封装
IService
接口。如需自定义通用Service方法,请创建自己的IBaseService继承IService - CRUD方法:
save/saveBatch/saveOrUpdate/saveOrUpdateBatch、remove/removeBatchByIds/removeById/removeByIds、update/updateBatchById/updateById、getById/getEntityClass/getObj/getOne/、list/listByIds/listObjs、page/pageMaps、count
//基类 public interface IBaseService<T> extends IService<T> { } //定义接口 public interface IShopService extends IBaseService { }
- 详情参考:https://baomidou.com/pages/49cc81/#service-crud-接口
- 通用 Service CRUD 封装
-
Mapper CRUD接口
- 通用 CRUD 封装
BaseMapper
接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器 - CRUD方法:
insert、delete/deleteById/deleteBatchIds、update/updateById、selectById/selectBatchIds/selectCount/selectList/selectPage/selectOne
public interface BlogMapper<T> extends BaseMapper<T> { }
- 详情参考:https://baomidou.com/pages/49cc81/#mapper-crud-接口
- 通用 CRUD 封装
-
分页查询
- 使用方式
- 引入插件主体MybatisPlusInterceptor和分页插件PaginationInnerInterceptor
- 请求查询传入IPage参数即可
- 示例
//1. 引入插件 @Configuration @MapperScan("org.example.mapper") public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
//2. 调用内置page方法查询 @Override public List<Person> selectPersonPage(SelectPersonReqBo reqBo) { Page page = new Page<>(reqBo.getNum(), reqBo.getSize()); LambdaQueryWrapper<Person> wrapper = new LambdaQueryWrapper<Person>().eq(Person::getPersonSex, reqBo.getPersonSex()); return this.page(page, wrapper).getRecords(); }
//2. 执行自定义SQL查询 //2.1 service @Override public List<Person> selectPersonPage(SelectPersonReqBo reqBo) { return personMapper.pageSelectPerson(new Page<>(reqBo.getNum(), reqBo.getSize()),reqBo.getPersonSex()); } //2.2 mapper List<Person> pageSelectPerson(IPage<Person> page, @Param("personSex") String personSex); //2.3 mapper.xml <select id="pageSelectPerson" resultType="org.example.entity.Person"> SELECT p.person_id ,p.person_name ,p.person_desc,p.person_sex FROM Person p <where> <if test="personSex != null and personSex != '' "> p.person_sex = #{personSex} </if> </where> </select>
- 官网说明:https://baomidou.com/pages/97710a/
- 使用方式
条件构造器
- AbstractWrapper
- 概述:AbstractWrapper是QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类。用于生成 sql 的 where 条件, 包含几乎所有sql操作的相关方法,同时entity 属性也用于生成 sql 的 where 条件。
- 方法:
allEq/eq/ne、gt/ge/lt/le、between/notBetween、like/likeLeft/likeRight/notLike/notLikeLeft/notLikeRight、isNull/isNotNull、in/notIn/inSql/notInSql、groupBy、orderByAsc/orderByDesc/orderBy、having、func、or/and、nested/apply/last、exists/notExists
- 官网说明:https://baomidou.com/pages/10c804/#abstractwrapper
- QueryWrapper
- 概述:继承自AbstractWrapper,用于查询Wrapper,子类LambdaQueryWrapper支持Lambda语言
- 方法:
select
- 官网说明:https://baomidou.com/pages/10c804/#querywrapper
- UpdateWrapper
- 继承自AbstractWrapper,用于修改Wrapper,子类LambdaUpdateWrapper支持Lambda语言
- 方法:
set/setSql
- 官网说明:https://baomidou.com/pages/10c804/#updatewrapper
SimpleQuery 工具类
- 对selectList查询后的结果用Stream流进行了一些封装,使其可以返回一些指定结果,简洁了api的调用。需要项目中已注入对应实体的BaseMapper。
- 静态方法:
keyMap、map、group、list/list2List/list2Map/listGroupBy
- 详情参考:https://baomidou.com/pages/49cc81/#操作步骤
- 测试demo:https://gitee.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/toolkit/SimpleQueryTest.java