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