MybatisPlusAutoConfiguration
Introduce mybatis-plus-boot-starter
, there is spring.factories
under the package, the content is as follows:
# Auto Configure org.springframework.boot.env.EnvironmentPostProcessor=\ com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\ com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\ com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
MybatisPlusAutoConfiguration
This class injects components such as SqlSessionFactory
and SqlSessionTemplate
.
SqlSessionFactory
MybatisPlusAutoConfiguration#sqlSessionFactory
, create SqlSessionFactory
.
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {<!-- --> MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); factory.setDataSource(dataSource); //... return factory. getObject(); }
MybatisSqlSessionFactoryBean#getObject
@Override public SqlSessionFactory getObject() throws Exception {<!-- --> if (this.sqlSessionFactory == null) {<!-- --> afterPropertiesSet(); } return this.sqlSessionFactory; }
MybatisSqlSessionFactoryBean#afterPropertiesSet
public void afterPropertiesSet() throws Exception {<!-- --> notNull(dataSource, "Property 'dataSource' is required"); state((configuration == null & amp; & amp; configLocation == null) || !(configuration != null & amp; & amp; configLocation != null), "Property 'configuration' and 'configLocation' can not be specified with together"); //TODO clean up resources, it is recommended not to keep this thing SqlRunner. DEFAULT. close(); this.sqlSessionFactory = buildSqlSessionFactory(); }
MybatisSqlSessionFactoryBean#buildSqlSessionFactory
, use MybatisXMLConfigBuilder
to parse xml and build Configuration information.
@MapperScan
Add @MapperScan(basePackages = {"com.charles.mapper"})
to the startup class, and all classes that inherit BaseMapper
under the directory will be scanned.
@Retention(RetentionPolicy.RUNTIME) @Target({<!-- -->ElementType. TYPE}) @Documented @Import({<!-- -->MapperScannerRegistrar.class}) @Repeatable(MapperScans. class) public @interface MapperScan {<!-- --> }
MapperScannerRegistrar
MapperScannerRegistrar
implements ImportBeanDefinitionRegistrar
, and injects MapperScannerConfigurer
into the Spring container in the rewriting method MapperScannerRegistrar#registerBeanDefinitions()
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {<!-- --> AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) {<!-- --> this.registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {<!-- --> BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); //... builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); builder.setRole(2); registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }
MapperScannerConfigurer
MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor
, rewrite method. Using ClassPathMapperScanner
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {<!-- --> if (this. processPropertyPlaceHolders) {<!-- --> this. processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils. hasText(this. lazyInitialization)) {<!-- --> scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization)); } if (StringUtils. hasText(this. defaultScope)) {<!-- --> scanner.setDefaultScope(this.defaultScope); } scanner. registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\\ ")); }
ClassPathMapperScanner#doScan
, scan under the specified package, and the type of registered Bean is MapperFactoryBean
.
public Set<BeanDefinitionHolder> doScan(String... basePackages) {<!-- --> Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions. isEmpty()) {<!-- --> LOGGER. warn(() -> {<!-- --> return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."; }); } else {<!-- --> this.processBeanDefinitions(beanDefinitions); } return beanDefinitions; } private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {<!-- --> BeanDefinitionRegistry registry = this. getRegistry(); Iterator var4 = beanDefinitions. iterator(); while(var4.hasNext()) {<!-- --> BeanDefinitionHolder holder = (BeanDefinitionHolder)var4.next(); AbstractBeanDefinition definition = (AbstractBeanDefinition)holder.getBeanDefinition(); //... try {<!-- --> definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName)); } catch (ClassNotFoundException var10) {<!-- --> } definition.setBeanClass(this.mapperFactoryBeanClass); //... definition.setLazyInit(this.lazyInitialization); if (!scopedProxy) {<!-- --> if ("singleton".equals(definition.getScope()) & amp; & amp; this.defaultScope != null) {<!-- --> definition.setScope(this.defaultScope); } if (!definition.isSingleton()) {<!-- --> BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); if (registry. containsBeanDefinition(proxyHolder. getBeanName())) {<!-- --> registry. removeBeanDefinition(proxyHolder. getBeanName()); } registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); } } } }
ClassPathMapperScanner#isCandidateComponent
, specify the candidate class. needs to be an interface
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {<!-- --> return beanDefinition.getMetadata().isInterface() & amp; & amp; beanDefinition.getMetadata().isIndependent(); }
MapperFactoryBean
MapperFactoryBean#getObject
, the object of this class in the Spring container.
public T getObject() throws Exception {<!-- --> return this.getSqlSession().getMapper(this.mapperInterface); }
MybatisMapperRegistry#getMapper
(mybatis: MapperRegistry#getMapper
)
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {<!-- --> MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) {<!-- --> mapperProxyFactory = (MybatisMapperProxyFactory)this.knownMappers.entrySet().stream().filter((t) -> {<!-- --> return ((Class)t. getKey()). getName(). equals(type. getName()); }).findFirst().map(Entry::getValue).orElseThrow(() -> {<!-- --> return new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry."); }); } try {<!-- --> return mapperProxyFactory. newInstance(sqlSession); } catch (Exception var5) {<!-- --> throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } }
MybatisMapperProxyFactory#newInstance()
, create a proxy class.
protected T newInstance(MybatisMapperProxy<T> mapperProxy) {<!-- --> return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{<!-- -->this.mapperInterface}, mapperProxy); } public T newInstance(SqlSession sqlSession) {<!-- --> MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); }
Execute Mapper
MybatisMapperProxy.PlainMethodInvoker#invoke
will be executed with the encapsulated MybatisMapperMethod
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {<!-- --> return this.mapperMethod.execute(sqlSession, args); }
MybatisMapperMethod#execute
, the core processing class
public Object execute(SqlSession sqlSession, Object[] args) {<!-- --> Object result; Object param; switch(this.command.getType()) {<!-- --> case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() & amp; & amp; this.method.hasResultHandler()) {<!-- --> this. executeWithResultHandler(sqlSession, args); result = null; } else if (this. method. returnsMany()) {<!-- --> result = this. executeForMany(sqlSession, args); } else if (this. method. returnsMap()) {<!-- --> result = this. executeForMap(sqlSession, args); } else if (this. method. returnsCursor()) {<!-- --> result = this. executeForCursor(sqlSession, args); } else if (IPage. class. isAssignableFrom(this. method. getReturnType())) {<!-- --> result = this. executeForIPage(sqlSession, args); } else {<!-- --> param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession. selectOne(this. command. getName(), param); if (this.method.returnsOptional() & amp; & amp; (result == null || !this.method.getReturnType().equals(result.getClass()))) {<!-- --> result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession. flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null & amp; & amp; this.method.getReturnType().isPrimitive() & amp; & amp; !this.method.returnsVoid()) {<!-- --> throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ") ."); } else {<!-- --> return result; } }
Mybatis cache
DefaultSqlSession#selectList()
, this method is used to query the collection
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {<!-- --> try {<!-- --> MappedStatement ms = configuration. getMappedStatement(statement); return executor. query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) {<!-- --> throw ExceptionFactory. wrapException("Error querying database. Cause: " + e, e); } finally {<!-- --> ErrorContext.instance().reset(); } }
BaseExecutor#query()
, the specific query method. A cache key will be generated based on information such as parameters to determine whether it exists in the cache. This cache is the first level cache.
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {<!-- --> BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @SuppressWarnings("unchecked") @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 & amp; & amp; 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; }
CachingExecutor#query()
uses the second-level cache.
@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() & amp; & amp; 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); // issues #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
SimpleExecutor#doQuery
, the default BaseExecutor
implementation of Mybatis is SimpleExecutor
, and finally calls the SimpleExecutor#doQuery
method.
@Override 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); } }