Solve the problem that mybatisplus reported org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.excep

Solution to mybatisplus reporting org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:

Article directory

  • Solve the problem that mybatisplus reported org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
    • Scenes:
    • Exception details:
    • Solution
    • analyze
      • 1. View logs
      • 2. View the source code
    • Summarize

use
mybatisplus
LambdaQueryChainWrapper reported an error

Scene:

RegionPO one = new LambdaQueryChainWrapper<>(regionDAO)
                .select(RegionPO::getRegionId)
                .eq(RegionPO::getName, "Guangdong Province")
                .one();

Exception details:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
### The error may exist in com/yhd/open/content/management/dao/RegionDAO.java (best guess)
### The error may involve com.yhd.open.content.management.dao.RegionDAO.selectList
### The error occurred while handling results
### SQL: SELECT region_id FROM region WHERE (`name` = ?)
### Cause: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)
at com.sun.proxy.$Proxy210.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:166)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:77)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at com.sun.proxy.$Proxy211.selectList(Unknown Source)
at com.baomidou.mybatisplus.core.mapper.BaseMapper.selectOne(BaseMapper.java:173)
at java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$DefaultMethodInvoker.invoke(MybatisMapperProxy.java:162)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at com.sun.proxy.$Proxy211.selectOne(Unknown Source)
at com.baomidou.mybatisplus.extension.conditions.query.ChainQuery.one(ChainQuery.java:48)
    ............................

Solution

It’s because the RegionPO object (that is, the receiving object) does not have a no-parameter construct, but a full-parameter construct is written in it. The error code is:

image-20231026120949316

Just remove @AllArgsConstructor, because if @AllArgsConstructor is written, the current object will not have a no-argument constructor

Analysis

1. The index out-of-bounds exception is very strange. At first, I subconsciously thought it was a problem with one:

image-20231026120534808

But when I change it to list, I still get the same error.

1. View logs

mybatis sql log

JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6329e680] will not be managed by Spring
==> Preparing: SELECT region_id FROM region WHERE (`name` = ?)
==> Parameters: Guangdong Province(String)
<== Columns: region_id
<== Row: 44
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@17826d60]

From the above log, we know that the results have been queried, which proves that there is no problem with the SQL. Then there must be a problem when assigning values to the objects.

2. View source code

In the MyBatis-Plus source code, the LambdaQueryChainWrapper class is the entry class for the chain query provided by MyBatis-Plus. It is not directly responsible for setting the query results into the entity object. In fact, the setting of query results is completed by MyBatis’s ResultSetHandler.

Specifically, when executing the query method of LambdaQueryChainWrapper (such as list(), getOne(), etc.), it will encapsulate the query conditions into a MappedStatement object, and perform query operations through MyBatis’s SqlSession. The query results will be processed by MyBatis’s ResultSetHandler to map the query results to entity objects.

In the ResultSetHandler implementation class of MyBatis, the commonly used implementation is DefaultResultSetHandler. It will call ObjectFactory to create the entity object, and set the query results to the properties of the entity object through the MetaObject object.

To sum up, in MyBatis-Plus, the LambdaQueryChainWrapper class is responsible for building query condition chain calls, and the setting of query results is completed by the ResultSetHandler implementation class of MyBatis. The specific value setting process involves MyBatis’s object factory, meta-object and reflection mechanisms.

1. From the above, it can be seen that the assignment is in the DefaultResultSetHandler class

image-20231026154328372

When debugging this step by step, we can find that an exception occurred in the createResultObject() method. Go inside this method:

2. DefaultResultSetHandler.createResultObject.createResultObject

image-20231026155147505

Enter here, when there is no parameter construction, you will enter the previous if statement

metaType.hasDefaultConstructor() //Determine whether there is no parameter constructor

3. Enter createByConstructorSignature ->applyConstructorAutomapping method

image-20231026155545465

configuration.isArgNameBasedConstructorAutoMapping()//Determine whether the constructor based on the Arg name is automatically mapped. There is no parameterless construction of course not.

So enter the applyColumnOrderBasedConstructorAutomapping method in else

4. Enter the applyColumnOrderBasedConstructorAutomapping method (final error reporting point)

image-20231026160045016

Finally, when this for loop is allowed for the second time, String columnName = rsw.getColumnNames().get(i); reports an index exceeded exception.

We can run the constructor.getParameterTypes() method and get the attribute type of each attribute of the object you assign.

image-20231026160204145

But when we run rsw.getColumnNames(), the result is only the field you query:

image-20231026160259982

Remember our initial statement:

RegionPO one = new LambdaQueryChainWrapper<>(regionDAO)
                .select(RegionPO::getRegionId)
                .eq(RegionPO::getName, "Guangdong Province")
                .one();

From here we can see that rsw.getColumnNames() gets the fields you need to query, so according to this logic, no error will be reported when we query all fields! !

The fact is exactly what I thought. If you check everything, you won’t get an error! !

4. When our object is constructed with no parameters, its assignment is:

Create an object with all properties null by createResultObject, which is the same as creating a new parameterless constructor.

Then come to the caller getRowValue method of createResultObject

image-20231026161442754

applyAutomaticMappings

I believe many people will feel very familiar when seeing this code, it is very similar to native JDBC.

createAutomaticMappings() Build mapping conditions, that is, the correspondence between database table fields and object properties, which will be constructed based on the fields you query.

final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); This line of code finds the value in the corresponding result set based on the field name

metaObject.setValue(mapping.property, value); Assignment

Summary

When the object does not have a no-parameter construct but has a full-parameter construct, mybatis traverses and assigns values based on all attributes of the object.

When the object is constructed with no parameters: the value is assigned based on the query field.