Spring整合Mybatis原理

1、@MapperScan注解发挥作用

在Spring整合Mybatis的时候,只需要一个@MapperScan注解就可以来进行操作,所以更加好奇的是@MapperScan底层是怎么来做到的。

下面先来研究一下@MapperScan:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
...
}

@Import注解导入了一个MapperScannerRegistrar。而在Spring启动的时候,会在org.Springframework.context.support.AbstractApplicationContext#refresh方法中会来执行@Import导入的类,看下如何来解析@Import注解的。直接来到org.Springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass会来解析@Import注解导入进来的类,那么下面就需要来看一下MapperScannerRegistrar做了什么事情。

1.1、导入MapperScannerRegistrar类

下面来看一下MapperScannerRegistrar的结构体系

1.1.2、执行ImportBeanDefinitionRegistrar接口中的registerBeanDefinitions方法

MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,而实现了接口中的方法,那么在注册BeanDefinition之前,就会来执行ImportBeanDefinitionRegistrar接口中的方法

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}

那么只需要看一下MapperScannerRegistrar中的registerBeanDefinitions方法是如何来进行实现的:

1.2、MapperScannerConfigurer

而在上面的代码中,主要是注册了一个MapperScannerConfigurer类型的BeanDefinition

那么来看一下这个BeanDefinition有什么特点:

MapperScannerRegistrar实现了BeanDefinitionRegistryPostProcessor接口。而这个接口中又会在ConfigurationClassPostProcessor之后发挥作用,而MapperScannerRegistrar对应的BeanDefinition是在此之前来进行解析的,所以将会在下面来解析MapperScannerRegistrar对应的BeanDefinition

那么就会来执行MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法

1.2、ClassPathMapperScanner

那么看一下Spring-Mybatis中自定义整合的ClassPathMapperScanner中的注册过滤器和扫描逻辑:

将扫描出来的类利用包含过滤器添加到当前逻辑中。

那么来看一下扫描逻辑:

然后可以看到只要接口的类

1.2.1、对筛选出来的BeanDefinition进行处理

然后对扫描出来的接口来进行筛选判断:

org.mybatis.Spring.mapper.ClassPathMapperScanner#processBeanDefinitions

1、首先获取得到接口的全限定类型。如:com.guang.dao.UserDao;

2、将当前的BeanClass设置成MapperFactoryBean,而这个类是FactoryBean类型的。在创建对象的时候,说明是要用getObject方法来创建对象的;

3、设置MapperFactoryBean构造函数中的值。在MapperFactoryBean构造函数中是Class类型,而这里设置的是String,在创建的时候Spring回来进行转换;

4、将MapperFactoryBean的注入模型设置为By-Type。也就是说,MapperFactoryBean中的setXxx中的属性会从容器中来进行查找

那么看一下MapperFactoryBean的构造方法:

  public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

这里会将mapperInterface在进行赋值的时候将java.lang.String转换成Class类型,表示对应的接口。

第二个:

说明了在MapperFactoryBean中的setXxxx方法对应的xxx属性,Spring会自动来进行赋值。而MapperFactoryBean继承了SqlSessionDaoSupport,在SqlSessionDaoSupport类中的set方法中存在setSqlSessionFactory方法和setSqlSessionTemplate方法,所以容器中如果存在着对应类型的对象的时候,那么Spring到时候来进行赋值的时候将会从容器中来进行获取得到对应的bean。

所以:SqlSessionTemplate我们可以自己配置,但是SqlSessionFactory就得由我们自己来进行配置了。

1.3、SqlSessionFactoryBean类结构体系

SqlSessionFactoryBean类就是来帮我们创建SqlSessionFactory,看下SqlSessionFactory的结构如下所示:

SqlSessionFactoryBean也是一个FactoryBean类型的,产生的对象的类型是:SqlSessionFactory。

看一下对应的getObjectType方法:

public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}

看一下对应的getObject方法:

public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
} return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
// 调用对应的buildSqlSessionFactory方法产生一下sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}

而根据buildSqlSessionFactory方法可以知道,我们可以通过对SqlSessionFactoryBean对象的属性来进行设置,从而实现对Configuration对象属性的设置。

其中有一行代码值得注意下:

targetConfiguration.setEnvironment(new Environment(this.environment,this.transactionFactory == null ?
new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));

对于Environment对象来说,看看构造函数:

public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
if (id == null) {
throw new IllegalArgumentException("Parameter 'id' must not be null");
}
if (transactionFactory == null) {
throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
}
this.id = id;
if (dataSource == null) {
throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
}
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}

需要的第二个参数是TransactionFactory(事务工厂),Spring-Mybatis整合包中,利用SpringManagedTransactionFactory类实现TransactionFactory,重写其中的方法,让当前事务交给Spring来进行处理。

那么后续Mybatis如果存在事务操作的时候,将会由Spring中的事务来进行处理。

1.3.1、SqlSessionFactoryBean使用

这里需要注意的是,下面的使用方式是通过配置类的方式来进行注入的。

因为@MapperScan注解导入的MapperScannerRegistrar的作用就是来注册一个MapperScannerConfigurer类型的BeanDefinition。

所以在下面可以直接利用@Bean注入一个对应类型的BeanDefinition,然后给BeanDefinition来设置一些属性而已。

其中在MapperScannerConfigurer实现InitializingBean接口中的afterPropertiesSet方法中判断了扫描包不能够为空,也就是说要求必须设置basePackage属性

第一种使用方式

第二种使用方式

结合SpringBoot项目使用

@ConfigurationProperties(
prefix = "mybatis"
)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private String typeAliasesPackage;
private String[] mapperLocations;
private Integer batchExecuteSize = 1000; public MybatisProperties() {
} public String getTypeAliasesPackage() {
return this.typeAliasesPackage;
} public void setTypeAliasesPackage(String typeAliasesPackage) {
this.typeAliasesPackage = typeAliasesPackage;
} public String[] getMapperLocations() {
return this.mapperLocations;
} public void setMapperLocations(String[] mapperLocations) {
this.mapperLocations = mapperLocations;
} public Integer getBatchExecuteSize() {
return this.batchExecuteSize;
} public void setBatchExecuteSize(Integer batchExecuteSize) {
this.batchExecuteSize = batchExecuteSize;
}
}
@Configuration
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceConfig.class})
@MapperScan({"com.guang.common.dao"})
public class MybatisConfig {
private static final String CONFIG_LOCATION = "classpath:mybatis/mybatis-config.xml";
private static final String COMMON_MAPPER_LOCATIONS = "classpath:com/guang/common/dao/mapper/*Mapper.xml";
private final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); public MybatisConfig() {
} @Bean(
name = {"sqlSessionFactory"}
)
@ConditionalOnClass({DataSource.class})
@ConditionalOnBean({DataSource.class})
public SqlSessionFactory sqlSessionFactory(MybatisProperties mybatisProperties, DataSource dataSource, ObjectProvider<Interceptor> interceptorObjectProvider) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setConfigLocation(this.getConfigLocation());
sqlSessionFactoryBean.setMapperLocations(this.getMapperLocations(mybatisProperties));
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackage());
List<Interceptor> interceptorList = (List)interceptorObjectProvider.stream().collect(Collectors.toList());
sqlSessionFactoryBean.setPlugins((Interceptor[])interceptorList.toArray(new Interceptor[0]));
return sqlSessionFactoryBean.getObject();
} @Primary
@Bean(
name = {"simpleSqlSessionTemplate"}
)
@ConditionalOnClass({SqlSessionFactory.class})
@ConditionalOnBean({SqlSessionFactory.class})
public SqlSessionTemplate simpleSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.SIMPLE);
} @Bean(
name = {"batchSqlSessionTemplate"}
)
@ConditionalOnClass({SqlSessionFactory.class})
@ConditionalOnBean({SqlSessionFactory.class})
public SqlSessionTemplate batchSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
} @Bean(
name = {"mapperBatchExecutor"}
)
@ConditionalOnClass({SqlSessionTemplate.class})
@ConditionalOnBean(
value = {SqlSessionTemplate.class},
name = {"batchSqlSessionTemplate"}
)
public MapperBatchExecutor mapperBatchExecutor(@Qualifier("batchSqlSessionTemplate") SqlSessionTemplate batchSqlSessionTemplate, MybatisProperties mybatisProperties) {
return new MapperBatchExecutor(batchSqlSessionTemplate, mybatisProperties.getBatchExecuteSize());
} @Bean(
name = {"pagingInterceptor"}
)
public PagingInterceptor pagingInterceptor() {
return new PagingInterceptor();
} private Resource getConfigLocation() {
return this.resourceResolver.getResource("classpath:mybatis/mybatis-config.xml");
} private Resource[] getMapperLocations(MybatisProperties mybatisProperties) throws IOException {
Resource[] mapperLocations = this.resourceResolver.getResources("classpath:com/guang/common/dao/mapper/*Mapper.xml");
String[] var3 = mybatisProperties.getMapperLocations();
int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) {
String mapperLocation = var3[var5];
Resource[] addedMapperLocations = this.resourceResolver.getResources(mapperLocation);
Resource[] originMapperLocations = mapperLocations;
mapperLocations = new Resource[mapperLocations.length + addedMapperLocations.length];
System.arraycopy(originMapperLocations, 0, mapperLocations, 0, originMapperLocations.length);
System.arraycopy(addedMapperLocations, 0, mapperLocations, originMapperLocations.length, addedMapperLocations.length);
} return mapperLocations;
}
}

1.4、SqlSessionTemplate

首先看一下体系结构图

实现了SqlSession接口,那么就有了SqlSession的功能。

说明,我们可以使用SqlsesseionTemplate对象来代替SqlSession对象来进行使用。

1.4.1、构造方法

看一下对应的构造方法,只有一个构造方法。也就是说,如果想要来设置SqlSessionTemplate成为bean,那么当前容器中必须要有一个SqlSessionFactory类型的Bean。

注意这里的参数信息:

  • 1、SqlSessionFactory是创建SqlSessionTemplate对象时候传入进来的,然后赋值给当前SqlSessionTemplate类中的属性sqlSessionFactory;
  • 2、ExecutorType执行器类型是从当前SqlSessionFactory获取得到的,然后赋值给当前SqlSessionTemplate类中的属性ExecutorType;
  • 3、sqlSessionProxy是sqlSession的代理对象,是利用JDK动态代理产生的对象;

1.4.2、增删改查方法

随便看一下都是类似如下所示,使用sqlSessionProxy来进行执行的

public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}

那么执行方法的时候,肯定会指定到InvocationHandler中的invoke方法中来。

那么来看一下SqlSessionInterceptor中的invoke方法。

1.4.3、SqlSessionInterceptor

从上面来看,可以知道是一个InvocationHandler,那么直接来看一下对应的invoke方法实现:

Spring整合Mybatis后为什么一级缓存失效

Spring-Mybatis提供的用来整合Spring和Mybatis的,所以又利用到了Spring中的事务中的内容,那么重点来研究一下这里的代码:

SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

首先要获取得到对应的SqlSession,SqlSession是mybatis中的门面。获取得到了SqlSession之后,才能够来执行对应的方法。

那么看一下如何获取得到SqlSession的。

首先看看是如何放入到当前线程上下文的。

这里有几个需要注意的点:

  • 1、mybatis中的数据源和@Transactional注解或者是TransactionTemplate使用的要是同一个数据源;
  • 2、一定要在开启事务的情况,获取的才是同一个SqlSession;没有开启事务,每次获取得到的SqlSession都是新创建的;

然后看下执行阶段,如下所示:

@Transactional
public void insert(){
userMapper.insert(new User());
orderMapper.insert(new Order());
}

看一下对应的执行

首先执行的时候判断

public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
// 判断是否是同一个SqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
return (holder != null) && (holder.getSqlSession() == session);
}

如果是同一个SqlSession,那么就先不提交!如果不是同一个SqlSession,那么一个方法执行完成之后,就执行对应的SqlSession.commit()方法,各自占用一个数据库连接。最终如果正常的执行的话,始终占用的是同一个数据库连接,在同一个数据库连接中来执行对应的方法。

Mybatis中的一级缓存是基于SqlSession来实现的,所以在执行同一个sql时,如果使用的是同一个SqlSession对象,那么就能利用到一级缓存,提高sql的执行效率。 但是在Spring整合Mybatis后,如果没有执行某个方法时,该方法上没有加@Transactional注解,也就是没有开启Spring事务,那么后面在执行具体sql时,没执行一个sql时都会新生成一个SqlSession对象来执行该sql,这就是我们说的一级缓存失效(也就是没有使用同一个SqlSession对象),而如果开启了Spring事务,那么该Spring事务中的多个sql,在执行时会使用同一个SqlSession对象,从而一级缓存生效,具体的底层执行流程在上图。

个人理解:实际上Spring整合Mybatis后一级缓存失效并不是问题,是正常的实现,因为,一个方法如果没有开启Spring事务,那么在执行sql时候,那就是每个sql单独一个事务来执行,也就是单独一个SqlSession对象来执行该sql,如果开启了Spring事务,那就是多个sql属于同一个事务,那自然就应该用一个SqlSession来执行这多个sql。所以,在没有开启Spring事务的时候,SqlSession的一级缓存并不是失效了,而是存在的生命周期太短了(执行完一个sql后就被销毁了,下一个sql执行时又是一个新的SqlSession了)。

sqlsession的提交和回滚在哪里

对于SqlSession来说,最终的commit和rollback方法,都会调用到connection.commit()和connection.rollback()方法上去。

而在Spring接管了事务之后,在Spring控制的事务中,也会在方法提交或者回滚之后释放数据库连接。

流程

Spring整合Mybatis之后SQL执行流程: Spring整合Mybatis之后SQL执行流程 | ProcessOn免费在线作图,在线流程图,在线思维导图 |

三、总结

由很多框架都需要和Spring进行整合,而整合的核心思想就是把其他框架所产生的对象放到Spring容器中,让其成为Bean。

比如Mybatis,Mybatis框架可以单独使用,而单独使用Mybatis框架就需要用到Mybatis所提供的一些类构造出对应的对象,然后使用该对象,就能使用到Mybatis框架给我们提供的功能,和Mybatis整合Spring就是为了将这些对象放入Spring容器中成为Bean,只要成为了Bean,在我们的Spring项目中就能很方便的使用这些对象了,也就能很方便的使用Mybatis框架所提供的功能了。

Mybatis-Spring 新版本底层源码执行流程

1、通过@MapperScan导入了MapperScannerRegistrar类;

2、MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以Spring在启动时会调用MapperScannerRegistrar类中的registerBeanDefinitions方法;

3、在registerBeanDefinitions方法中定义了一个ClassPathMapperScanner对象,用来扫描mapper,设置ClassPathMapperScanner对象可以扫描到接口,因为在Spring中是不会扫描接口的

4、同时因为ClassPathMapperScanner中重写了isCandidateComponent方法,导致isCandidateComponent只会认为接口是备选者Component

通过利用Spring的扫描后,会把接口扫描出来并且得到对应的BeanDefinition

5、接下来把扫描得到的BeanDefinition进行修改,把BeanClass修改为MapperFactoryBean,把AutowireMode修改为byType

6、扫描完成后,Spring就会基于BeanDefinition去创建Bean了,相当于每个Mapper对应一个FactoryBean

7、在MapperFactoryBean中的getObject方法中,调用了getSqlSession()去得到一个sqlSession对象,然后根据对应的Mapper接口生成一个Mapper接口代理对象,这个代理对象就成为Spring容器中的Bean

8、sqlSession对象是Mybatis中的,一个sqlSession对象需要SqlSessionFactory来产生

9、MapperFactoryBean的AutowireMode为byType,所以Spring会自动调用set方法,有两个set方法,一个setSqlSessionFactory,一个setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以Spring容器中要存在SqlSessionFactory类型的bean和SqlSessionTemplate类型的bean。(注意要注册两个的话,记得要在SIMPLESQLSESSIONTEMPLATE上加上@Primary注解)

10、如果你定义的是一个SqlSessionFactory类型的bean,那么最终也会被包装为一个SqlSessionTemplate对象,并且赋值给sqlSession属性

11、而在SqlSessionTemplate类中就存在一个getMapper方法,这个方法中就产生一个Mapper接口代理对象

到时候,当执行该代理对象的某个方法时,就会进入到Mybatis框架的底层执行流程,详细的请看下图

Spring整合Mybatis之后SQL执行流程: Spring整合Mybatis之后SQL执行流程 | ProcessOn免费在线作图,在线流程图,在线思维导图 |

有点遗漏的步骤,我在这里补充一下:

带来的好处是,可以不使用@MapperScan注解,而可以直接定义一个Bean,比如:

@Bean
public static MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.luban");
return mapperScannerConfigurer;
}

Spring整合Mybatis原理的更多相关文章

  1. 深入源码理解Spring整合MyBatis原理

    写在前面 聊一聊MyBatis的核心概念.Spring相关的核心内容,主要结合源码理解Spring是如何整合MyBatis的.(结合右侧目录了解吧) MyBatis相关核心概念粗略回顾 SqlSess ...

  2. Spring整合Mybatis原理简单分析

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" ...

  3. 【springboot spring mybatis】看我怎么将springboot与spring整合mybatis与druid数据源

    目录 概述 1.mybatis 2.druid 壹:spring整合 2.jdbc.properties 3.mybatis-config.xml 二:java代码 1.mapper 2.servic ...

  4. spring基础:什么是框架,框架优势,spring优势,耦合内聚,什么是Ioc,IOC配置,set注入,第三方资源配置,综合案例spring整合mybatis实现

    知识点梳理 课堂讲义 1)Spring简介 1.1)什么是框架 源自于建筑学,隶属土木工程,后发展到软件工程领域 软件工程中框架的特点: 经过验证 具有一定功能 半成品 1.2)框架的优势 提高开发效 ...

  5. Spring学习总结(六)——Spring整合MyBatis完整示例

    为了梳理前面学习的内容<Spring整合MyBatis(Maven+MySQL)一>与<Spring整合MyBatis(Maven+MySQL)二>,做一个完整的示例完成一个简 ...

  6. Spring学习总结(五)——Spring整合MyBatis(Maven+MySQL)二

    接着上一篇博客<Spring整合MyBatis(Maven+MySQL)一>继续. Spring的开放性和扩张性在J2EE应用领域得到了充分的证明,与其他优秀框架无缝的集成是Spring最 ...

  7. 分析下为什么spring 整合mybatis后为啥用不上session缓存

    因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存. 习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,Guava 所以提出来纠结下 实验 ...

  8. 2017年2月16日 分析下为什么spring 整合mybatis后为啥用不上session缓存

    因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存. 习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,Guava 所以提出来纠结下 实验 ...

  9. spring整合mybatis错误:class path resource [config/spring/springmvc.xml] cannot be opened because it does not exist

    spring 整合Mybatis 运行环境:jdk1.7.0_17+tomcat 7 + spring:3.2.0 +mybatis:3.2.7+ eclipse 错误:class path reso ...

  10. spring 整合Mybatis 《报错集合,总结更新》

    错误:java.lang.NoClassDefFoundError: org/aspectj/weaver/reflect/ReflectionWorld$ReflectionWorldExcepti ...

随机推荐

  1. 安装es客户端软件elasticsearch-head

    安装ElasticSearch插件 一 Head插件介绍 elasticsearch-head是elasticsearch的一款可视化工具,依赖于node.js ,所以需要先安装node.js 二 安 ...

  2. Django框架:13、csrf跨站请求伪造、auth认证模块及相关用法

    Django框架 目录 Django框架 一.csrf跨站请求伪造 1.简介 2.csrf校验策略 form表单csrf策略 ajax请求csrf策略 3.csrf相关装饰器 FBV添加装饰器方式 C ...

  3. day01-ES6新特性

    ES6新特性 1.ES6是什么? DCMAScript 6.0(以下简称ES6)是JavaScript语言的下一代标准,2015年6月发布 ES6设计目标:达到JavaScript语言可以用来编写复杂 ...

  4. Spark通信框架RPC介绍

    Spark通信框架RPC介绍 内容安排: 1.RPC原理 2.nio操作 3.netty简单的api 4.自定义RPC框架 RPC原理学习 什么是RPC RPC(Remote Procedure Ca ...

  5. toastr.js 便捷弹框怎么用?怎么本地化?

    〇.简介 toastr.js 是一个非常简洁的弹窗消息插件,主要原因就是其脚本和样式文件较小. 并且可以根据自己的需求,修改样式文件,可以应用在多种不同的场景. https://codeseven.g ...

  6. JavaFx 页面和控件设置快捷键

    原文:JavaFx 页面和控件设置快捷键 - Stars-One的杂货小窝 之前说过一篇window系统全局快捷键的设置,本期主要是讲解JavaFx应用程序的快捷键设置,还是有所区别的 这里主要是To ...

  7. 微服务框架——MybatisPlus

    MybatisPlus 一.快速入门 1.mybatisPlus特性 无侵入:只增强,不改变. 损耗小:启动的时候直接注入基本CRUD 强大的CRUD操作:提供通用Mapper,通用service,条 ...

  8. Coolify系列-解决WARNING: IPv4 forwarding is disabled. Networking will not work.以及开启防火墙端口

    背景 我在windows电脑安装了一个VM,使用VM开启了Linux服务器,运行docker,然后遇到了这个报错. 解决 首先:在宿主机上执行 echo "net.ipv4.ip_forwa ...

  9. windows使用管理员权限安装软件

    安装步骤 系统搜索 cmd 点击右键,使用管理者方式运行 输入用户名密码 成功以管理员身份运行 cd 到软件存储的目录 输入软件执行文件名, 按回车键,成功开始安装

  10. Solon v2.0 大版本发布。提效率!降成本!

    一个高效的 Java 应用开发框架:更快.更小.更简单.不是 Spring,没有 Servlet,也无关 JavaEE:新兴独立的开放生态.主框架仅 0.1 MB. 150来个生态插件,覆盖各种不同的 ...