Mybatis-Plus

入门

简介

Mybatis-Plus(简称:MP),是一个Mybatis的增强工具,在Mybatis的基础上只做增强,不做改
变,为简化开发、提高效率而生

特性

无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分
CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配
置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大
的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、
Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同
于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、
Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出
慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误
操作

代码生成器

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、
Mapper、 XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

  @Test
    public void generator(){
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/drj",
                        "root", "xxxx")
                .globalConfig(builder -> {
                    builder.author("dairuijie") // 设置作者
                            .enableSwagger() // 开启 swagger 模式// .fileOverride() // 覆盖已生成文件,默认值:false
                            .disableOpenDir() //禁止打开输出目录,默认值:true
                            .dateType(DateType.ONLY_DATE) //定义生成的实体 类中日期类型 时间策略 DateType.ONLY_DATE 默认值: DateType.TIME_PACK
                            .outputDir("D:\\mpdemo" +
                                    "/src/main/java"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.chonger.eg") // 设置父包名
                            .moduleName("user3") // 设置父包模块名表名注解,应用于类上

                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml,
                                    "D:\\mpdemo" + "/src/main/resources/mapper")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("ym_user") // 设置需要生成的表名
                            .addTablePrefix("ym_"); // 设置过滤表前缀
                    builder.entityBuilder()
                            .naming(NamingStrategy.underline_to_camel)//  数据库表映射到实体的命名策略.默认下划线转驼峰命名

                            .columnNaming(NamingStrategy.underline_to_camel)//数据库表字段映射到实体的命名策略,默认为 null,未指定按照 naming 执行
                            .addTableFills(new Column("createTime",
                                    FieldFill.INSERT))
                            .addTableFills(new Property("updateTime",
                                    FieldFill.INSERT_UPDATE))
                            .idType(IdType.AUTO) //主键策略
                            .enableLombok();//开启 lombok 模型,默认 值:false
                    builder.controllerBuilder()
                            .enableHyphenStyle()//开启驼峰转连字符,默认 值:false
                            .enableRestStyle();//开启生成@RestController 控制器,默认值:false
                    builder.serviceBuilder()
                            .formatServiceImplFileName("%sServiceImp")// 格式化 service 实现类文件名称
                            .formatServiceFileName("%sService");//格式化 service 接口文件名称,去掉Service接口的首字母I
                })
                .strategyConfig(builder -> {
                })
// .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

注解配置

TableName

value: 表名,默认为“”
schema:schema,默认为“”
keepGlobalPrefix:是否保持使用全局的tablePrefix值(如果设置了全局tablePrefix且自行设置
了value的值),默认为false
resultMap:xml中resultMap的id,默认为“”
autoResultMap:是否自动构建 resultMap 并使用,默认为false
excludeProperty:需要排除的属性名
以上都为非必输项。

TableId

主键注解,应用于主键字段上

value:主键字段名,默认为“”
type:主键类型,默认为IdType.NONE
以上都为非必输项。
IdType:
AUTO:数据库ID自增
NONE:无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT:insert前自行set主键值
ASSIGN_ID:分配ID,主键类型为Number(Long和Integer)或String,版本
3.3.0以上,使用接口
IdentifierGenerator的方法nextId(默认实现类为
DefaultIdentifierGenerator 雪花算法)
ASSIGN_UUID:分配UUID,主键类型为String,版本3.3.0以上,使用接口
IdentifierGenerator的方法nextUUID
以下已过时,不建议使用:
ID_WORKER:分布式全局唯一ID 长整型类型,使用ASSIGN_ID代替
UUID:32位UUID字符串,使用ASSIGN_UUID代替
ID_WORKER_STR:分布式全局唯一ID 字符串类型,使用ASSIGN_ID代替

TableField

字段注解(非主键)

value:数据库字段名
el:映射为原生#{}逻辑,相当于写在xml中的#{}部分
exist:是否为数据库表字段,默认为true
condition:where查询比较条件,如果设置了值,则获取设置的值,如果没有则默认为全局的-
->%s=#{%s},%s会填充为字段名
参考:[链接](https://github.com/baomidou/mybatis-plus/blob/3.0/mybatis- plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/SqlConditio n.java)
update:字段update set部分注入,例如:字段名 = ‘%s+1’,表示更新时,会set
version=version+1
该属性优先级高于el。
insertStrategy:字段验证策略之 insert: 当insert操作时,该字段拼接insert语句时的
策略
IGNORED: 直接拼接
NOT_NULL: 判断是否为空NULL后,在决定是否拼接
NOT_EMPTY: 判断是否为NULL和‘’后,在决定是否拼接
DEFAULT:默认的,一般只用于注解里,在全局里代表 NOT_NULL,在注解里代表 跟
随全局
eg:
insert into table (字段名)
values (#{字段名})
updateStrategy:字段验证策略之 update: 当更新操作时,该字段拼接set语句时的策略
IGNORED: 直接拼接
NOT_NULL:判断是否为空NULL后,在决定是否拼接
NOT_EMPTY:判断是否为NULL和‘’后,在决定是否拼接
DEFAULT:默认的,一般只用于注解里,在全局里代表 NOT_NULL,在注解里代表 跟
随全局
whereStrategy:段验证策略之 where: 表示该字段在拼接where条件时的策略
IGNORED: 直接拼接
NOT_NULL:判断是否为空NULL后,在决定是否拼接
NOT_EMPTY:判断是否为NULL和‘’后,在决定是否拼接
DEFAULT:默认的,一般只用于注解里,在全局里代表 NOT_NULL,在注解里代表 跟
随全局
fill:字段自动填充策略,在对应模式下将会忽略 insertStrategy 或 updateStrategy
的配置,等于断言该字段必有值
FieldFill:
DEFAULT:默认不处理
INSERT:插入时填充字段
UPDATE:更新时填充字段
INSERT_UPDATE:插入和更新时填充字段
select:是否进行 select 查询,值大字段可设置为 false 不加入 select 查询范围,提
高响应效率
keepGlobalFormat:是否保持使用全局的 columnFormat 的值
只生效于既设置了全局的 columnFormat 也设置了上面vaule的值,如果是 false
, 全局的 columnFormat 不生效
jdbcType:JDBC类型,只生效于 mp 自动注入的 method,一般和autoResultMap一起使用
typeHandler:类型处理器,只生效于 mp 自动注入的 method,一般和autoResultMap一起
使用
numericScale:指定小数点后保留的位数,只生效于 mp 自动注入的 method,一般和
autoResultMap一起使用

Version
  • 乐观锁注解、在对应的字段上,使用@version注解字段
EnumValue
  • 通枚举类注解(注解在枚举字段上)
TableLogic
  • 表字段逻辑处理注解
  • 逻辑删除字段上
value:未删除的值
delval:已删除的值
  • 一般在3.3.0版本以上,此注解建议过时,可直接在springboot的配置文件中,一次性指定逻辑字段、未删除的值和已删除的值
# 逻辑删除
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
mybatis-plus.global-config.db-config.logic-delete-field= 逻辑删除的字段名
# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value=1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value=0
KeySequence主键
  • 序列主键策略,作用于类上

value:序列名
dbType:数据库类型,未配置默认使用注入 IKeyGenerator 实现,多个实现必须指定

OrderBy
  • 内置 SQL 默认指定排序
  • 优先级低于 wrapper 条件查询

~~isDesc:是否倒序查询,默认true,false为正序 ~~
ase:默认false
sort:数字越小越靠前,默认Short.MAX_VALUE

API功能

说明

  • 一般简单的CRUD操作,我们可以直接使用mapper接口进行操作,无需写xml和service
  • 但是在常规企业研发中,一般我们会涉及很多复杂的sql和业务,以及规范要求,不写xml和service则无法满足所以,一般我们会进行封装IService接口,进一步封装CRUD接口,采用更符合规范的命名来进行API书写,避免和mapper接口混淆
  • 同样,也有对应的 T 泛型,为任意实体对象
  • 创建自己的IService继承Mybatis-plus提供的基类
  • 对象Wrapper为条件构造器

IService层API

save

// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量,一次性插入)
boolean saveBatch(Collection<T> entityList);//执行1条sql,将数据全部一次性插入
// 插入(批量,batchSize:插入批次数量,默认批次提交数量=1000)
boolean saveBatch(Collection<T> entityList, int batchSize);//可查看控制台日志,
例如batchSize=2,则执行了2条sql

saveOrUpdate

// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);//先执行update,如
果为0,则执行insert
// 批量修改插入,没执行一条数据,都先SELECT,然后update或者insert
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入,batchSize:插入批次数量,默认批次提交数量=1000
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

Remove

// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据实体(ID)删除
boolean removeById(T entity);
// 根据 columnMap 条件,表字段 map 对象,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList)

Update

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新,1条sql执行
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新,batchSize条sql执行
boolean updateBatchById(Collection<T> entityList, int batchSize);

Get

// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,取一条加上限制条件
wrapper.last("LIMIT 1")
// 随机取则加:order by rand() limit 1
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录,有多个 result 是否抛出异常,如果为false则输出多个记
录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V>
mapper);

List

// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object,
V> mapper);

Page

// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T>
queryWrapper);

Count

// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);

Chain

// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list()


// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);

Mapper层API

insert

// 插入一条记录
int insert(T entity);

delete

// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

Select

// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T>
queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T>
queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page,
@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

Update

// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER)
Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);

条件表达构造器

  • boolean condition:表示该条件是否加入最后生成的sql中
  • 不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输。wrapper 很重,正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
  • QueryWrapper和UpdateWrapper都是用于生成sql的where条件,包含Lambda

AbstractWrapper

![image.png](https://img-blog.csdnimg.cn/img_convert/8fbfef383e8d7d452ec1957db5660533.png#clientId=ua4243f61-cf92-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=445&id=u6add83ff&margin=[object Object]&name=image.png&originHeight=890&originWidth=685&originalType=binary&ratio=1&rotation=0&showTitle=false&size=93211&status=done&style=none&taskId=ua70cd60f-02ed-48d2-b162-0633b0b9165&title=&width=342.5)

QueryWrapper

  • 继承自 AbstractWrapper
  • 自身的内部属性 entity 也用于生成 where 条件
  • select:设置查询字段

UpdateWrapper

  • 继承自 AbstractWrapper
  • 自身的内部属性 entity 也用于生成 where 条件
  • set:设置更新字段
  • setsql:设置sq

![Wrapper.png](https://img-blog.csdnimg.cn/img_convert/393e1bf1fe7972415a7d5080b47fb800.png#clientId=ua4243f61-cf92-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=uaf4f6f71&margin=[object Object]&name=Wrapper.png&originHeight=640&originWidth=1166&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23670&status=done&style=none&taskId=u590594c9-3f44-47f9-a418-cc030d3a07c&title=)

其它功能

数据审计对比

// 1,异步回调,注意 @EnableAsync 开启异步
 applicationEventPublisher.publishEvent(new DataAuditEvent((t) -> {
    List<Change> changes = t.apply(newVersion, oldVersion);
    for (Change valueChange : changes) {
        ValueChange change = (ValueChange) valueChange;
        System.err.println(String.format("%s不匹配,期望值 %s 实际值 %s", change.getPropertyName(), change.getLeft(), change.getRight()));
    }
}));

// 2,手动调用对比
DataAuditor.compare(obj1, obj2);

敏感词过滤
 @Bean
    public IParamsProcessor paramsProcessor() {
        return new SensitiveWordsProcessor() {

            /**
             // 可以指定你需要拦截处理的请求地址,默认 /* 所有请求
             @Override public Collection<String> getUrlPatterns() {
             return super.getUrlPatterns();
             }
             */

            @Override
            public List<String> loadSensitiveWords() {
                // 这里的敏感词可以从数据库中读取,也可以本文方式获取,加载只会执行一次
                return sensitiveWordsMapper.selectList(Wrappers.<SensitiveWords>lambdaQuery().select(SensitiveWords::getWord))
                        .stream().map(t -> t.getWord()).collect(Collectors.toList());
            }

            @Override
            public String handle(String fieldName, String fieldValue, Collection<Emit> emits) {
                if (CollectionUtils.isNotEmpty(emits)) {
                    try {
                        // 这里可以过滤直接删除敏感词,也可以返回错误,提示界面删除敏感词
                        System.err.println("发现敏感词(" + fieldName + " = " + fieldValue + ")" +
                                "存在敏感词:" + toJson(emits));
                        String fv = fieldValue;
                        for (Emit emit : emits) {
                            fv = fv.replaceAll(emit.getKeyword(), "");
                        }
                        return fv;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return fieldValue;
            }
        };
    }
脱敏
 public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (null == SENSITIVE_STRATEGY) {
            throw new RuntimeException("You used the annotation `@FieldSensitive` but did not inject `SensitiveStrategy`");
        } else {
            gen.writeObject(Objects.equals("1", RequestDataTransfer.get("skip_sensitive")) ? value : SENSITIVE_STRATEGY.handle(this.type, value));
        }
    }

    public JsonSerializer<?> getJsonSerializer(SerializerProvider provider, BeanProperty property) throws JsonMappingException {
        if (Objects.equals(property.getType().getRawClass(), String.class)) {
            FieldSensitive sensitiveInfo = (FieldSensitive)this.getAnnotation(property, FieldSensitive.class);
            if (null != sensitiveInfo) {
                return new SensitiveSerializer(sensitiveInfo.value());
            }
        }

        return provider.findValueSerializer(property.getType(), property);
    }
乐观锁
  • OptimisticLockerInnerInterceptor
  • 当要更新一条记录的时候,希望这条记录没有被别人更新,就会用到锁
  • 乐观锁实现方式

取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败

 @Bean
  public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor mybatisPlusInterceptor = new
MybatisPlusInterceptor();
    mybatisPlusInterceptor.addInnerInterceptor(new
OptimisticLockerInnerInterceptor());
    return mybatisPlusInterceptor;
 }
字段加密解密
 public static void O000000o(IEncryptor var0, IDataBind var1, EncryptorProperties var2, MetaObject var3, FieldSetProperty var4) {
        String var5 = var4.getFieldName();
        Object var6 = var3.getValue(var5);
        if (null != var6) {
            if (null != var0 && var6 instanceof String) {
                try {
                    FieldEncrypt var7 = var4.getFieldEncrypt();
                    if (null != var7) {
                        var6 = O0000oo0.O000000o(var0, var7.encryptor()).decrypt(var2.algorithm(var7), var2.password(var7), var2.getPrivateKey(), (String)var6, var3);
                    }
                } catch (Exception var9) {
                    log.error("field decrypt", var9.getMessage());
                }
            }

            boolean var10 = true;
            if (null != var1) {
                FieldBind var8 = var4.getFieldDict();
                if (null != var8) {
                    var10 = false;
                    var1.setMetaObject(var8, var6, var3);
                }
            }

            if (var10) {
                var3.setValue(var5, var6);
            }
        }

    }
读写分离
public List<String> getDatasourceKeys(SqlCommandType var1, String var2) {
        if (null != this.slaveKeys && var1 == SqlCommandType.SELECT) {
            return this.slaveKeys;
        } else if (null == this.masterKeys) {
            throw new O000000o("database group: [ " + var2 + " ] master not available");
        } else {
            return this.masterKeys;
        }
    }
多数据源事务处理
排除非表字段

使用 transient 修饰(如果实体对象有父类,想排除父类字段:)

private transient String userName; 

使用 static 修饰

private static String userName; 

使用 TableField 注解

@TableField(exist=false)
private String userName;

源码分析

在这里插入图片描述

MyBatis

 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
    
    
    
   
  
  root:
  <configuration>
    <properties resource="db.properties"/>    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>            
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>                
                <property name="url" value="${jdbc.url}"/>                
                <property name="username" value="${jdbc.username}"/>                
                <property name="password" value="${jdbc.password}"/>                
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/User.xml"/>        
    </mappers>
</configuration>

   
解析mybatis-config.xml
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  
  
 private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
获取数据源
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
          break;
        }
      }
    }
  }
  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

在这里插入图片描述

获取执行sql
 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
          //案例
            ErrorContext.instance().resource(resource);
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
  
    public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
     //获取sql
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
  
  
  
  
  

在这里插入图片描述

如何执行sql
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   // 获取到执行sql #{name} 转为?
   BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
    
    @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
  
   @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
  

  
  
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  
  
  
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  
    @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }
  
  
  
  // 返回封装的数据结构
    @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }
  
  
    private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -1) {
          // no more results. Must be no resultset
          break;
        }
      }
    }
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }
  
  	 //获取mysql查询数据的getMetaData  所有字段类型、名称分别获取
    public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
    }
  }
  
  // 真正获取值赋值到实体对象
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

在这里插入图片描述

流程图

在这里插入图片描述

MyBatisPlus 构造SqlSessionFactory

MybatisXMLConfigBuilder 解析
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
            //TODO 这里换成 MybatisXMLConfigBuilder 而不是 XMLConfigBuilder
            MybatisXMLConfigBuilder parser = new MybatisXMLConfigBuilder(reader, environment, properties);
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                reader.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }
    
     public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

 private void parseConfiguration(XNode root) {
        try {
            // issue #117 read properties first
            propertiesElement(root.evalNode("properties"));
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            typeAliasesElement(root.evalNode("typeAliases"));
            pluginElement(root.evalNode("plugins"));
            objectFactoryElement(root.evalNode("objectFactory"));
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            environmentsElement(root.evalNode("environments"));
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            typeHandlerElement(root.evalNode("typeHandlers"));
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
    
      private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                if ("package".equals(child.getName())) {
                    String mapperPackage = child.getStringAttribute("name");
                    
                    configuration.addMappers(mapperPackage);
                } else {
                    String resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                            mapperParser.parse();
                        }
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        try(InputStream inputStream = Resources.getUrlAsStream(url)){
                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                            mapperParser.parse();
                        }
                    } else if (resource == null && url == null && mapperClass != null) {
                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        configuration.addMapper(mapperInterface);
                    } else {
                        throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                    }
                }
            }
        }
    }
MybatisConfiguration addMappers

在这里插入图片描述

    /**
     * 使用自己的 MybatisMapperRegistry
     */
    @Override
    public void addMappers(String packageName) {
        mybatisMapperRegistry.addMappers(packageName);
    }
    
     @Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // TODO 如果之前注入 直接返回
                return;
                // TODO 这里就不抛异常了
//                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
                knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.
                // TODO 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    
    
    
   
MybatisMapperAnnotationBuilder 解析
  @Override
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            loadXmlResource();
            configuration.addLoadedResource(resource);
            String mapperName = type.getName();
            assistant.setCurrentNamespace(mapperName);
            parseCache();
            parseCacheRef();
            InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(type);
            for (Method method : type.getMethods()) {
                if (!canHaveStatement(method)) {
                    continue;
                }
                if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                    && method.getAnnotation(ResultMap.class) == null) {
                    parseResultMap(method);
                }
                try {
                    // TODO 加入 注解过滤缓存
                    InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
                    parseStatement(method);
                } catch (IncompleteElementException e) {
                    // TODO 使用 MybatisMethodResolver 而不是 MethodResolver
                    configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
                }
            }
            // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql
            try {
               
                if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                    parserInjector();
                }
            } catch (IncompleteElementException e) {
               // 添加至尚未完成处理的方法集合中
                configuration.addIncompleteMethod(new InjectorResolver(this));
            }
        }
        //补偿之前未完成解析操作的方法
        parsePendingMethods();
    }

    void parserInjector() {
        GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
    }
    
    
    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);
        if (modelClass != null) {
            String className = mapperClass.toString();
            Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
            if (!mapperRegistryCache.contains(className)) {
                TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);
                if (CollectionUtils.isNotEmpty(methodList)) {
                    // 循环注入自定义方法
                    methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
                } else {
                    logger.debug(mapperClass.toString() + ", No effective injection method was found.");
                }
                mapperRegistryCache.add(className);
            }
        }
    }
    
        /**
     * 注入自定义方法
     */
    public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        this.configuration = builderAssistant.getConfiguration();
        this.builderAssistant = builderAssistant;
        this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        /* 注入自定义方法 */
        injectMappedStatement(mapperClass, modelClass, tableInfo);
    }
    
    public class SelectById extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
        SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),
            sqlSelectColumns(tableInfo, false),
            tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
            tableInfo.getLogicDeleteSql(true, true)), Object.class);
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
    }
    
        /**
     * 添加 MappedStatement 到 Mybatis 容器 这里和Mybatis一致
     */
    protected MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource,
                                                 SqlCommandType sqlCommandType, Class<?> parameterType,
                                                 String resultMap, Class<?> resultType, KeyGenerator keyGenerator,
                                                 String keyProperty, String keyColumn) {
        String statementName = mapperClass.getName() + DOT + id;
        if (hasMappedStatement(statementName)) {
            logger.warn(LEFT_SQ_BRACKET + statementName + "] Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET);
            return null;
        }
        /* 缓存逻辑处理 */
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType,
            null, null, null, parameterType, resultMap, resultType,
            null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn,
            configuration.getDatabaseId(), languageDriver, null);
    }

在这里插入图片描述

MapperBuilderAssistant addMappedStatement

在这里插入图片描述

构造SqlSessionFactory
   @Override
    public SqlSessionFactory build(Configuration configuration) {
        GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);

        final IdentifierGenerator identifierGenerator;
        if (null == globalConfig.getIdentifierGenerator()) {
        // 主键生成器
            identifierGenerator = new DefaultIdentifierGenerator();
            globalConfig.setIdentifierGenerator(identifierGenerator);
        } else {
            identifierGenerator = globalConfig.getIdentifierGenerator();
        }
        IdWorker.setIdentifierGenerator(identifierGenerator);
        // MP默认插入一些动态方法的xml 脚本方法。
        if (globalConfig.isEnableSqlRunner()) {
            new SqlRunnerInjector().inject(configuration);
        }

        SqlSessionFactory sqlSessionFactory = super.build(configuration);

        // 缓存 sqlSessionFactory
        globalConfig.setSqlSessionFactory(sqlSessionFactory);

        return sqlSessionFactory;
    }

后期查询根据代理类Mapper 获取sql 、执行sql 和Mybatis一致

流程图

在这里插入图片描述

参考:

https://mybatis.org/mybatis-3/zh/configuration.html
https://baomidou.com/

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蜗牛乌龟一起走

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值