Mybatis framework Overview

Mybatis is a framework of persistence layer, which encapsulates JDBC internally, so that development only needs to focus on THE SQL statement itself, and there is no need to deal with loading drivers, creating connections, creating statements, etc. Here we also analyze it through an example.

Start by writing a SQLmapconfig.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> <! -- Configure Mybatis environment --> <environmentsdefault="mysql"> <! -- Configure mysql --> <environment id="mysql"> <! <transactionManager type="JDBC"></transactionManager> <! --> <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="JDBC: mysql: / / 127.0.0.1:3306 / SSM? characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/> </dataSource> </environment> </environments> <! -- configure mybatis mapping location --> <mappers> <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>
Copy the code

Create a new mappers directory under the Resources directory, and then a new usermapper.xml configuration file

<? 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="com.jd.dao.UserMapper">
 
    <select id="findUser" resultType="com.jd.domain.User">
        select id ,userName as userName,birthday as birthday,sex as sex,
        address as address FROM user where yn=1
    </select>
 
</mapper>
Copy the code

Create a new entity class

 
@Data
public class User {
    private Integer id;
    private Date birthday;
    private String userName;
    private String sex;
    private String address;
    private Integer yn;
}
Copy the code

Create an interface class

public interface UserMapper {
    /** * Query the user */
 
    public List<User> findUser(a);
 
}
Copy the code

Writing test classes

 
public class MybatisTest {
 
    @Test
    public void test(a) throws IOException {
        // The first part of the initialization work, parsing the configuration file
 
        // read the configuration file
        InputStream in=Resources.getResourceAsStream("SqlMapConfig.xml");
        // create a constructor object for SqlSessionFactory
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        // create factory object SqlSessionFactory with constructor
        SqlSessionFactory factory= builder.build(in);
        
        // Execute SQL
 
        // create SqlSession with SqlSessionFactory
        SqlSession session=factory.openSession();
        // create a proxy object for the DAO interface using SqlSession
        UserMapper userDao =session.getMapper(UserMapper.class);
 
 
 
        //6. Execute query method with proxy object
        List<User> list=userDao.findUser();
 
        for (User user:list){
            System.out.println(user);
        }
        // release resources
        session.close();
        try {
            in.close();
        } catch(IOException e) { e.printStackTrace(); }}}Copy the code

In this position, there’s actually another way we could write it,

Let’s talk about using proxy objects to call methods in the interface

Next we will analyze the principle of native Mybatis

There are two main parts,

This part of the code mainly does the initialization work and parses the configuration file

This part of the code is mainly to execute SQL

First, let’s analyze the first part

 // read the configuration file
 InputStream in=Resources.getResourceAsStream("SqlMapConfig.xml");
 // create a constructor object for SqlSessionFactory
 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
 // create factory object SqlSessionFactory with constructor
 SqlSessionFactory factory= builder.build(in);
Copy the code

Start on source code:

// 1. The build we originally called
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
// Overloaded methods are called
        return this.build((InputStream)inputStream, (String)null, properties);
    }
// 2. Overloaded methods called
 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
 
            // XMLConfigBuilder is a class that parses the configuration file of Mybatis
            XMLConfigBuilder e = new XMLConfigBuilder(inputStream, environment, properties);
 
           // call an overloaded method of buid. E.p arse() returns the Configuration object
            var5 = this.build(e.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
 
            try {
                inputStream.close();
            } catch(IOException var13) { ; }}return var5;
    }
 
Copy the code

Solution: What is the object of Configuration

The object of Configuration is similar to that of an XML Configuration file

For example, tags in an XML configuration file include:

Properties, Settings, typeAliases, typeHandlers, objectFactory, Mappers, etc

A member variable of the Configuration class

public class Configuration {
    protected Environment environment;
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel;
    protected boolean cacheEnabled;
    protected boolean callSettersOnNulls;
    protected boolean useActualParamName;
    protected boolean returnInstanceForEmptyRow;
    protected String logPrefix;
    protected Class<? extends Log> logImpl;
    protected Class<? extends VFS> vfsImpl;
    protected LocalCacheScope localCacheScope;
    protected JdbcType jdbcTypeForNull;
    protected Set<String> lazyLoadTriggerMethods;
    protected Integer defaultStatementTimeout;
    protected Integer defaultFetchSize;
    protected ExecutorType defaultExecutorType;
    protected AutoMappingBehavior autoMappingBehavior;
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
    protected Properties variables;
    protected ReflectorFactory reflectorFactory;
    protected ObjectFactory objectFactory;
    protected ObjectWrapperFactory objectWrapperFactory;
    protected boolean lazyLoadingEnabled;
    protected ProxyFactory proxyFactory;
    protected String databaseId;
    protectedClass<? > configurationFactory;protected final MapperRegistry mapperRegistry;
    protected final InterceptorChain interceptorChain;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final LanguageDriverRegistry languageRegistry;
    protected final Map<String, MappedStatement> mappedStatements;
    protected final Map<String, Cache> caches;
    protected final Map<String, ResultMap> resultMaps;
    protected final Map<String, ParameterMap> parameterMaps;
    protected final Map<String, KeyGenerator> keyGenerators;
    protected final Set<String> loadedResources;
    protected final Map<String, XNode> sqlFragments;
    protected final Collection<XMLStatementBuilder> incompleteStatements;
    protected final Collection<CacheRefResolver> incompleteCacheRefs;
    protected final Collection<ResultMapResolver> incompleteResultMaps;
    protected final Collection<MethodResolver> incompleteMethods;
    protected final Map<String, String> cacheRefMap;
    // omit some source code
}
Copy the code

The essence of initialization is to encapsulate the data in the XML Configuration file into the properties of the Configuration class.

Next, we continue to analyze the method e. press

// Go to prase in XMLConfigBuilder
 public Configuration parse(a) {
        if(this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            
            this.parsed = true;
            // Parser is an XPathParser object that reads data from nodes. < Configuration > is a MyBatis configuration file
            // the top-level tag in
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration; }}// Configuration method (parseConfiguration). This method encapsulates the tags in the XML Configuration file in the Configuration property
private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties e = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(e);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(e);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ var3, var3); }}Copy the code

At this point the parsing of the XML is complete, and then we go back to the overload method of build

Var5 = this.build(e.parse());
// Pass in the Configuration object as an entry and return a SessionFactory
public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
Copy the code

Now comes the second part of executing the SQL

SqlSession is an interface that has a default implementation class DefaultSqlSession. A session requires only one SqlSession, which needs to be closed () upon completion.

Executor is also an interface. It has three implementation classes, BatchExecutor (reuses statements and performs batch updates), ReuseExecutor (reuses prepared statements), and SimpleExecutor (plain Executor, default).

// Create a SqlSession using SqlSessionFactory
SqlSession session=factory.openSession();
Copy the code

Get the session

// enter openSession in DefaultSqlSessionFactory
  public SqlSession openSession(a) {
        //getDefaultExecutorType() returns a SimpleExecutor
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null.false);
    }
 
 
/ / 7, into the openSessionFromDataSource this method
/ / ExecutorType as the Executor of the type, TransactionIsolationLevel for transaction isolation level, whether the autoCommit open transactions
// Multiple overloaded openSession methods can specify the Executor type of the SeqSession obtained and the handling of the transaction
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
 
        DefaultSqlSession var8;
        try {
            Environment e = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e);
            tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit);
 
            // Create an Executor of the specified type based on the arguments
            Executor executor = this.configuration.newExecutor(tx, execType);
 
            // Return DefaultSqlSession
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }
 
        return var8;
    }
Copy the code

Let’s go back to the previous test code

 // Create a proxy object for the DAO interface using SqlSession
  UserMapper userDao =session.getMapper(UserMapper.class);
  // Use proxy objects to perform query methods
  List<User> list=userDao.findUser();
Copy the code

The Mapper interface class does not implement this interface class, but allows you to call the methods in it. This is actually using JDK dynamic proxy technology in design mode. If you are not very clear about this dynamic proxy, you can read my blog. Blog.csdn.net/qq_30353203…

MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry Each interface corresponds to a factory. Mappers can configure the package path of the interface, or a specific interface class

When the Mappers tag is parsed, it determines that it is parsing a Mapper configuration file and then wraps the add, delete, change check tag 1 in the corresponding configuration file into an MappedStatement object, which is stored in mappedStatements.

When mappers are parsed to an interface, the MapperProxyFactory object is created and stored in the HashMap, key = the bytecode object of the interface, Value = MapperProxyFactory object corresponding to this interface.

// Enter the MapperRegistry class
public class MapperRegistry {
    private final Configuration config;
    // Create a HashMap where key is the bytecode object and value is the MapperProxyFactory for the corresponding interface class
    private finalMap<Class<? >, MapperProxyFactory<? >> knownMappers =new HashMap();
        
        // Determine the type and, if it is the parsed interface type, store it in the HashMap
        public <T> void addMapper(Class<T> type) {
        if(type.isInterface()) {
            if(this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
 
            boolean loadCompleted = false;
 
            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if(! loadCompleted) {this.knownMappers.remove(type); }}}} omit part of the source}Copy the code

Mybatis uses JDK dynamic proxy to create proxy objects.

// Create a proxy object for the DAO interface using SqlSession
 UserMapper userDao =session.getMapper(UserMapper.class);
Copy the code
// Enter the DefaultSqlSession class, which implements the SqlSession interface
public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }
 
// Go to the getMapper method in the Configuration class
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
 
// Enter the getMapper method of the MapperRegistry class
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory = MapperProxyFactory = MapperProxyFactory = MapperProxyFactory
        MapperProxyFactory mapperProxyFactory = 
                (MapperProxyFactory)this.knownMappers.get(type);
        if(mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                // Instantiate objects through dynamic proxy factories
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: "+ var5, var5); }}}// Enter the newInstance method in MapperProxyFactory
 public T newInstance(SqlSession sqlSession) {
        // Create the JDK dynamic proxy Handler class
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, 
        this.methodCache);
        // Call the constructor newInstance
        return this.newInstance(mapperProxy);
    }
 
// This constructor, an overloaded method, is returned by a new example created by the dynamic proxy. NewProxyInstance has three incoming arguments, the last of which needs to implement the InvocationHandler interface class. The returned object is the dynamic proxy object that executes the Invoke method
protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }
 
MapperProxy implements the InvocationHandler interface class
public class MapperProxy<T> implements InvocationHandler.Serializable {
 
 private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;
 
    // The proxy object in each session is different from the proxy object in each session.
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache; }}Copy the code

After dynamic proxy returns an instance, we can call the method in the Mapper interface, indicating that the method has been implemented for us in the Invoke method in MapperProxy.

// Go to the invoke method in MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
 
            if(Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
 
            if(this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args); }}catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
 
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        // This is the important point: MapperMethod finally calls the executing party
        return mapperMethod.execute(this.sqlSession, args);
    }
 
// Enter the excute method
public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        
// Execute different CRUDS depending on the order of command transfer, still calling SqlSession methods.
switch(null.$SwitchMap$org$apache$ibatis$mapping$SqlCommandType[this.command.getType().ordinal()]) {
        case 1:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case 2:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case 3:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case 4:
            if(this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if(this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if(this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if(this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;
        case 5:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }
 
        if(result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method \'" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            returnresult; }}Copy the code

At this point, the principle of Mybatis, source code analysis is over