MyBatis
MyBatis is an excellent persistence framework that supports customized SQL, stored procedures, and advanced mapping. MyBatis avoids almost all JDBC code and manual parameter setting and retrieving result sets. MyBatis can use simple XML or annotations to configure and native Map s, mapping interfaces and Java POJOs(Plain Old Java Objects, common Java objects) into records in the database.
Before we get to know MyBatis in depth, let's first look at how we deal with the interaction with the database without MyBatis.
I. Starting with JDBC
JDBC (Java DataBase Connectivity,java database connection) is a Java API for executing SQL statements, which can provide unified access to multiple relational databases.
Looking at the following example, the logic is very simple, that is, to query all users from the database:
import java.sql.*; public class FirstExample { static final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://localhost/EMP"; static final String USER = "username"; static final String PASS = "password"; public static void main(String[] args) { Connection conn = null; Statement stmt = null; try{ Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(DB_URL,USER,PASS); stmt = conn.createStatement(); String sql = "SELECT id, first, last, age FROM user"; ResultSet rs = stmt.executeQuery(sql); List<User> userList = new ArrayList<>(); while(rs.next()){ //Retrieve by column name int id = rs.getInt("id"); int age = rs.getInt("age"); String first = rs.getString("first"); String last = rs.getString("last"); User newUser = new User(); newUser.setId(id); newUser.setAge(age); newUser.setFirst(first); newUser.setLast(last); userList.add(newUser); } rs.close(); stmt.close(); conn.close(); }catch(SQLException se){ //Handle errors for JDBC se.printStackTrace(); }catch(Exception e){ //Handle errors for Class.forName e.printStackTrace(); }finally{ //finally block used to close resources try{ if(stmt!=null) stmt.close(); }catch(SQLException se2){ }// nothing we can do try{ if(conn!=null) conn.close(); }catch(SQLException se){ se.printStackTrace(); } } } }
This code does not seem to be complicated, so database operation is also completely possible, you can use code generation tools to quickly generate CRUD operations on a table, but for more complex queries, the generation tools are unable to.
So let's compare how MyBatis interacts with the database.
Get started with MyBatis
Like many Java frameworks, if you need to use MyBatis, you need MyBatis's jar package to appear in the classpath, which can be handled by downloading, Maven, Gradle, and so on.
According to international practice, frameworks need to be configured.
2.1 Configuration
The following is a very basic configuration mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
BlogMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.mybatis.example.BlogMapper"> <select id="selectBlog" resultType="Blog"> select * from Blog where id = #{id} </select> </mapper>
2.2 Load Configuration
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2.3 Perform database operations
SqlSession session = sqlSessionFactory.openSession(); try { Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101); } finally { session.close(); }
Using MyBatis to manipulate databases is completely different from using JDBC. Next, let's take a detailed look.
MyBatis
Mybatis can be roughly divided into two phases, one is configuration phase, the other is execution phase.
- In the start-up phase, the main achievements are as follows:
- Configuration file parsing
- Create core Configuration objects, create associated objects through configuration, and populate Configuration objects with these objects
- Create an instance of SqlSession
- In the execution stage, according to the previous configuration, it is executed through SqlSession, parameter processing, SQL statement parsing, statement execution and processing return.
3.1 Configuration Phase
What does the 3.1.1 configuration contain?
- For configuration file reading, Mybatis abstracts a class named Resources to find the configuration file from the classpath and convert it to InputStream or Reader.
- Pass the InputStream or Reader object obtained in the previous step to the SqlSessionFactory builder, which calls its parse method to get an instance of Configuration and passes the instance of Configuration to the DefaultSqlSessionFactory. Thus, Mybatis configuration analysis is done through XML ConfigBuilder.
3.1.1.1 All-inclusive Configuration
Before we get into the details of XML ConfigBuilder, we need to look at what Mybatis Configuration contains. From the official documentation, Configuration contains the following configuration information:
-
properties
These are externalizable, substitutable properties that can be configured in a typical Java Properties file instance, or passed in through sub-elements of the properties element. For example:<properties resource="org/mybatis/example/config.properties"> <property name="username" value="dev_user"/> <property name="password" value="F2Fa3!33TYyg"/> </properties>
-
settings
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
-
typeAliases
<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases>
<typeAliases> <package name="domain.blog"/> </typeAliases>
@Alias("author") public class Author { ... }
typeHandlers
Whenever MyBatis sets a parameter on a PreparedStatement or retrieves a value from a ResultSet, a TypeHandler is used to retrieve the value in a means appropriate to the Java type. The following table describes the default TypeHandlersobjectFactory
Each time MyBatis creates a new instance of a result object, it uses an ObjectFactory instance to do so. The default ObjectFactory does little more than instantiate the target class with a default constructor, or a parameterized constructor if parameter mappings exist. If you want to override the default behaviour of the ObjectFactory-
plugins
MyBatis allows you to intercept calls to at certain points within the execution of a mapped statement. By default, MyBatis allows plug-ins to intercept method calls of:- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
-
environments
- environment
MyBatis can be configured with multiple environments. This helps you to apply your SQL Maps to multiple databases for any number of reasons.
One important thing to remember though: While you can configure multiple environments, you can only choose ONE per SqlSessionFactory instance.
- transactionManager
There are two TransactionManager types (i.e. type="[JDBC|MANAGED]") that are included with MyBatis
dataSource
-
databaseIdProvider
MyBatis is able to execute different statements depending on your database vendor.
-
mappers
Now that the behavior of MyBatis is configured with the above configuration elements, we're ready to define our mapped SQL statements. But first, we need to tell MyBatis where to find them. Java doesn't really provide any good means of auto-discovery in this regard, so the be.
In the Configuration class, the
- environment
- databaseId
- setting Configuration Item
- jdbcTypeForNull
- lazyLoadTriggerMethods
- defaultStatementTimeout
- defaultFetchSize
- defaultExecutorType
- autoMappingBehavior
- NONE Disables auto-mapping.
- PARTIAL(default) Will only auto-map results with no nested result mappings defined inside
- FULL Will auto-map result mappings of any complexity (containing nested or otherwise)
- autoMappingUnknownColumnBehavior
- NONE(default) Do nothing
- WARNING Output warning log
- FAILING Fail mapping
- variables(properties)
- reflectorFactory Reflector represents a cached set of class definition information that allows for easy mapping between property names and getter/setter methods
- objectFactory MyBatis uses an ObjectFactory to create all needed new Objects.
- objectWrapperFactory Wrapper of the object, mainly used in MetaObject
- proxyFactory proxyFactory,default is JavassistProxyFactory
- configurationFactory Configuration factory class. Used to create Configuration for loading deserialized unread properties.
- mapperRegistry There are two important ways to cache mappers through the field Map < Class <?>, mapper ProxyFactory <?> knownMappers:
- public <T> void addMapper(Class<T> type) First, you add an instance of type and MapperProxyFactory that corresponds to the knownmapper. Then you create an instance of MapperAnnotation Builder and call its parse method.
- public <T> T getMapper(Class<T> type, SqlSession sqlSession) Trying to find the corresponding MapperProxyFactory from the knownmapper through type, if there is a MapperProxyFactory, call its newInstance() to create a proxy instance of the mapper.
-
interceptorChain
All Interceptor s are saved using an ArrayList. The most important method is:public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
-
typeHandlerRegistry
//JDBC Type private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); //Java Type private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>(); //Unknown Type private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); //All Type private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>(); //NULL Type private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = new HashMap<JdbcType, TypeHandler<?>>();
typeAliasRegistry
languageRegistry
mappedStatements(Map<String, MappedStatement>)
caches (Map<String, Cache>)
resultMaps(Map<String, ResulMap>)
parameterMaps(Map<String, ParameterMap>)
keyGenerators(Map<String, KeyGenerator>)
loadedResources(Set<String>)
sqlFragments(<String, XNode>)
cacheRefMap(Map<String, String>)
incompleteStatements(Collection<XMLStatementBuilder>)
incompleteCacheRefs(Collection<CacheRefResolver>)
incompleteResultMaps(Collection<ResultMapResolver>)
incompleteMethods(Collection<MethodResolver>)
3.1.1.2 What did you do with the new Configuration?
In the new Configuration, some default type alias are initialized.
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); typeAliasRegistry.registerAlias("FIFO", FifoCache.class); typeAliasRegistry.registerAlias("LRU", LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
At the same time, the default language driver is set as XML Language Driver and registered as Raw Language Driver.
3.1.2 Configuration File Processing
3.1.2.1 XML
Configuration instance configurations in Mobile are accomplished through XML ConfigBuilder, specifically, mainly through:
private void parseConfiguration(XNode root) { try { //issue #117 read properties first 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); // read it after objectFactory and objectWrapperFactory issue #631 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); } }
It's going on.
Here we need to pay attention to mapperElement, because in Mybatis's xml configuration, there are two types and four types of mapper configuration:
- The first is through XML.
- The other is through java's interface.
- The two types of configurations can be mixed.
The configuration of XML is parsed by: XML Mapper Builder.
Interface is configured by: Mapper Annotation Builder for parsing.
<!-- 1. Using classpath relative resources --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers> <!-- 2. Using url fully qualified paths --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers> <!-- 3. Using mapper interface classes --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers> <!-- 4. Register all interfaces in a package as mappers --> <mappers> <package name="org.mybatis.builder"/> </mappers>
When the xml Mapper Builder parses the xml configuration, it first processes namespace, and then processes it in turn:
- cache-ref
The namespace in cache-ref is read out and processed by CacheRefResolver's resolveCacheRef() method
-
resolveCacheRef() proxies processing logic to MapperBuilderAssistant
private final MapperBuilderAssistant assistant; public Cache resolveCacheRef() { return assistant.useCacheRef(cacheRefNamespace); }
-
Mapper Builder Assistant is getCache(namespace) from Configuration
public Cache useCacheRef(String namespace) { if (namespace == null) { throw new BuilderException("cache-ref element requires a namespace attribute."); } try { unresolvedCacheRef = true; Cache cache = configuration.getCache(namespace); if (cache == null) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found."); } currentCache = cache; unresolvedCacheRef = false; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e); } }
Similar to other configurations, the final information is aggregated into the Configuration object.
- cache
Read the configuration in xml
-
Complete the creation of Cache object through builder Assistant, and add the newly created Cache object to Configuration.
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
parameterMap
DeprecatedresultMap
Grab all resultMap s in the xml file and parse them one by one
When dealing with each resultMap, the XML Mapper Builder reads the attribute configurations of the resultMap from xml, and then completes the resultMap creation with the processing agent to builder Assistant as well.
After obtaining the resultMap object, because resultMap can extend other resultMaps, and the inherited resultMap may or may not have been parsed, ResultMapResolver handles this problem, and ResultMapResolver throws the problem to builder Assistant to complete the final processing.
In builder Assistant
* If `parentResultMap'is not loaded yet, Mybatis throws an exception `IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");` In `XML Mapper Builder', `Incomplete Element Exception'is captured and `ResultMapResolver' is added to `Configuration': `configuration. add Incomplete ResultMap Exception'.` * If `parentResultMap'has been loaded, 1. Replace `constructor'of `parentResultMap' with `constructor'of `subResultMap'.` 2. Merge `subResultMap'and `parentResultMap'.` 3. Complete the creation of an instance of `ResultMap'by `ResultMap.Builder', and add it to `Configuration'.
-
sql
This element can be used to define a reusable fragment of SQL code that can be included in other statements. It can be statically (during load phase) parametrized. Different property values can vary in include instances. For example:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
The SQL fragment can then be included in another statement, for example:
<select id="selectUsers" resultType="map"> select <include refid="userColumns"><property name="alias" value="t1"/></include>, <include refid="userColumns"><property name="alias" value="t2"/></include> from some_table t1 cross join some_table t2 </select>
As can be seen from the above, the analysis of sql configuration should be relatively simple: sql should be XNode, stored in the form of (Id,XNode) in XML MapperBuilder. sql Fragments.
-
select|Insert|update|delete
select|Insert|update|delete is the most complex configuration item, and Mybatis handles it to the XML Statement Builder.
In parseStatementNode() of XMLStatementBuilder, the following processing is included:
- Reading of Simple Configuration Items
- Complete the processing of include nodes through XML IncludeTransformer. applyIncludes
- Processing selectKey
- After processing include and selectKey, Mybatis replaces or removes the initial include and selectKey tags, and then completes the processing of the SQL statement through Language Driver, returning an instance of SqlSource.
The default Language Driver is XML Language Driver, which proxies the parsing of SQL statements to XMLScript Builder. parseScriptNode ():
public SqlSource parseScriptNode() { List<SqlNode> contents = parseDynamicTags(context); MixedSqlNode rootSqlNode = new MixedSqlNode(contents); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
List < SqlNode > returned in parseDynamicTags contains the following SqlNodes
tag SqlNode handler ${} TextSqlNode N/A trim TrimSqlNode TrimHandler where WhereSqlNode WhereHandler set SetSqlNode SetHandler foreach ForEachSqlNode ForEachHandler if IfSqlNode IfHandler choose ChooseSqlNode ChooseHandler when IfSqlNode IfHandler otherwise MixedSqlNode OtherwiseHandler bind VarDeclSqlNode BindHandler ordinary StaticTextSqlNode N/A - After obtaining the SqlSource object, Mybatis is more configurable to initialize KeyGenerator
- Finally, MappedStatement is generated by MappedStatement.Builder and added to Configuration.
So far, the xml configuration of a mapper has been processed, and the next step is to add the java class corresponding to the namespace to the Configuration through configuration.addMapper.
Finally, pending resolver is called:
parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();
3.1.2.2 Annotation
Now, there is another problem that has not been solved, that is, the definition of Mapper, which can be used in XML, Annotation, or two mixed ways. We have already known how the configuration of XML is handled. Where is Annotation handled and how is Annotation handled?
When calling configuration.addMapper, Mybatis will proxy to MapperRegistry, and then complete Annotation processing through Mapper Annotation Builder. Annotation supported by Mybatis is as follows:
Annotation | Target | XML equivalent |
---|---|---|
@CacheNamespace | Class | <cache> |
@Property | N/A | <property> |
@CacheNamespaceRef | Class | <cacheRef> |
@ConstructorArgs | Method | <constructor> |
@Arg | N/A | <arg><idArg> |
@TypeDiscriminator | Method | <discriminator> |
@Case | N/A | <case> |
@Results | Method | <resultMap> |
@Result | N/A | <result><id> |
@One | N/A | <association> |
@Many | N/A | <collection> |
@MapKey | Method | |
@Options | Method | Attributes of mapped statements. |
@Insert @Update @Delete @Select | Method | <insert> <update> <delete> <select> |
@InsertProvider @UpdateProvider @DeleteProvider @SelectProvider | Method | <insert> <update> <delete> <select> |
@Param | Parameter | N/A |
@SelectKey | Method | <selectKey> |
@ResultMap | Method | N/A |
@ResultType | Method | N/A |
@Flush | Method | N/A |
Before Mapper Annotation Builder starts processing Annotation, Mapper Annotation Builder checks whether the related resources with Mapper namespace are loaded by configuration.isResourceLoaded(resource). If not, it loads and parses the configuration of XML first, then processes Cache and CacheRef, because the two Annotations are at the Type level, so priority is given. There is not much difference between processing and XML.
Next iterate through all methods on Type:
-
Processing ParameterType
If it is a single parameter, the ParameterType is the Type of that parameter, otherwise, it is ParamMap.class.
Get Language Driver
-
Getting SqlSource through Language Driver
- Get @Insert/@Update/@Delete/@Select
- Get @InsertProvider/@UpdateProvider/@DeleteProvider/@SelectProvider
- Two kinds of annotations can't appear at the same time, they can only choose one from the other.
- If the first is the case, then:
- Get sql statements from sqlAnnotation, resulting in an array of String types
- Connecting String arrays with spaces
- Create SqlSource through Language Driver.
- If there is < script > in the SQL statement, the SQL is processed through the XMLScript Builder.
- If there is no < script >, then through PropertyParser.parse, and then by creating SqlSource.
- In case of type 2, an instance of ProviderSqlSource is returned.
If the sql statement type is INSERT or UPDATE, the Key Generator is determined based on the two Annotation s of SelectKey and Options
Processing @ResultMap, splicing the value of ResultMap together to form a resultMapId.
-
If it is SELECT, call resultMapId = parseResultMap(method);
- Get return Type, Constructor Args, Results, Type Discriminator
- If Results has a specified id, then resultMapId=type.getName() + "." + results.id() If not specified, resultMapId=type.getName() + "." + method.getName() + suffix
- Convert ConstructorArg to a ResultMapping object.
- Processing Results and turning it into a ResultMapping object.
- Processing Discriminator
Finally, the MappedStatement object is obtained through MapperBuilderAssistant.
3.2 The execution process of database operation
So far, both the XML configuration and the Annotation configuration have been loaded, and then the mapper method can be invoked.
-
Get the proxy instance of Mapper through SqlSession:
//1. DefaultSqlSession.java public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } //2. Configuration.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } //3. MapperRegistry.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
-
As can be seen from the above code, the object of Mapper we get is an example of MapperProxy. The declaration of MapperProxy is as follows:
public class MapperProxy<T> implements InvocationHandler, Serializable {...}
When we execute a method, its execution process is as follows:
- MapperProxy tries to get a MapperMethod instance from the methodCache. If not, it creates an instance and adds it to the methodCache.
- Execute mapperMethod.execute(sqlSession, args);
- MapperMethod contains two examples:
* SqlCommand //Get the ID of the type and appedStatement through MappedStatement * MethodSignature ```java private final boolean returnsMany; private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver; ``` //When executing the execute method, 1. adopt SqlCommand Judgment is INSERT/UPDATE/DELETE/SELECT/FLUSH,The following are INSERT For example. 2. adopt MethodSignature Of ParamNameResolver There are three situations when parameter objects are obtained: * When there are no parameters, return null * If there is only one parameter, it is the parameter itself. * If there are more than one parameter, return one Map 3. implement`rowCountResult(sqlSession.insert(command.getName(), param))`
DefaultSqlSession retrieves the MappedStatement from the Configuration through the ID of the MappedStatement, which is then executed by Executor.
-
When configuring Mybatis, we can configure Executor to SIMPLE/REUSE/BATCH by default Executor Type and SIMPLE by default.
- Clean up local cache
-
The Configuration object is obtained by MappedStatement, and the Configuration object is obtained by:
configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
Get the StatementHandler object, which in this case is RoutingStatementHandler, which will proxy the actual operation to SimpleStatementHandler/PreparedStatementHandler (default)/CallableStatementHandler according to the type of MappedStatement.
When you create the PreparedStatement Handler, you get the BoundSql through MappedStatement. getBoundSql - > sqlSource. getBoundSql (parameterObject).
Finally, plugin all interceptor instances to this StatementHandler.
The action of plugin is to dynamically proxy the Statement Handler layer by layer.
-
Mybatis encapsulates a Transaction object, gets Connection from the implementation class JdbcTransaction, and sets Transaction Isolation:
protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommmit); }
-
Call the prepare method of StatementHandler to get the Statement object:
//1. Create a Statement instance stmt = handler.prepare(connection, transaction.getTimeout()); //2. Adding parameters handler.parameterize(stmt); /* 1. Get List < Parameter Mapping > through boundSql 2. Step by step, add parameters to the Statement through the corresponding TypeHandler. parameterMapping.getTypeHandler(); */
//PreparedStatementHandler.java, create a Statement instance, //Ultimately, the PreparedStatement object is created and processed through the JDBC connection object. protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); 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); } }
-
Call Handler's update/query method to execute:
public int update(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); int rows = ps.getUpdateCount(); Object parameterObject = boundSql.getParameterObject(); KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject); return rows; } public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
The difference between the two methods is that update needs to process key s, while query needs to process resultSet s.
* Jdbc3KeyGenerator 1. adopt PreparedStatement.getGeneratedKeys 2. Get the configuration in keyProperties 3. Getting the corresponding type TypeHandler,take TypeHandler Processed values are set to objects. * SelectKeyGenerator 1. adopt SimpleExecutor implement KeyStatement Obtained Key Value 2. Set the value to Parameter In the Object * ResultHandler //When executing SELECT, MapperMethod needs to choose different execution strategies according to the type of return value: ```java if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); //Use the specified resultHandler or null result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); //The resultHandler passed in when executor.query is null } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); //The resultHandler passed in when executor.query is null //Before returning, the results are processed through DefaultMapResultHandler } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); //The resultHandler passed in when executor.query is null } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); //The resultHandler passed in when executor.query is null } ``` //Executor execution is that if the incoming ResultHandler is null, it tries to get the result from the local Cache, and if the result is null, it will query from the Database. SimpleExecutor stay doQuery When it passes, it will pass.`configuration.newStatementHandler`When it passes, it will pass. `configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);` //Create ResultSetHandler and use resultHandler as an incoming parameter. ResultSetHandler In its method: ```java public List<Object> handleResultSets(Statement stmt) throws SQLException {...} ``` //If the resultHandler is found to be null, an instance of the new DefaultResultHandler will process the result.