Upgrade mybatis to mybatis plus

Time:2021-7-22

preface

Recently, ruoyi Vue was used to manage scaffolding in the background. Ruoyi Vue is a Java EE enterprise level rapid development platform, based on the combination of classic technologies (spring boot, spring security, mybatis, JWT, Vue), with built-in modules such as department management, role users, menu and button authorization, data permissions, system parameters, log management, code generation, etc. Online timing task configuration; Support cluster and multiple data sources. The official documents are as follows

http://doc.ruoyi.vip/

Interested friends, you can click the link to view. The current ORM framework of this platform is mybatis, while the ORM framework of the project team is mybatis plus. In order to unify the technology stack, the project team decided to upgrade the ORM framework of ruoyi to mybatis plus. Because I have the experience of upgrading mybatis to mybatis plus before, I feel that the upgrade is very simple. However, after the transformation, the running program reported the following exception

Invalid bound statement (not found): com.lybgeek.admin.file.mapper.FileMapper.insert

Investigation

The literal meaning of exception is that the insert method in filemapper has no binding. Looking at the configuration of filemapper.xml, you can’t find the SQL statement block of binding insert. Is it possible to solve the problem by adding the SQL statement block of insert? Plus, it can really solve the problem.

However, if you have used mybatis plus, you should know that basemapper in mybatis plus has already packaged a series of single table addition, deletion, modification and query. We can add, delete, modify and query a single table without writing configuration. Therefore, to configure insert in XML is to treat the symptoms but not the root cause.

How to check?

1. Direction 1: is it caused by packet conflict?

Using Maven helper plug-in package conflict

Upgrade mybatis to mybatis plus
As can be seen from the figure, it is not caused by packet conflict.

Note:Because I suffered from packet conflicts before, before changing the orm of ruoyi to mybatis plus, the jar conflicts related to mybatis have been removed

Direction 2: do you want to introduce basemapper with different class packages

What we have to introduce is

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

instead of

import com.baomidou.mybatisplus.mapper.BaseMapper;

However, this problem is usually caused by the introduction of different versions of mybatis plus jar. If you only use version 3 + or above, it will only be introduced

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

Direction 3: general method (breakpoint debugging)

In fact, the most fear of code troubleshooting is that the exception stack is eaten. If there is exception information, the direction of troubleshooting is relatively easy to find. For example, the exception stack information of this exception is

Caused by: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.lybgeek.admin.file.mapper.FileMapper.insert
    at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235)
    at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:53)
    at org.apache.ibatis.binding.MapperProxy.lambda$cachedInvoker$0(MapperProxy.java:107)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    at org.apache.ibatis.binding.MapperProxy.cachedInvoker(MapperProxy.java:94)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)
    at com.sun.proxy.$Proxy129.insert(Unknown Source)
    at com.baomidou.mybatisplus.extension.service.IService.save(IService.java:59)
    at com.baomidou.mybatisplus.extension.service.IService$$FastClassBySpringCGLIB$$f8525d18.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

From the exception stack information, we can know that the exception is from

org.apache.ibatis.binding.MapperMethod

So we can set the breakpoint here first. Through the source code, we can knoworg.apache.ibatis.mapping.MappedStatementEmpty, resulting in the above exception, and mappedstatement is generated by

org.apache.ibatis.session.Configuration

Provide. And configuration is through

org.apache.ibatis.session.SqlSessionFactory

Make the settings. Then continue to investigate, and you will find that

com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

This automatic assembly class. There is such a piece of code in it

@Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        //Todo uses mybatissqlsessionfactorybean instead of sqlsessionfactorybean
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }
        applyConfiguration(factory);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }
        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }
        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }
        if (this.properties.getTypeAliasesSuperType() != null) {
            factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
        }
        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            factory.setTypeHandlers(this.typeHandlers);
        }
        Resource[] mapperLocations = this.properties.resolveMapperLocations();
        if (!ObjectUtils.isEmpty(mapperLocations)) {
            factory.setMapperLocations(mapperLocations);
        }

        //Todo has made some modifications to the source code (because the source code adapts to the old version of mybatis, but we don't need to adapt it)
        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
        if (!ObjectUtils.isEmpty(this.languageDrivers)) {
            factory.setScriptingLanguageDrivers(this.languageDrivers);
        }
        Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);

        //Todo custom enumeration package
        if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
            factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
        }
        //Todo must be non null here
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        //Todo injection filler
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        //Todo injection primary key generator
        this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i));
        //Todo injection SQL injector
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        //Todo injection ID generator
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
        //Todo set globalconfig to mybatissqlsessionfactorybean
        factory.setGlobalConfig(globalConfig);
        return factory.getObject();
    }

The author has written in his notes that he should use

Mybatissqlsessionfactorybean instead of sqlsessionfactorybean

So check the ruoyi code and find the following code fragment in mybatis configuration class in ruoyi

 @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
    {
        String typeAliasesPackage = env.getProperty("mybatis.type-aliases-package");
        String mapperLocations = env.getProperty("mybatis.mapper-locations");
        String configLocation = env.getProperty("mybatis.config-location");
        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);

        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        return sessionFactory.getObject();
    }

From the source code of mybatisplus autoconfiguration, we can know that when the project has been configured with sqlsessionfactory. Mybatis plus will not automatically inject sqlsessionfactory for us, but use our own sqlsessionfactory. If the sqlsessionfactory configured by project is not mybatissqlsessionfactorybean

repair

1. Method 1

Replace the sqlsessionfactorybean of mybatis with mybatis sqlsessionfactorybean of mybatis plus

2. Method 2

Remove the sqlsessionfactory from the project. In this way, mybatis plus will automatically inject sqlsessionfactory for us

summary

Some friends may feel that if they encounter abnormal problems, they can not find the answer directly through the search engine. This is really a good method, but sometimes we may not find the answer after searching for half a day. We can get some useful information through exception information stack and debugging thread stack. It’s not terrible to have an exception. What’s terrible is that when something goes wrong, the exception log information is swallowed, and you don’t know where to check. Finally, if Amway relies on this scaffold to manage the backstage development artifact, who will use it