工作中一般都是使用MyBatis-Plus来代替Mybatis来进行对数据库的操作,也一直都想了解MyBatis-Plus和Mybatis的区别。最近结合工作中使用到的MyBatis-Plus功能,简单的看了一次MyBatis-Plus的源码,整理下写个文档,方便不久的将来忘记了,可以重新复习下。
下文描述的是SpringBoot如何把Mybatis-Plus整合到Spring中,以及在我们项目中经常使用到的Mybatis-Plus具体是怎么实现的。
@MapperScan
@MapperScan的作用可以理解为@Import注解 + @MapperScan中配置的包名
@EnableAsync
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@EnableFeignClients({"com.***.***.common.proxy.api"})
@EnableApolloConfig("application")
@MapperScan("com.***.***.backend.**.dao")
public class SopBackendApplication {
public static void main(String[] args) { SpringApplication.run(SopBackendApplication.class);
}
}
SpringBoot启动时会记录启动类上的注解信息,然后Spring会通过ConfigurationClassPostProcessor解析对应的注解bean,当获取到@MapperScan注解中有@Import注解,然后会通过ConfigurationClassPostProcessor类把@Import中的MapperScannerRegistrar加入到Spring的启动逻辑中,因为MapperScannerRegistrar实现了Spring的ImportBeanDefinitionRegistrar接口,所以会被调用到registerBeanDefinitions()方法,而registerBeanDefinitions()方法的逻辑会判断类上面是否有@MapperScan注解,有的话就会通过MapperScannerConfigurer扫描@MapperScan配置的目录,通过ClassPathMapperScanner类扫描@Mapperscan配置目录下的所有加了@Repository注解的mapper,并生成对应的beandefinition信息保存到spring容器中,其中这些beandefinition的beanclass被设置成了MapperFactoryBean,而MapperFactoryBean实现了spring的Facyorybean接口,当spring通过beandefinitionmap创建bean的过程中,会调用MapperFactoryBean的getObject()方法生成xxxMapper接口的代理对象MybayisMapperProxy。到此Mapper接口就可以有代理对象被调用了。
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
//设置beanclass为MappeFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
xxxMapper.xml
MybatisPlusAutoConfiguration被spring解释的时候,通过调用sqlSessionFactory()方法,加载默认路径下mapper.xml文件,保存到MappedStatement中,包括查询类型,sql语句,参数信息等,通过namespace和mapper对应上。
@Data
@Accessors(chain = true)
@ConfigurationProperties(prefix = Constants.MYBATIS_PLUS)
public class MybatisPlusProperties {
private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};
public Resource[] resolveMapperLocations() {
return Stream.of(Optional.ofNullable(this.mapperLocations).orElse(new String[0]))
.flatMap(location -> Stream.of(getResources(location)))
.toArray(Resource[]::new);
}
private Resource[] getResources(String location) {
try {
return resourceResolver.getResources(location);
} catch (IOException e) {
return new Resource[0];
}
}
public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//解析xml文件,生成MapperStatement
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
}
}
BaseMapper<T>
BaseMapper,继承了该接口后,无需编写mapper.xml文件,即可以获得CRUD功能,那么MyBatis-Plus又是如何做到无需配置mapper.xml就能获得CRUD功能呢?答案就是通过sql注入器AbstractSqlInjecor自己生成sql。
@TableName
在AbstractSqlInject中inspectInject方法会获取BaseMapper<T>中的泛型类型,然后获取Entity上@tableName配置的表名以及@TableField注解字段的信息。
public enum SqlMethod {
/**
* 插入
*/
INSERT_ONE("insert", "插入一条数据(选择字段插入)", "<script>\nINSERT INTO %s %s VALUES %s\n</script>"),
public abstract class AbstractSqlInjector implements ISqlInjector {
private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class);
@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
// 获取BaseMapper中的泛型类型,,通过TableName获取mapper关联的是哪个表
Class<?> modelClass = extractModelClass(mapperClass);
if (modelClass != null) {
String className = mapperClass.toString();
Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
if (!mapperRegistryCache.contains(className)) {
List<AbstractMethod> methodList = this.getMethodList(mapperClass);
if (CollectionUtils.isNotEmpty(methodList)) {
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// 循环注入自定义方法
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
} else {
logger.debug(mapperClass.toString() + ", No effective injection method was found.");
}
mapperRegistryCache.add(className);
}
}
}
public class TableInfoHelper {
/**
* <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();
}
/* 初始化表名相关 */
initTableName(clazz, globalConfig, tableInfo);
/* 初始化字段相关 */
initTableFields(clazz, globalConfig, tableInfo);
/* 放入缓存 */
TABLE_INFO_CACHE.put(clazz, tableInfo);
/* 缓存 lambda */
LambdaUtils.installCache(tableInfo);
/* 自动构建 resultMap */
tableInfo.initResultMapIfNeed();
return tableInfo;
}
/**
* <p>
* 初始化 表数据库类型,表名,resultMap
* </p>
*
* @param clazz 实体类
* @param globalConfig 全局配置
* @param tableInfo 数据库表反射信息
*/
private static void initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
/* 数据库全局配置 */
GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
TableName table = clazz.getAnnotation(TableName.class);
String tableName = clazz.getSimpleName();
String tablePrefix = dbConfig.getTablePrefix();
String schema = dbConfig.getSchema();
boolean tablePrefixEffect = true;
if (table != null) {
if (StringUtils.isNotEmpty(table.value())) {
tableName = table.value();
if (StringUtils.isNotEmpty(tablePrefix) && !table.keepGlobalPrefix()) {
tablePrefixEffect = false;
}
} else {
tableName = initTableNameWithDbConfig(tableName, dbConfig);
}
...
} 我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我花了三天的时间用头撞墙,试图弄清楚为什么简单的“rake”不能通过我的规范文件。如果您遇到这种情况:任何文件夹路径中都不要有空格!。严重地。事实上,从现在开始,您命名的任何内容都没有空格。这是我的控制台输出:(在/Users/*****/Desktop/LearningRuby/learn_ruby)$rake/Users/*******/Desktop/LearningRuby/learn_ruby/00_hello/hello_spec.rb:116:in`require':cannotloadsuchfile--hello(LoadError) 最佳
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request
在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo
使用Ruby1.9.2运行IDE提示说需要gemruby-debug-base19x并提供安装它。但是,在尝试安装它时会显示消息Failedtoinstallgems.Followinggemswerenotinstalled:C:/ProgramFiles(x86)/JetBrains/RubyMine3.2.4/rb/gems/ruby-debug-base19x-0.11.30.pre2.gem:Errorinstallingruby-debug-base19x-0.11.30.pre2.gem:The'linecache19'nativegemrequiresinstall
我知道全局变量$!包含最新的异常对象,但我对下面的语法感到困惑。谁能帮助我理解以下语法?rescue$! 最佳答案 此构造可防止异常停止您的程序并使堆栈跟踪冒泡。它还会将该异常作为值返回,这很有用。a=get_me_datarescue$!在此行之后,a将保存请求的数据或异常。然后您可以分析该异常并采取相应措施。defget_me_dataraise'Nodataforyou'enda=get_me_datarescue$!puts"Executioncarrieson"pa#>>Executioncarrieson#>>#更现实的
我在我正在处理的一些代码中发现了这一点。它旨在解决从磁盘读取key文件的要求。在生产环境中,key文件的内容位于环境变量中。旧代码:key=File.read('path/to/key.pem')新代码:key=File.read('|echo$KEY_VARIABLE')这是如何工作的? 最佳答案 来自IOdocs:Astringstartingwith“|”indicatesasubprocess.Theremainderofthestringfollowingthe“|”isinvokedasaprocesswithappro
我今天看到了一个ruby代码片段。[1,2,3,4,5,6,7].inject(:+)=>28[1,2,3,4,5,6,7].inject(:*)=>5040这里的注入(inject)和之前看到的完全不一样,比如[1,2,3,4,5,6,7].inject{|sum,x|sum+x}请解释一下它是如何工作的? 最佳答案 没有魔法,符号(方法)只是可能的参数之一。这是来自文档:#enum.inject(initial,sym)=>obj#enum.inject(sym)=>obj#enum.inject(initial){|mem
我刚刚有一个关于RubyonRails和模型(Rails3)中的attr_accessible属性的一般性问题。有人可以解释应该在那里定义哪些模型属性吗?我记得一些关于批量分配风险的事情,虽然我在这方面不太了解......谢谢:) 最佳答案 想象一个带有一些字段的订单类:Order.new({:type=>'Corn',:quantity=>6})现在假设订单也有折扣代码,比如:price_off。您不想将:price_off标记为attr_accessible。这会阻止恶意代码制作最终会执行如下操作的帖子:Order.new({: