入门
简介
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/