preface

Recently at the time of chat with fans fans asked JDBC and mybatis underlying implementation this a problem, but also more than one friend asked, so I seem to know the seriousness of the problem, and I spent two days to sort out your understanding of online access to the data and wrote the article, few words said, full of dry goods were in the following.

Before talking about the underlying implementation of MyBatis, let’s take a look at the basic knowledge of JDBC

JDBC is the most basic implementation of connecting to a database, and any operation to a database is based on JDBC

1. Register driver class.forname ("com.mysql.jdbc.Driver"); 2. Access to the database Connection Connection conn = DriverManager. GetConnection (url, user, p); 3. Create a statement object that sends SQL to data. Statement STMT = conn.createstatement (); SQL ResultSet rs = STmt.executeQuery (SQL)//select statement int updateaSum = stmt.executeUpdate(SQL)// INSERT,update The delete statement is 5. Processing result setwhile(rs.next()){rs.getString(column name) rs.getint (column name)} 6. Rs.close (); stmt.close(); conn.close();Copy the code

Sqlsession, Connection and Transaction analysis in Mybatis

Connection JDBC is the most basic API we use to interact with databases. Connection as a session for a particular database, in the context of a Connection, SQL statements are executed and results are returned. Sqlsession can be seen as a more advanced abstraction of Connection. From its method, it can be seen that it has more obvious operation characteristics. Transaction A Transaction is a representation of a relationship that succeeds or fails simultaneously when N(N>=1) operations are executed.

Let’s take a look at how SQLSession performs database operations:

 String resource = "mybatis-config.xml"; / / get the data flow configuration InputStream InputStream = Resources. The getResourceAsStream (resource); SqlSessionFactory SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); / / get sqlSession through sqlSessionFactory sqlSession sqlSession = sqlSessionFactory. OpenSession (); // Perform operations on the database try {TbUserMapper userMapper = SQLsession. getMapper(tbusermapper.class); TbUser user = new TbUser("liybk"."liybk"."186.."."123"); userMapper.insertUser(user); sqlSession.commit(); } finally {sqlsession.close (); }Copy the code

SqlSessionFactoryBuilder().build(inputStream)

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
Copy the code

The final execution returns the DefaultSqlSessionFactory constructor through a series of XML file parses

public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
Copy the code

The constructor simply initializes its Configuration property, which contains an Environment property. The Environment property contains data sources, connect, transactions, and other basic properties of database operations

public final class Environment { private final String id; private final TransactionFactory transactionFactory; private final DataSource dataSource; . }Copy the code

Ok, we return to the main steps SqlSession SqlSession = sqlSessionFactory. OpenSession () method Its implementation class is DefaultSqlSessionFactory, the purpose is to obtain the sqlSession

public SqlSession openSession() {
        return/ / call the class openSessionFromDataSource method of enclosing openSessionFromDataSource (enclosing configuration. GetDefaultExecutorType (), (TransactionIsolationLevel)null,false); } / / openSessionFromDataSource methods private SqlSession openSessionFromDataSource (ExecutorTypeexecType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; Try {/ / get the Environment Environment Environment = enclosing configuration. GetEnvironment (); // Get the TransactionFactory from the Environment; TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); / / create Transaction object on the database connection Transaction tx. = transactionFactory newTransaction (environment. GetDataSource (), level, the autoCommit mode). / / create Executor object Executor Executor = this. Configuration. NewExecutor (tx,execType); // Create a SQLSession object. 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

To create a SQLSession, mybatis goes through the following steps:

  1. Get the Environment (data source) from the core configuration file mybatis-config.xml;
  2. Get DataSource from Environment;
  3. Get the TransactionFactory from the Environment;
  4. SQL > select * from DataSource;
  5. Create a Transaction object on the acquired database connection.
  6. Create the Executor object (which is so important that virtually all sqlSession operations are done through it)
  7. Create the SQLSession object.

We object to the Executor focuses on, because the object is to execute SQL implementation class: enter the Executor Executor = enclosing configuration. NewExecutor (tx, execType) method

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
Copy the code

As you can see, CachingExecutor is created if cache is enabled, and normal Executor is created otherwise. Normal Executor has three basic types. BatchExecutor is used to execute batch SQL operations. ReuseExecutor reuses Statement to perform SQL operations, and SimpleExecutor simply executes SQL. CachingExecutor, on the other hand, looks in the cache before querying the database, and if it doesn’t find one, calls the delegate (the Executor object passed in when it was constructed) to query from the database and store the query results in the cache. Executor objects can be intercepted by plug-ins. If a plug-in for Executor type is defined, the resulting Executor object is a proxy object inserted by each plug-in. Let’s take a quick look at how SimpleExecutor, the simplest of the three basic types, executes SQL

public class SimpleExecutor extends BaseExecutor {
    public SimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }

    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; int var6; Try {// get the Configuration attribute. Configuration = Ms. GetConfiguration (); / / get StatementHandler StatementHandler handler = configuration. NewStatementHandler (this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null); PrepareStatement STMT = this.prepareStatement(handler, Ms. GetStatementLog ()); Var6 = handler.update(STMT); } finally { this.closeStatement(stmt); }return var6;
    }
Copy the code

StatementHandler

As you can see, Executor is also essentially a hands-off Executor, and StatementHandler does the work. After the Executor passes the baton to the StatementHandler, it’s up to the StatementHandler to do its job. Let’s first look at how StatementHandler is created.

publicStatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,  
        ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {  
   StatementHandler statementHandler = newRoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);  
   statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);  
   returnstatementHandler;  
}  

Copy the code

You can see that each StatementHandler created is a RoutingStatementHandler, which is just a distributor with a property delegate that specifies which specific StatementHandler to use. The StatementHandler options are SimpleStatementHandler, PreparedStatementHandler, and CallableStatementHandler. Which one to choose is specified in each statement of the Mapper configuration file. The default is PreparedStatementHandler. Also note that StatementHandler can be intercepted by an interceptor, and like Executor, the intercepted object is a proxy object. For example, many physical paging implementations, such as database physical paging, are implemented in this place using interceptors

We don’t know what relationships are made between the first two parts of the code block before executing the Executor.

. / / get sqlSession through sqlSessionFactory sqlSession sqlSession = sqlSessionFactory. OpenSession (); 1, TbUserMapper userMapper = sqlsession.getMapper (tbusermapper.class); 2, TbUser user = new TbUser("liybk"."liybk"."186.."."123"); 3, userMapper. InsertUser (user);Copy the code

So what does this mapper do, how is it created, how does it relate to SQLSessions and so on? We enter the method:

public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }
Copy the code

The final call is to the mapperRegistry method of Configuration

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
//mapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }


//mapperProxyFactory
 public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }
Copy the code

As you can see, mapper is a proxy object that implements the interface that is passed in as Type, which is why mapper objects can be accessed directly through the interface. You can also see that the SQLSession object is passed in when the Mapper proxy object is created, thus associating the SQLSession as well. We enter the Proxy newProxyInstance (this. MapperInterface. GetClassLoader (), new Class [] {enclosing mapperInterface}, mapperProxy) method, Notice that this method passes in the parameter mapperProxy

@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); Final Class<? >[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager();if(sm ! = null) {/ / permission check checkProxyAccess (Reflection) getCallerClass (), loader, intfs); } /* * Look up or generate the designated proxy class. > cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try {if(sm ! = null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } // Get final Constructor<? > cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h;if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Voidrun() {
                        cons.setAccessible(true);
                        returnnull; }}); } // Pass the mapperProxy argument to InvocationHandlerreturn cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else{ throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); }}Copy the code

MapperProxy is a subclass of InvocationHandler, followed by cons.newInstance(new Object[]{h})

 public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if(! override) {if(! Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<? >caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers); }}if((clazz.getModifiers() & Modifier.ENUM) ! = 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

Copy the code

Invoke (mapperProxy) {invoke (mapperProxy); invoke (mapperProxy) {invoke (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);
        return mapperMethod.execute(this.sqlSession, args);
    }
Copy the code

Invoke transfers execution control to MapperMethod. Let’s look at how the MapperMethod works:

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            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);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            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

As you can see, MapperMethod is like a distributor that selects different SQLSession methods to execute based on the parameters and return value types. The Mapper object is then truly associated with the SQLSession.

Finally:

Everyone have what don’t understand welcome to leave a message below discussion, or can also ask me a private letter, generally I see after work will reply, sometimes more busy when did not see the hope of understanding, can also pay attention to my public number: bright future!