Interpret according to the jdbc stage of preliminary preparation
- to configure
- Splicing sql statements
- Load the data source and get the Connection
- Get Statement
- Execute get results
- Result conversion
- close
- exception handling
Example 1: com.dzq.MybatisSessionTest
The most basic function, read the source code according to this example
public static void main(String[] args) throws IOException { // configuration file String resource = "mybatis-config.xml"; // Parsing configuration files InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // Get session SqlSession sqlSession = sqlSessionFactory.openSession(); // Find the sql statement according to the state and pass in the parameters for execution String flowKey = sqlSession.selectOne("com.dzq.mapper.TransactionMapper.getFlowKey", 10); System.out.println(flowKey); }
Example 2: com.dzq.mybatismapppertest
How to generate a dynamic agent Mapper and what the dynamic agent does: how to find the corresponding sql according to the dynamic agent
public static void main(String[] args) throws IOException { // configuration file String resource = "mybatis-config.xml"; // Parsing configuration files InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // Get session SqlSession sqlSession = sqlSessionFactory.openSession(); // Get mapper proxy TransactionMapper transactionMapper = sqlSession.getMapper(TransactionMapper.class); // Execute the proxy method and convert the result String flowKey = transactionMapper.getFlowKey(10); System.out.println(flowKey); }
The configuration file mybatis-config.xml and the corresponding TransactionMapper.xml
<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://10.9.224.45:3306/activiti?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/TransactionMapper.xml"/> </mappers> </configuration>
<mapper namespace="com.dzq.mapper.TransactionMapper"> <select id="getFlowKey" resultType="string"> select flow_key from transaction where id = #{id} </select> </mapper>
1. Configuration
When we don't know the overall design of mybatis, too many interpretations of each attribute and parameter of the configuration actually don't make much sense. At least we know some configuration parameters through the jdbc preview above, but we can understand them at this stage
- What configuration mode does mybatis use? Is it properties or xml or database support.
- How is the configuration loaded during parsing? What kind of design pattern is designed?
Configuration and selection
The configuration mode selected by mybatis is the xml file used. xml can well use the label corresponding class, and can also well describe the relationship and dependency between classes.
load
String resource = "mybatis-config.xml"; // Find the file in the classpath to get the input source: the parsing parameters of many files are InputStream, which can come from the network: (the application layer can be a distributed file system, http/https, etc.) or from the local InputStream inputStream = Resources.getResourceAsStream(resource); // When you get the data source, you can get the data and then parse it. After parsing, a SqlSessionFactory factory is generated. SqlSession is a class that directly executes sql // The corresponding factory is generated through configuration. Because the "part" is known, the corresponding "product" can be created through the part SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Loading process
-
Create a special parsing class XMLConfigBuilder to inherit BaseBuilder
// inputStream can get all the configuration information with input, but a special class is created for parsing // The parser stores the configuration of all parsed results and whether they have been parsed. Is it the XPathParser that is responsible for parsing xml XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // Construction method public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // The first parameter, xpath parser, is the xpath parser used this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); }
-
analysis
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // Resolve root label configuration parseConfiguration(parser.evalNode("/configuration")); // Return configuration to build SqlSessionFactory:SqlSessionFactory has only one attribute, that is, it return configuration; }
-
/configuration tag parsing
// Analyze each node private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // Focus on the analysis environment environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
-
Introduce XNode class design
Xnode is the encapsulation of nodes. It refers to the node node in the jdk and encapsulates all functions of the node node. It is the node adapter. Some values of the node node have been put into Xnode, such as attributes.
-
Why not put all the values into Xnode? So you don't have to have a reference to it
node has many attributes, mainly related to the acquisition of child nodes. There may be many child nodes. Lazy loading is used in this place.
private final Node node; private final String name; private final String body; private final Properties attributes; private final Properties variables; private final XPathParser xpathParser; public XNode(XPathParser xpathParser, Node node, Properties variables) { this.xpathParser = xpathParser; this.node = node; this.name = node.getNodeName(); this.variables = variables; this.attributes = parseAttributes(node); this.body = parseBody(node); }
-
-
environments tag parsing
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default"); } // Query all sub tag environment s for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) { // Transaction resolution TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // Data source analysis, focusing on explanation DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); // assignment Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // assignment configuration.setEnvironment(environmentBuilder.build()); } } } }
Parsing process core class diagram
-
DataSourceFactory resolution
private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); // Subtag: < property name = "driver" value = "com. Mysql. JDBC. Driver" / > // All child tags are Properties, so they are directly converted to Properties Properties props = context.getChildrenAsProperties(); // Get the class file from the Map object according to the type return, and then create it DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); // Like the SqlSessionFactory factory, it also needs "parts" to produce "products" // The materials here use Properties in order to adapt to all label factory modes, such as TransactionFactory // Reflect DataSourceFactory by creating MetaObject factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }
-
Create DefaultSqlSessionFactory
new DefaultSqlSessionFactory(config);// The DefaultSqlSessionFactory property has only one config
2. Splicing sql
mybatis writes all sql statements, but the variables are placeholders. All sql is in the configuration file, so it has been parsed when parsing the configuration file. Look at the parsing file.
// Parse the mappers tag. The original configuration file refers to a mapper xml file. Jump to the parse xml file again mapperElement(root.evalNode("mappers"));
Parse mapper.xml file
// org.apache.ibatis.builder.xml.XMLMapperBuilder public void parse() { if (!configuration.isResourceLoaded(resource)) { // Parsing mapper tags is critical // Go back to the parsing tag org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode directly. There are many attributes to parse, but they are relatively simple. You can only know the location // Finally, it is encapsulated in config.mappedStatements. key: id,value:MappedStatement. The specific parameters can be directly viewed from this class configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); // Parsing Mapper.java interface // Finally, it is encapsulated in config.knownMappers, key:type(com.dzq.mapper.TransactionMapper, Class type), value: MapperProxyFactory bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
3. Load the data source and obtain the Connection
// Execution type, from config, transaction level, auto commit private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // Get environment final Environment environment = configuration.getEnvironment(); // Transaction factory final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // Put it into dataSource. dataSource can get Connection tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // If there is tx in the actuator and it is called according to the configuration, you can get other configurations final Executor executor = configuration.newExecutor(tx, execType); // Create a session, configure an actuator, and automatically execute it // It's everywhere return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
4. Get Statement
At the beginning of execution, you can execute after obtaining SqlSession. SqlSession is the entry for execution. First look at this interface
// query <T> T selectOne(String statement); <E> List<E> selectList(String statement); // modify int update(String statement); // insert int insert(String statement); // delete int delete(String statement);
First, check the source code of the selectList. Imagine the process: get the return value and parameters of the sql statement according to the statement, execute the selectList statement according to the executor, get the results, and encapsulate the return according to the return value.
Two key classes MappedStatement and Executor
// SqlSession class main properties and methods // The main attributes, configuration, executor and autoCommit, are created during construction. dirty and cursorList are intermediate states of execution // The reference of configuration can get all configurations private final Configuration configuration; // Executor, real execution class private final Executor executor; // Auto submit private final boolean autoCommit; // Dirty data private boolean dirty; // Store all cursor information. When public < T > cursor < T > selectcursor (string statement) is called, all return values will be stored private List<Cursor<?>> cursorList; @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // MappedStatement is obtained from config and parsed by config. MappedStatement is assigned when parsing configuration MappedStatement ms = configuration.getMappedStatement(statement); // Execution, and then look at the execution method return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
// Executor key properties and methods // Encapsulated actuator, built-in adapter mode protected Executor delegate; // Transaction with dataSource protected Transaction transaction; // to configure protected Configuration configuration; // Query, ms:mapper attribute, parameter: parameter, rowboundaries: paging, ResultHandler: result processing public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // Assemble sql according to parameter splicing. A simple sql also needs to be encapsulated. See how to encapsulate it // BoundSql key attribute, sql: sql statement, with wildcards, parameterMappings: parameter, id, type, parameterObject: parameter value BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
@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 the cache is empty, you can directly see whether the query method of delegate is an adaptation mode or an Executor if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } // delegate real executor execution return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
// SimpleExecutor @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // Get config Configuration configuration = ms.getConfiguration(); // Get statementHandler, and create statementHandler with config, which is also an adapter mode StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); /** configuration.newStatementHandler Method introduction start //StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);The following are the key properties and construction methods of RoutingStatementHandler. The real call uses the delegate call //private final StatementHandler delegate; //public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // switch (ms.getStatementType()) { // case STATEMENT: // delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); // break; // case PREPARED: // delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); // break; // case CALLABLE: // delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); // break; // default: // throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); // } //} configuration.newStatementHandler Method introduction **/ //Generate jdbc statement stmt = prepareStatement(handler, ms.getStatementLog()); /**prepareStatement Internal method private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // Generate connection Connection connection = getConnection(statementLog); // prepare The template mode is described in the following code stmt = handler.prepare(connection, transaction.getTimeout()); // Assignment parameters have different effects for different statement s handler.parameterize(stmt); return stmt; } **/ // statement execute query // statementHandler query, processing stmt return handler.<E>query(stmt, resultHandler); } finally { // Close stmt closeStatement(stmt); } }
org.apache.ibatis.executor.statement.BaseStatementHandler#prepare template method
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { // There are two core methods for generating statements: PreparedStatementHandler, simplestationhandler and instantiateStatement abstract methods statement = instantiateStatement(connection); /**PreparedStatementHandler Instantiatestatestatement method for protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); // There is no design pattern, and different methods are called to generate statement according to different situations if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { String[] keyColumnNames = mappedStatement.getKeyColumns(); if (keyColumnNames == null) { return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() != null) { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } else { return connection.prepareStatement(sql); } } **/ // Set timeout setStatementTimeout(statement, transactionTimeout); // Set the number of fetches each time setFetchSize(statement); return statement; } catch (SQLException e) { closeStatement(statement); throw e; } catch (Exception e) { closeStatement(statement); throw new ExecutorException("Error preparing statement. Cause: " + e, e); } }
-
Execute get result handler.query(stmt, resultHandler);
// org.apache.ibatis.executor.statement.PreparedStatementHandler query results public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // Execute. Next, process the results ps.execute(); // resultSetHandler is one of the properties of PreparedStatementHandler return resultSetHandler.<E> handleResultSets(ps); }
6. Result conversion resultSetHandler. handleResultSets(ps);
The resultSetHandler class is created through the property of resultSetHandler created when StatementHandler is created
configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
@Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; // Get ResultSetWrapper // ResultSetWrapper design: first of all, RS has some other attributes. If you only hold the reference of RS and need to repeatedly obtain some other attributes obtained through RS when calling other methods, you can directly convert rs to the attribute of ResultSetWrapper. The following is the encapsulation of rs //public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException { // super(); // Encapsulation of other attributes, typeHandlerRegistry, and some attributes converted by rs // this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); // this.resultSet = rs; // final ResultSetMetaData metaData = rs.getMetaData(); // final int columnCount = metaData.getColumnCount(); // for (int i = 1; i <= columnCount; i++) { // Returned column name // columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i)); // Returned jdbc type: such as varchar // jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i))); // Returned Java type: for example, java.lang.String // classNames.add(metaData.getColumnClassName(i)); // } //} // Take out the fi rs t one ResultSetWrapper rsw = getFirstResultSet(stmt); // The return type recorded according to the configuration file // Resulttype and resultmap corresponding to the configuration file List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { // Take out the resultMap and put the value of the corresponding result into multipleResults ResultMap resultMap = resultMaps.get(resultSetCount); // Put structure into multipleResults handleResultSet(rsw, resultMap, multipleResults, null); // Get the next result rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } 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++; } } // Returns a single result // multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults return collapseSingleResultList(multipleResults); }
Processing handleResultSet results
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) {// The default is empty // Get the default interface processor DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); // Processing results handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); /** Calling method: 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<Object>(); // skip Number of articles skipRows(rsw.getResultSet(), rowBounds); // Judge whether to stop and cannot exceed the configured number while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { // Because resultMap.getDiscriminator() is empty, it will directly return resultMap itself ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); // Resolve database values Object rowValue = getRowValue(rsw, discriminatedResultMap); // getRowValue Internal method Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null); // createResultObject Internal method Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); // Create the result of the original type and judge that the returned type is in the basic data type return createPrimitiveResultObject(rsw, resultMap, columnPrefix) //2021-10-25 Supplement the entity class to obtain the resultObject method, and explain how to assign values in the next code return objectFactory.create(resultType);// objectFactory To create an instance reconciliation, and assign a value after returning // The type processing class is obtained according to the resultType, which is obtained from rsw and can be cached final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName); // Corresponding type processing return typeHandler.getResult(rsw.getResultSet(), columnName); // Put the field value into resultContext and resultHandler storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } **/ // Take out the results and put them into the multipleResults result set multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } }
On October 25, 2021, the content of assigning values to non original classes was added
// Get examples Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); // Continue creating createResultObject // Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); // objectFactory.create(resultType); Create instances from reflections through objectfactory // If there is a non default constructor // createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs); // final Constructor<?> defaultConstructor = findDefaultConstructor(constructors); // Continue to call createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor); /** private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException { boolean foundValues = false; // Loop parameter list, and obtain the column name in rsw according to the subscript for (int i = 0; i < constructor.getParameterTypes().length; i++) { Class<?> parameterType = constructor.getParameterTypes()[i]; String columnName = rsw.getColumnNames().get(i); TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName); // Value based on column name Object value = typeHandler.getResult(rsw.getResultSet(), columnName); constructorArgTypes.add(parameterType); // Put into the construction parameter array constructorArgs.add(value); foundValues = value != null || foundValues; } return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null; } **/ // After obtaining the entity, judge that it is not the basic data type, enter the judgment statement and assign again if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { // Create metadata objects, final MetaObject metaObject = configuration.newMetaObject(rowValue); // Create MetaObject /** new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory); //It is neither an Array nor a List, so the BeanWrapper is created directly, which mainly contains the get and set method information inside the Class this.objectWrapper = new BeanWrapper(this, object); **/ boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } // Assign values to instances foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; }
7. Close
Statement closing: you can see that the generation is generated from the Executor. The processing of statement is processed in other classes, so it is also closed in this class
// SimpleExecutor @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { // generate 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.<E>query(stmt, resultHandler); } finally { // close closeStatement(stmt); } }
ResultSet close: the ResultSet is obtained from org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets and encapsulated in the ResultSetWrapper. The value is in handleResultSet, and then the next ResultSet is removed. Close in handleResultSet
// Encapsulated in ResultSetWrapper private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // Close after value taking closeResultSet(rsw.getResultSet()); } }
Connection closing: the Transaction is created in advance when creating a SqlSession and then when creating an executor, so it is kept indirectly by the SqlSession and closed by the SqlSession.
Statement and ResultSet are generated during each execution and need to be closed every time. Connection is a link, so it can be closed when not in use.
8. Exception handling
Loading file: exception needs to be handled IOException:IO exception is an exception prepared by mybatis in advance, and it has not really been linked to the database. File exception cannot be handled. It needs to be considered when writing.
Start execution: you don't need to handle exceptions. You can execute directly and check errors during execution. Key analysis is needed. The key exception SQLException of JDBC operation is the exception that needs to be caught. How to handle it?
At the beginning of execution, there are two steps: 1: preliminary preparation, mybatis custom exception, 2: JDBC and SQLException
The prepared exceptions are all runtimeexceptions, which do not need to be caught and handled when there is an error, such as
@Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { // Exception handling when there is too much query data throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } }
All exceptions involving JDBC are not handled, but exceptions are caught at the outermost layer
// All exceptions involving jdbc are thrown @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); }
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // Call the code above return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { // Capture all exceptions, encapsulate them, and return custom exceptions /** public static RuntimeException wrapException(String message, Exception e) { return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e); } **/ throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
For null pointer verification, try to encapsulate it into methods, such as the above code
MappedStatement ms = configuration.getMappedStatement(statement); // The mappedstatement obtained from the configuration is not verified to be empty. If it is empty, it will be null pointerexception. Where is the verification? View configuration. Getmappedstatement (statement);
Think of another problem, the data structure must be reasonable
Calling code
configuration.getMappedStatement(statement); get into this.getMappedStatement(id, true); get into //Get the value from the mappedStatements of the configuration attribute, and the exception has not been verified //protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"); return mappedStatements.get(id); //The StrictMap class gets, rewrites the get method, and throws an exception at the bottom layer, not at the business layer public V get(Object key) { V value = super.get(key); if (value == null) { // If it is empty, an exception will be thrown, and the exception information prompt is also very accurate. name is the StrictMap attribute, naming the map. throw new IllegalArgumentException(name + " does not contain value for " + key); } if (value instanceof Ambiguity) { throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name + " (try using the full name including the namespace, or rename one of the entries)"); } return value; }
Write your own examples
public class Student { private String name; // How many addresses do students have private List<Address> addressList; public List<Address> getAddressList() { if (addressList == null || addressList.size() == 0) { throw new RuntimeException(name + " Home address not filled in"); } return addressList; } public static void main(String[] args) { Student student = new Student(); // It does not affect the appearance of business code List<Address> addressList = student.getAddressList(); //TODO } }