mybatisSQL注入,mybatis plus 注解
00-1010前言案例测试原理分析
00-1010 MyBatis-Plus是MyBatis的增强工具。基于MyBatis,只做增强不做改动,为简化开发提高效率而生。
那么MyBatis-Plus是如何强化的呢?其实有些crud方法是封装的,开发者不用再写SQL了,通过间接调用方法就可以得到封装的SQL语句。
特点:
非侵入性:只增强不改变,它的引入不会影响已有项目,而且如丝般平滑,损失很小:启动时会自动注入基本的凝乳,性能基本无损。强大的直接面向对象操作的CRUD操作:内置了universal Mapper和universal Service,只需要少量的配置就可以实现单个表的大部分CRUD操作,更强大的条件构造函数。满足各种使用需求,支持Lambda形式化调用:通过Lambda表达式方便地编写各种查询条件,无需担心字段写错,支持主键自动生成,最多支持四种主键策略(包括分布式唯一ID生成器-序列),可自由配置。主键问题的完美解决方案支持ActiveRecord模式:支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的CRUD操作支持自定义全局通用操作:支持全局通用方法注入(一次编写,随处使用)内置代码生成器:代码或Maven插件可快速生成Mapper、Model、Service、Controller层代码,支持模板引擎,拥有众多自定义配置。可以使用内置的分页插件:基于MyBatis物理分页,开发者不需要关心具体的操作。配置好插件后,分页相当于普通的列表查询分页插件支持多种数据库:MySql、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer等数据库。它可以输出SQL语句及其执行时间。建议在开发和测试时启用该功能。内置可快速找出慢查询的全局拦截插件:对所有表的删除和更新操作提供智能分析和拦截的数据库,还可以自定义拦截规则防止误操作:
MySQL、Oracle、db2、PostgreSQL、SqlServer等。
00-1010让我们从一个简单的演示开始,感受一下MyBatis-plus的便利性。
MP封装的BaseMapper接口
Interface basemappert扩展map pert {/* * * Insert a record * * @ param entity entity object */int Insert(tenentity);/* * *根据实体条件删除记录* * @param wrapper实体对象封装操作类(可以为空)*/int delete(@ param(constants . wrapper)wrapper包装器);/* * *根据whereEntity条件更新记录* * @param entity实体对象(设置条件值,可以为null) * @param updateWrapper实体对象封装操作类(可以为null,里面的实体用于生成where语句)*/int update(@ param(constants . entity)tenetey,@ param(constants . wrapper)wrapper更新wrapper);/* * *按ID查询* * @param id主键ID */T selectById(可序列化ID);}实体类对象
/* * *实体类* * @ author chil */@ data @ tablename( user )@ equalsandhashcode(call super=true)公共类user扩展tenantety { private static final longserialversionuid=1l;/* * *用户号*/私有字符串代码;/* * * account */私有字符串帐户;/* * * password */私有字符串密码;/* * *昵称*/私有字符串名;}用户映射程序继承了BaseMapper接口
/** *映射器
接口 * * @author Chill */public interface UserMapper extends BaseMapper<User> {}
测试
@Overridepublic User getById(String id){User user = userMapper.selectById(id);return null;}
最终查询的 SQL 语句如下图:
从打印的日志我们可以知道,MyBatis-Plus 最终为我们自动生成了 SQL 语句。根据上述操作分析:UserMapper 继承了 BaseMapper,拥有了 selectById 的方法,但是 MyBatis-Plus 是基于 mybatis 的增强版,关键在于最终仍然需要提供具体的SQL语句,来进行数据库操作。
下面我们 DEBUG 跟踪 MyBatis-Plus 是如何生成业务 sql 以及自动注入的,如下图所示:
发现 SQL 语句在 MappedStatement 对象中,而 sqlSource 存的就是相关的 SQL 语句,基于上面的分析,我们想要知道 SQL 语句是什么时候获取到的,就是要找到 mappedStatement 被添加的位置。追踪到 AbstractMethod 的抽象方法中。
原理解析
Mybatis-Plus 在启动后会将 BaseMapper 中的一系列的方法注册到 meppedStatements 中,那么究竟是如何注入的呢?下面我们一起来分析下。
在 Mybatis-Plus 中,ISqlInjector 负责 SQL 的注入工作,它是一个接口,AbstractSqlInjector 是它的实现类,SqlInjector SQL 自动注入器接口的相关 UML 图如下:
找到了下面我们所讲到的都基于这几个类实现,接着上一个问题,追踪到 AbstractMethod 的抽象方法中,
下面我们继续 DEBUG 跟踪代码是怎么注入的。
首先跳进来 AbstractSqlInjector 抽象类执行 inspectInject 方法
@Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { Class<?> modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { //获取 CRUD 实现类列表 List<AbstractMethod> methodList = this.getMethodList(mapperClass); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循环注入自定义方法,这里开始注入 sql methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } }
在这里我们找到 inject 方法,跳进去
在跳进去 injectMappedStatement 方法,选择你执行的 CRUD 操作,我这里以 slectById 为例
从这里我们找到了 addMappedStatement() 方法,可以看到,生成了 SqlSource 对象,再将 SQL 通过 addSelectMappedStatement 方法添加到 meppedStatements 中。
那么实现类是怎么获取到的呢?
在 AbstractSqlInjector 抽象类 inspectInject 方法从 this.getMethodList 方法获取,如下图:
这里的 getMethodList 方法获取 CRUD 实现类列表
/** * SQL 默认注入器 * * @author hubin * @since 2018-04-10 */public class DefaultSqlInjector extends AbstractSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { return Stream.of( new Insert(), new Delete(), new DeleteByMap(), new DeleteById(), new DeleteBatchByIds(), new Update(), new UpdateById(), new SelectById(), new SelectBatchByIds(), new SelectByMap(), new SelectOne(), new SelectCount(), new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage() ).collect(toList()); }}
从上面的源码可知,项目启动时,首先由默认注入器生成基础 CRUD 实现类对象,其次遍历实现类列表,依次注入各自的模板 SQL,最后将其添加至 mappedstatement。
那么 SQL 语句是怎么生成的?此时 SqlSource 通过解析 SQL 模板、以及传入的表信息和主键信息构建出了 SQL 语句,如下所示:
@Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { /** 定义 mybatis xml method id, 对应 <id="xyz"> **/ SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID; /** 构造 id 对应的具体 xml 片段 **/ SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true)), Object.class); /** 将 xml method 方法添加到 mybatis 的 MappedStatement 中 **/ return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo); }
那么数据库表信息是如何获取的?主要根据AbstractSqlInjector抽象类的 inspectInject 方法中的initTableInfo方法获取,如下图:
/** * <p> * 实体类反射获取表信息【初始化】 * </p> * * @param clazz 反射实体类 * @return 数据库表反射信息 */ public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) { TableInfo tableInfo = TABLE_INFO_CACHE.get(clazz); if (tableInfo != null) { if (builderAssistant != null) { tableInfo.setConfiguration(builderAssistant.getConfiguration()); } return tableInfo; } /* 没有获取到缓存信息,则初始化 */ tableInfo = new TableInfo(clazz); GlobalConfig globalConfig; if (null != builderAssistant) { tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace()); tableInfo.setConfiguration(builderAssistant.getConfiguration()); globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration()); } else { // 兼容测试场景 globalConfig = GlobalConfigUtils.defaults(); } /* 初始化表名相关 */ final String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo); List<String> excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList(); /* 初始化字段相关 */ initTableFields(clazz, globalConfig, tableInfo, excludePropertyList); /* 放入缓存 */ TABLE_INFO_CACHE.put(clazz, tableInfo); /* 缓存 lambda */ LambdaUtils.installCache(tableInfo); /* 自动构建 resultMap */ tableInfo.initResultMapIfNeed(); return tableInfo; }
分析 initTableName() 方法,获取表名信息源码中传入了实体类信息 class,其实就是通过实体上的@TableName 注解拿到了表名。
我们在定义实体类的同时,指定了该实体类对应的表名。
那么获取到表名之后怎么获取主键及其他字段信息呢?主要根据AbstractSqlInjector抽象类的 inspectInject 方法中的initTableFields方法获取,如下图:
/** * <p> * 初始化 表主键,表字段 * </p> * * @param clazz 实体类 * @param globalConfig 全局配置 * @param tableInfo 数据库表反射信息 */ public static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) { /* 数据库全局配置 */ GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig(); ReflectorFactory reflectorFactory = tableInfo.getConfiguration().getReflectorFactory(); //TODO @咩咩 有空一起来撸完这反射模块. Reflector reflector = reflectorFactory.findForClass(clazz); List<Field> list = getAllFields(clazz); // 标记是否读取到主键 boolean isReadPK = false; // 是否存在 @TableId 注解 boolean existTableId = isExistTableId(list); List<TableFieldInfo> fieldList = new ArrayList<>(list.size()); for (Field field : list) { if (excludeProperty.contains(field.getName())) { continue; } /* 主键ID 初始化 */ if (existTableId) { TableId tableId = field.getAnnotation(TableId.class); if (tableId != null) { if (isReadPK) { throw ExceptionUtils.mpe("@TableId cant more than one in Class: "%s".", clazz.getName()); } else { isReadPK = initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId, reflector); continue; } } } else if (!isReadPK) { isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field, reflector); if (isReadPK) { continue; } } /* 有 @TableField 注解的字段初始化 */ if (initTableFieldWithAnnotation(dbConfig, tableInfo, fieldList, field)) { continue; } /* 无 @TableField 注解的字段初始化 */ fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field)); } /* 检查逻辑删除字段只能有最多一个 */ Assert.isTrue(fieldList.parallelStream().filter(TableFieldInfo::isLogicDelete).count() < 2L, String.format("@TableLogic cant more than one in Class: "%s".", clazz.getName())); /* 字段列表,不可变集合 */ tableInfo.setFieldList(Collections.unmodifiableList(fieldList)); /* 未发现主键注解,提示警告信息 */ if (!isReadPK) { logger.warn(String.format("Can not find table primary key in Class: "%s".", clazz.getName())); } }
到处我们知道 SQL 语句是怎么注入的了,如果想要更加深入了解的小伙伴,可以自己根据上面的源码方法深入去了解。
到此这篇关于Mybatis-Plus注入SQL原理分析的文章就介绍到这了,更多相关Mybatis-Plus注入SQL内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。