Encapsulation data revealed by mybatis query statement

  • 2021-07-16 02:33:05
  • OfStack

1. Preface

Following the previous mybatis query statement, this one mainly focuses on the later operation of mybatis query, that is, when interacting with the database. Because I am also 1 side learning source code 1 side record, the content inevitably has errors or deficiencies, also hope you correct, this article can only be used as a reference. Remember!

2. Analysis

Following the query example in the previous blog post, mybatis will eventually follow the doQuery method of SimpleExecutor class in the final query.


@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();
 //  Here, the strategy mode is adopted ( Personally, it feels a bit like ) , actual statementHandler For routingStatementHandler
 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 stmt = prepareStatement(handler, ms.getStatementLog());
 //  Although it is executed, routingStatementHandler.query But the result is returned by the PreparedStatementHandler Deal with 
 return handler.query(stmt, resultHandler);
 } finally {
 closeStatement(stmt);
 }
 }

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
 Statement stmt;
 //  The proxy pattern is used, which can also be understood as the connection Has been carried out 1 Layer packaging, the function here is to add log Deal with 
 Connection connection = getConnection(statementLog);
 // Precompile , That is, similar jdbc Adj.  sql, Such as  select * from user where id=?
 stmt = handler.prepare(connection, transaction.getTimeout());
 //  Object that executes the query sql Set parameters 
 handler.parameterize(stmt);
 return stmt;
 }

About the role of handler. prepare here under a brief introduction, do not do code analysis.

fetchSize will be set, and its function is to grab data from the database once, as if the default value is 10 items. If only one item is grabbed at a time, the database will be checked again when rs. next is carried out.

If it is an insert operation, and the database primary key is self-increasing and the primary key can be returned, it will also do the operation of obtaining the primary key.

Let's start with setting parameters, that is, handler. parameterize. First look at the source code, the specific location in the DefaultParameterHandler class inside


@Override
 public void setParameters(PreparedStatement ps) {
 ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
 //  Object in the configuration file sql Parameter information, such as sql For select * from user where id=#{userId,jdbcType=INTEGER}
 // ParameterMapping  The parameter name is recorded, which is userId, There are also records of the corresponding jdbc Type, and the corresponding javaType Wait, you can specifically debug Take a look 
 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 if (parameterMappings != null) {
 for (int i = 0; i < parameterMappings.size(); i++) {
 ParameterMapping parameterMapping = parameterMappings.get(i);
 if (parameterMapping.getMode() != ParameterMode.OUT) {
  Object value;
  String propertyName = parameterMapping.getProperty();
  //  If is true , then sql Parameters have similar  user.name  Format 
  if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
  value = boundSql.getAdditionalParameter(propertyName);
  } else if (parameterObject == null) {
  value = null;
  } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
  value = parameterObject;
  } else {
  // metaObject  Similar 1 Tool class, which contains 1 Reflection factory, which can be specially parsed 1 Class information, such as the setter/getter/ Attribute information, do not do redundant introduction here 
  // 1 , described in detail below  
  MetaObject metaObject = configuration.newMetaObject(parameterObject);
  value = metaObject.getValue(propertyName);//  Value 
  }
  //  Gets the corresponding typeHandler , 1 If the general situation is not set, it is basically ObjectTypeHandler
  TypeHandler typeHandler = parameterMapping.getTypeHandler();
  JdbcType jdbcType = parameterMapping.getJdbcType();
  if (value == null && jdbcType == null) {
  jdbcType = configuration.getJdbcTypeForNull();
  }
  try {
  //  Set values 
  typeHandler.setParameter(ps, i + 1, value, jdbcType);
  } catch (TypeException e) {
  throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
  } catch (SQLException e) {
  throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
  }
 }
 }
 }
 }

For the first part of the above code, it is responsible for rounding out the values in parameterObject (i.e. the passed-in parameters). If the parameters are map structure, the values are taken from map. If not, such as a single non-javabean parameter, the values are taken directly. If it is a single javabean, the values are converted into an BeanWrapper through metaObject class

This code is also responsible for setting parameters for the precompiled sql, and the logic here is mainly carried out around the following steps.

Get the parameter name, get the parameter value, get the parameter type, and then set the value


/**
 * mybatis Data processing includes single result set and multi-result set processing, 1 As many result sets appear in the stored procedure, if two result sets are written in the stored procedure select Statement, such as 
 * select * from user , select * from classes  This situation is not introduced here, because I don't use it much, and my understanding is not very thorough. 
 *  I don't do much introduction here, but I only do it for simple mapping here 1 A general introduction 
 *
 */
public List<Object> handleResultSets(Statement stmt) throws SQLException {
 ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
 //  Save query results 
 final List<Object> multipleResults = new ArrayList<>();

 int resultSetCount = 0;
 //  Get the 1 Bar data 
 ResultSetWrapper rsw = getFirstResultSet(stmt);
 //  If it is not a multi-result set mapping, 1 Like resultMaps The size of is 1
 // resultMap Class field properties, database field names, and other information stored in 
 List<ResultMap> resultMaps = mappedStatement.getResultMaps();
 int resultMapCount = resultMaps.size();
 //  Verify the correctness of data 
 validateResultMapsCount(rsw, resultMapCount);
 while (rsw != null && resultMapCount > resultSetCount) {
 ResultMap resultMap = resultMaps.get(resultSetCount);
 //  Handling result set mappings 
 handleResultSet(rsw, resultMap, multipleResults, null);
 rsw = getNextResultSet(stmt);
 cleanUpAfterHandlingResultSet();
 resultSetCount++;
 }
 //  Deal with slect  Labeled resultSets Attributes, multiple separated by commas, almost never used by individuals, skipped 
 String[] resultSets = mappedStatement.getResultSets();
 if (resultSets != null) {
 while (rsw != null && resultSetCount < resultSets.length) {
 ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
 if (parentMapping != null) {
  String nestedResultMapId = parentMapping.getNestedResultMapId();
  ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
  handleResultSet(rsw, resultMap, null, parentMapping);
 }
 rsw = getNextResultSet(stmt);
 cleanUpAfterHandlingResultSet();
 resultSetCount++;
 }
 }

 return collapseSingleResultList(multipleResults);
 }

The above code is to pave the way for the result mapping, focusing on the hanleResultSet method.


private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
 try {//  For simple mappings, parentMapping Is for Null Adj. 
 if (parentMapping != null) {
 handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
 } else {
 //  Use by default defaultResultHandler If you need to use custom, you can enter it in the transfer resultHandler Interface implementation class 
 if (resultHandler == null) {
  DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
  //  Process the result, the result exists resultHandler Li 
  handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
  multipleResults.add(defaultResultHandler.getResultList());
 } else {
  handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
 }
 }
 } finally {
 // issue #228 (close resultsets)
 closeResultSet(rsw.getResultSet());
 }
 }

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
 //  Handling cases with nested mappings 
 if (resultMap.hasNestedResultMaps()) {
 ensureNoRowBounds();
 checkResultHandler();
 handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
 } else {// No nested mappings 
 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
 }
 }

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
 throws SQLException {
 DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
 ResultSet resultSet = rsw.getResultSet();
 //  How many lines are skipped to reach the specified record position, such as passing in when passing parameters rowBounds Is based on the class's offset Value to the specified record position 
 skipRows(resultSet, rowBounds);
 // shouldProcessMoreRows  Used to detect whether you can continue to map subsequent results 
 while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
 // Used to deal with resultMap Node is configured in the discriminator Node, ignored here 
 ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
 //  The result is that sql Post-execution 1 Row records, such as returning User Object information, the rowValue It means that 1 A user Instance, which already has a value 
 Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
 // Save data 
 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
 }
 }

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
 final ResultLoaderMap lazyLoader = new ResultLoaderMap();
 //  Create an object, which can be understood as the resultMap Node's type Attribute value, the reflection processing is carried out, and the 1 Objects, but the property values are all default values. 
 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
 if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
 final MetaObject metaObject = configuration.newMetaObject(rowValue);
 boolean foundValues = this.useConstructorMappings;
 // Whether automatic mapping is required, there are 3 Species mapping, which are None,partial,full , default number 2 To handle non-nested mappings, you can use the autoMappingBehavior  Configure 
 if (shouldApplyAutomaticMappings(resultMap, false)) {
 //  Mapping resultMap Columns that are not explicitly specified in the class, such as those in the class containing username Property, but resultMap If it is not configured in, you can still query the results by mapping data through this 
 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
 }
 //  Deal with resultMap Columns specified in the 
 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
 foundValues = lazyLoader.size() > 0 || foundValues;
 //  If no query results are found, but the configuration can return an empty object ( Refers to an object with no property value set ) Returns an empty object, otherwise returns a null
 rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
 }
 return rowValue;
 }

Only the columns specified in resultMap are introduced here


private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
 throws SQLException {
 //  Get the data field name 
 final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
 boolean foundValues = false;
 //  The data obtained is resultMap Configured in the node result Node, with multiple result Node, what is the size of this collection 
 //  What is stored inside is the attribute name / Information such as field names 
 final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
 for (ResultMapping propertyMapping : propertyMappings) {
 String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
 //  Whether there are nested mappings 
 if (propertyMapping.getNestedResultMapId() != null) {
 // the user added a column attribute to a nested result map, ignore it
 column = null;
 }
 //  Aim at 1 To say 1 Often used with nested queries 
 // 2  Basic mapping of judgment attributes 
 // 3  Multi-result set 1 One processing 
 if (propertyMapping.isCompositeResult()// 1
  || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))// 2
  || propertyMapping.getResultSet() != null) {// 3
 //  Gets the current column Field for the value of the typeHandler To perform the parameter's 1 Transformations 
 Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
 
 // Gets the property field name of the class 
 final String property = propertyMapping.getProperty();
 if (property == null) {
  continue;
 } else if (value == DEFERRED) {//  Similar to placeholders. Handling lazy loading data 
  foundValues = true;
  continue;
 }
 if (value != null) {
  foundValues = true;
 }
 if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
  //  Set property values 
  metaObject.setValue(property, value);
 }
 }
 }
 return foundValues;
 }

Perhaps some people wonder why they didn't see the query object with set operation, and the value went to the object. Here, all metaObject is for you to operate. Specifically, you can know this class by yourself, and you can only say that this class is very powerful.

Summarize


Related articles: