MyBatis almost in my just learning programming has been in use, has not been to see its source code, this time, just to study, the source code to see again, there is no small harvest, specially arranged under, if there is any problem, welcome to point out

An overview of the

Create a mybatis_config. XML configuration file, create a mapper interface, create a mapper. XML file, and then call the service layer. Do not analyze the source code for the moment, if you assume that you develop such an ORM framework, the function of completely drink MyBatis the same, that in front of their own problems have a total of three as follows

  • How to encapsulate the configuration (database link address, user name, password) so that you only register once and don’t need to deal with this later
  • How to bind mapper interface to mapper.xml file
  • How do YOU generate a proxy object that allows a method in the interface to find the corresponding Mapper statement, and then take the argument in to execute it

With these problems, step by step is better to learn the source code, of course, alone with these problems is not fully developed, here I will as far as possible with about it, if some of the more unpopular configuration, may have to go to their own in-depth study.

Review of JDBC& native MyBatis calls

First, MyBatis is a wrapper around traditional JDBC, so let’s review traditional JDBC first

JDBC

public class User {

    / / the user table id
    private Integer id;

    / / user name
    private String username;
    
    public Integer getId(a) {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername(a) {
        return username;
    }

    public void setUsername(String username) {
        this.username = username == null ? null : username.trim();
    }

    @Override
    public String toString(a) {
        return "User [id=" + id + ", username="+ username ; }}Copy the code
public class JDBCDemo {
    // Create a database connection
    private static Connection getConnection(a) {
        Connection connection = null;
        try {
            // Load the user driver
            Class.forName("com.mysql.cj.jdbc.Driver");
            // Address to connect to the database
            String url = "JDBC: mysql: / / 127.0.0.1:3306 / test1";
            // User name of the database
            String user = "root";
            // Database password
            String password = "12345678";
            // Get a database connection
            connection = DriverManager.getConnection(url, user, password);
        } catch (ClassNotFoundException e) {
            System.out.println(JDBCDemo.class.getName() + "Database driver package not found!");
            return null;
        } catch (SQLException e) {
            System.out.println(JDBCDemo.class.getName() + "SQL statement problem, query failed!");
            return null;
        }
        return connection;// Return the connection
    }

    public User getUser(int id) {
        // Get a connection to the database
        Connection connection = getConnection();
        // Declare a null preprocessed Statement
        PreparedStatement ps = null;
        // Declare a result set to store the results of SQL queries
        ResultSet rs = null;
        try {
            // precompile the SQL for the User table
            ps = connection.prepareStatement("select * from user where id=?");
            // Set the parameter Id to the data condition
            ps.setInt(1, id);
            // Execute the query statement. Returns the result to the ResultSet ResultSet
            rs = ps.executeQuery();
            // iterate over the number from the result set
            while (rs.next()) {
                // Retrieve the user ID of Statement
                int user_id = rs.getInt("id");
                // Retrieve the username of Statement
                String username = rs.getString("username");
                User user = new User();
                // In the user object
                user.setId(user_id);
                user.setUsername(username);
                returnuser; }}catch (SQLException e) {
            e.printStackTrace();
        } finally {
            this.close(rs, ps, connection);
        }
        return null;
    }

    /** * Check whether the database is closed *@paramRs view result set is hysteresis closed *@paramWhether the STMT preprocessing SQL is closed *@paramConn Whether the database connection is closed */
    private void close(ResultSet rs, Statement stmt, Connection conn) {
        try {
            if(rs ! =null) { rs.close(); }}catch (SQLException e) {
            System.out.println(JDBCDemo.class.getName() + "ResultSet failed to close!);
        }
        try {
            if(stmt ! =null) { stmt.close(); }}catch (SQLException e) {
            System.out.println(JDBCDemo.class.getName() + "Statement failed to close!");
        }
        try {
            if(conn ! =null) { conn.close(); }}catch (SQLException e) {
            System.out.println(JDBCDemo.class.getName() + "Connection closed failed!"); }}public static void main(String[] args) {
        User id = 1
        User user = new JDBCDemo().getUser(1);
        // Print out the queried dataSystem.out.println(user); }}Copy the code

Here is a brief introduction to the three main classes, and how to encapsulate them later

  • DriverManager: When calling a methodgetConnectionWhen,DriverManagerAn attempt is made to find the appropriate driver from the driver loaded in initialization and to explicitly load the driver using the same classloader as the current applet or application.
  • Connection: Connection to the database. Execute the SQL statement and return the result in the context of the connection.
  • Statement: An object used to execute a static SQL Statement and return its generated results.
  • ResultSet: indicates the database ResultSet

Native MyBatis call

After a general understanding we can look at the native MyBatis written mybatis_config


      
<! Licensed under the Apache License, Version 2.0 (the "License"); Copyright 2009-2017 The original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -->
<! DOCTYPEconfiguration
        PUBLIC "- / / mybatis.org//DTD Config / 3.0 / EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <! -- autoMappingBehavior should be set in each test case -->

    <environments default="development">
        <environment id="development">
           <! -- Configure transaction manager -->
            <transactionManager type="JDBC" />
            <! Configure the data source type and the database link status.
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.hsqldb.jdbcDriver"/>
                <property name="url" value="JDBC: mysql: / / 127.0.0.1:3306 / test"/>
                <property name="username" value="root"/>
                <property name="password" value="12345678"/>

            </dataSource>
        </environment>
    </environments>
    <! -- Mapper file location -->
    <mappers>
        <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
    </mappers>

</configuration>
Copy the code

mapper

public interface AutoConstructorMapper {
  PrimitiveSubject getSubject(final int id);

  @Select("SELECT * FROM subject")
  List<PrimitiveSubject> getSubjects(a);

  @Select("SELECT * FROM subject")
  List<AnnotatedSubject> getAnnotatedSubjects(a);

  @Select("SELECT * FROM subject")
  List<BadSubject> getBadSubjects(a);

  @Select("SELECT * FROM extensive_subject")
  List<ExtensiveSubject> getExtensiveSubjects(a);
}
Copy the code

xml


      
<! Licensed under the Apache License, Version 2.0 (the "License"); Copyright 2009-2017 The original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -->
<! DOCTYPEmapper
    PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.apache.ibatis.autoconstructor.AutoConstructorMapper">
  <select id="getSubject" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject">
    SELECT * FROM subject WHERE id = #{id}
  </select>
</mapper>
Copy the code

use

private static SqlSessionFactory sqlSessionFactory;

@BeforeAll
static void setUp(a) throws Exception {
  // create a SqlSessionFactory
  try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) {
    sqlSessionFactory = newSqlSessionFactoryBuilder().build(reader); }}@Test
void fullyPopulatedSubject(a) {
  try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
    final Object subject = mapper.getSubject(1); assertNotNull(subject); }}Copy the code

Parsing mybatis_config. XML

How does MyBatis parse mybatis_config.xml

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      reader.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.}}}Copy the code

The MyBatis core class Configuration is created using the XMLConfigBuilder:: Parse method. MyBatis loads all the Configuration files at the initialization time, and saves all the result files in Configuration. It is only created at the time of initialization, it contains all the configuration of mybatis-config. XML, also includes all the resolved Mapper, and its mapping relationship, after loading is to be able to find in this, is the core class of Mybatis. The member variables are listed below, and you can see quite a bit of mybatis_config by name. (Member variables are as follows, you can first feel the mybatis source code without almost no comments, by the way feel the pain OF my source code QAQ)

public class Configuration {
    protected Environment environment;
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled = true;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled = true;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel = true;
    protected boolean cacheEnabled = true;
    protected boolean callSettersOnNulls;
    protected boolean useActualParamName = true;
    protected boolean returnInstanceForEmptyRow;
    protected boolean shrinkWhitespacesInSql;

    protected String logPrefix;
    protected Class<? extends Log> logImpl;
    protected Class<? extends VFS> vfsImpl;
    protectedClass<? > defaultSqlProviderType;protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
    protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
    protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals"."clone"."hashCode"."toString"));
    protected Integer defaultStatementTimeout;
    protected Integer defaultFetchSize;
    protected ResultSetType defaultResultSetType;
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

    protected Properties variables = new Properties();
    protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

    protected boolean lazyLoadingEnabled = false;
    protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

    protected String databaseId;
    /**
     * Configuration factory class.
     * Used to create Configuration for loading deserialized unread properties.
     *
     * @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
     */
    protectedClass<? > configurationFactory;protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
    // Alias of a common class
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
        .conflictMessageProducer((savedValue, targetValue) ->
            ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
    protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
    protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
    protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
    protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
    protected final Set<String> loadedResources = new HashSet<>();
    protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

    protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
    protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
    protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
    protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

    /* * A map holds cache-ref relationship. The key is the namespace that * references a cache bound to another namespace and the value is the * namespace which the actual cache is bound to. */
    protected final Map<String, String> cacheRefMap = newHashMap<>(); . Method skim}Copy the code

XPathParser usage

Let’s take a look at the constructor of XMLConfigBuilder

public class XMLConfigBuilder extends BaseBuilder {
    private boolean parsed;// Whether mybatis-config.xml has been parsed
    / / parsing mybatis - config. XML
    private final XPathParser parser;
    private String environment;
    // Create and cache a Reflctor object
    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
    public XMLConfigBuilder(Reader reader, String environment, Properties props) {
      this(new XPathParser(reader, true, props, newXMLMapperEntityResolver()), environment, props); }}Copy the code

It wraps an XPathParser object inside, so let’s look at how it’s used

<employee id="${id_var}">
  <blah something="that"/>
  <first_name>Jim</first_name>
  <last_name>Smith</last_name>
  <birth_date>
    <year>1970</year>
    <month>6</month>
    <day>15</day>
  </birth_date>
  <height units="ft">5.8</height>
  <weight units="lbs">200</weight>
  <active bot="YES" score="3.2">true</active>
</employee>

Copy the code
  @Test
  void constructorWithInputStreamValidationVariablesEntityResolver(a) throws Exception {

    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XPathParser parser = new XPathParser(inputStream, false.null.null);
      System.out.println(parser.evalLong("/employee/birth_date/year").equals(1970L));//true
      System.out.println(parser.evalNode("/employee/birth_date/year").getLongBody().equals(1970L));//true
      System.out.println(parser.evalNode("/employee").evalString("@id"));//${id_var}
      System.out.println(parser.evalNode("/employee/active").getDoubleAttribute("score"));/ / 3.2}
  }
Copy the code

XPathParser encapsulates JDK native Document, EntityResolver, XPath, and Properties objects, making it easier to parse XML files. I wrote an example above, so that each value can be obtained.

The node analytical

After the XMLConfigBuilder is initialized, its parse() method is called. Parsing XML, mapper parsing, and Mapper binding are all done within this method. So let’s look at this method

/** * Parses mybatis-config. XML * initializes the call *@return* /
public Configuration parse(a) {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  // Find the configuration node resolution
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
Copy the code

parser.evalNode("/configuration")This step is fetchconfigurationNode, the next step is atparseConfigurationMethod to resolve the individual child nodes

private void parseConfiguration(XNode root) {
  try {
    // Parse each node
    // issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    // Class name registration
    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"));
    / / parsing typeHandler
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code

You should be familiar with mybatis_config. XML by placing each node in +Element. Since we’ve only configured environments and Mappers, let’s look at those two methods.

Resolve the Environment

private void environmentsElement(XNode context) throws Exception {
  if(context ! =null) {
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        // Create datasource, datasourceFactory, and set the corresponding properties
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
        break; }}}}Copy the code

Transaction

MyBatis has two subclasses of Transaction

TransactionDefines the

public interface Transaction {

  /** * Get the corresponding database connection object */
  Connection getConnection(a) throws SQLException;

  /** * Commit transaction */
  void commit(a) throws SQLException;

  /** * rollback transaction */
  void rollback(a) throws SQLException;

  /** * Close the database connection */
  void close(a) throws SQLException;

  /** * get transaction timeout */
  Integer getTimeout(a) throws SQLException;

}
Copy the code

JdbcTransaction encapsulates the transaction isolation level, connection, and data source objects, and basically calls the corresponding method Connction

public class JdbcTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(JdbcTransaction.class);

     // Transactions correspond to database connections
     protected Connection connection;
     // The database connection belongs to the datasource
     protected DataSource dataSource;
     // Transaction isolation level
     protected TransactionIsolationLevel level;
     // Whether to commit automatically
     protected boolean autoCommit;
     @Override
     public void rollback(a) throws SQLException {
       if(connection ! =null && !connection.getAutoCommit()) {
         if (log.isDebugEnabled()) {
           log.debug("Rolling back JDBC Connection [" + connection + "]"); } connection.rollback(); }}.. Other slightly}Copy the code

Another is ManagedTransaction, which hands commit and rollback to the container implementation

public class ManagedTransaction implements Transaction { ... @override public void commit() throws SQLException {// Does nothing} @override public void rollback() throws SQLException { // Does nothing } }Copy the code

DataSource

The Configuration constructor registers a bunch of class aliases and creates them by reflection.

public Configuration() { typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); . Other slightly}Copy the code

A DataSource is an encapsulation of a driver. Let’s take a look at how MyBatis encapsulates. UnpooledDataSourceThe core method is as follows, is not very friendly, in fact, is a kind of encapsulation of traditional JDBC. This is not much different from the example written above, but the main difference is that it can support multiple, encapsulated.

/** * implements the getConnection() method and its overloaded method *@author Clinton Begin
 * @author Eduardo Macarron
 */
public class UnpooledDataSource implements DataSource {

  // Load the classloader for the Driver class
  private ClassLoader driverClassLoader;
  // Database connection driver configuration
  private Properties driverProperties;
  // Cache all registered database connection drivers
  private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();

  // The driver name of the database connection
  private String driver;
  // Database connection URL
  private String url;
  / / user name
  private String username;
  / / password
  private String password;
  // Whether to commit automatically
  private Boolean autoCommit;
  // Transaction isolation level
  private Integer defaultTransactionIsolationLevel;
  private Integer defaultNetworkTimeout;

  static {
    // Register the JDBC driver with DriverManager
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while(drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); }}@Override
  public Connection getConnection(String username, String password) throws SQLException {
    return doGetConnection(username, password);
  }

  private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if(driverProperties ! =null) {
      props.putAll(driverProperties);
    }
    if(username ! =null) {
      props.setProperty("user", username);
    }
    if(password ! =null) {
      props.setProperty("password", password);
    }
    return doGetConnection(props);
  }

  // Create a new connection each time
  private Connection doGetConnection(Properties properties) throws SQLException {
    // Initialize the database driver
    initializeDriver();
    // Create a real database connection
    Connection connection = DriverManager.getConnection(url, properties);
    // Configure the autoCommit and isolation levels for database connections
    configureConnection(connection);
    return connection;
  }

  private synchronized void initializeDriver(a) throws SQLException {
    if(! registeredDrivers.containsKey(driver)) {// Check whether the driver is registeredClass<? > driverType;try {
        if(driverClassLoader ! =null) {
          driverType = Class.forName(driver, true, driverClassLoader);// Register the driver
        } else {
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();// Create the Driver object
        DriverProxy is an inner class defined in UnpooledDataSource, which is the static proxy class of the Driver
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        // Add the driver to registeredDrivers
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: "+ e); }}}Copy the code

This should make the whole line very clear. Let’s look at the tradition

  private void configureConnection(Connection conn) throws SQLException {
    if(defaultNetworkTimeout ! =null) {
      conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
    }
    if(autoCommit ! =null&& autoCommit ! = conn.getAutoCommit()) { conn.setAutoCommit(autoCommit); }if(defaultTransactionIsolationLevel ! =null) {
      // Set transaction isolation levelconn.setTransactionIsolation(defaultTransactionIsolationLevel); }}private static class DriverProxy implements Driver {
    private Driver driver;

    DriverProxy(Driver d) {
      this.driver = d; }... Omitted}... Slightly}Copy the code

You think that’s it? It’s not. PooledDataSource = ‘datasource’; PooledDataSource = ‘datasource’; PooledDataSource = ‘datasource’; If you’re interested, you can read about it. If you’re not, you can read about it.

Looking at PoolState first, the author maintains the Connection object with two lists.

Public class PoolState {// dataSource object protected PooledDataSource dataSource; // Protected final List<PooledConnection> idleConnections = new ArrayList<>(); // Protected Final List<PooledConnection> activeConnections = new ArrayList<>(); protected long requestCount = 0; Protected Long accumulatedRequestTime = 0; Protected long accumulatedCheckoutTime = 0; / / connections accumulated checkoutTime length protected long claimedOverdueConnectionCount = 0; / / the timeout connection number protected long accumulatedCheckoutTimeOfOverdueConnections = 0; // Protected long accumulatedWaitTime = 0; // Cumulative wait time protected long hadToWaitCount = 0; // Wait times protected Long badConnectionCount = 0; // Invalid connection number}Copy the code

PooledDataSource = MyBatis PooledDataSource = MyBatis PooledDataSource = MyBatis PooledDataSource = MyBatis PooledDataSource

public class PooledDataSource implements DataSource {
protected void pushConnection(PooledConnection conn) throws SQLException {

  synchronized (state) {
    state.activeConnections.remove(conn);//
    if (conn.isValid()) {// Whether the connection is valid
      // Check whether the number of idle connections has reached the upper limit
      if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
        state.accumulatedCheckoutTime += conn.getCheckoutTime();// Cumulative checkout duration
        if(! conn.getRealConnection().getAutoCommit()) {// Roll back uncommitted transactions
          conn.getRealConnection().rollback();
        }
        / / create a javax.sql.pooledconnection
        // the realConnection is used
        PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
        state.idleConnections.add(newConn);// Add to the inactive collection
        newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
        newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
        conn.invalidate();// Set the original PooledConnection object to invalid
        if (log.isDebugEnabled()) {
          log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
        }
        state.notifyAll();
      } else {
        // The free collection is already full
        state.accumulatedCheckoutTime += conn.getCheckoutTime();
        if(! conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.getRealConnection().close();if (log.isDebugEnabled()) {
          log.debug("Closed connection " + conn.getRealHashCode() + "."); } conn.invalidate(); }}else {
      if (log.isDebugEnabled()) {
        log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection."); } state.badConnectionCount++; }}}private PooledConnection popConnection(String username, String password) throws SQLException {
  boolean countedWait = false;
  PooledConnection conn = null;
  long t = System.currentTimeMillis();
  int localBadConnectionCount = 0;

  // Spin if no connection object is retrieved
  while (conn == null) {
    synchronized (state) {
      /** * There is no idle connection processing */
      //idleConnections Idle connection
      if(! state.idleConnections.isEmpty()) {// Free connection
        // Pool has available connection
        conn = state.idleConnections.remove(0);// Get the connection
        if (log.isDebugEnabled()) {
          log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); }}else {
        // Pool does not have available Connection If the number of active connections does not reach the upper limit, a new connection can be created
        if (state.activeConnections.size() < poolMaximumActiveConnections) {
          // Can create new Connection creates a new database connection and encapsulates it as a PooledConnection object
          conn = new PooledConnection(dataSource.getConnection(), this);
          if (log.isDebugEnabled()) {
            log.debug("Created connection " + conn.getRealHashCode() + "."); }}else {// If the number of active connections reaches the maximum, new connections cannot be created
          // Cannot create new Connection Gets the first active connection created
          PooledConnection oldestActiveConnection = state.activeConnections.get(0);
          long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
          if (longestCheckoutTime > poolMaximumCheckoutTime) {// Check whether the connection timed out
            // Can claim expected connection statistics about timeout connections
            state.claimedOverdueConnectionCount++;
            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
            state.accumulatedCheckoutTime += longestCheckoutTime;
            // Move the timeout connection out of the activeConnections collection
            state.activeConnections.remove(oldestActiveConnection);
            // If the timeout connection is not submitted, it will be automatically rolled back
            if(! oldestActiveConnection.getRealConnection().getAutoCommit()) {try {
                oldestActiveConnection.getRealConnection().rollback();
              } catch (SQLException e) {
                /* Just log a message for debug and continue to execute the following statement like nothing happened. Wrap the bad connection with a new PooledConnection, this will help to not interrupt current executing thread and give current thread a chance to join the next competition for another valid/good database connection. At the end of this loop, bad {@link @conn} will be set as null. */
                log.debug("Bad connection. Could not roll back"); }}// Create a new PooledConnection object and reuse the old Collection object
            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
            // reuse the timestamp
            conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
            conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
            // Timeout PooledConnection set to invalid
            oldestActiveConnection.invalidate();
            if (log.isDebugEnabled()) {
              log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); }}else {
            // If there are no idle connections, no new connections can be created, and no timeout connections, you can only block and wait
            try {
              if(! countedWait) { state.hadToWaitCount++;// Count the wait times
                countedWait = true;
              }
              if (log.isDebugEnabled()) {
                log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
              }
              long wt = System.currentTimeMillis();
              state.wait(poolTimeToWait);
              state.accumulatedWaitTime += System.currentTimeMillis() - wt;
            } catch (InterruptedException e) {
              break; }}}}if(conn ! =null) {
        // ping to server and check the connection is valid or not
        if (conn.isValid()) {// Check that the connection is valid
          //
          if(! conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); conn.setCheckoutTimestamp(System.currentTimeMillis()); conn.setLastUsedTimestamp(System.currentTimeMillis()); state.activeConnections.add(conn);/ / statistics
          state.requestCount++;
          state.accumulatedRequestTime += System.currentTimeMillis() - t;
        } else {
          // The current connection is invalid, continue to select
          if (log.isDebugEnabled()) {
            log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
          }
          state.badConnectionCount++;
          localBadConnectionCount++;
          conn = null;
          if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
            if (log.isDebugEnabled()) {
              log.debug("PooledDataSource: Could not get a good connection to the database.");
            }
            throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
          }
        }
      }
    }

  }

  if (conn == null) {
    if (log.isDebugEnabled()) {
      log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
    }
    throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
  }

  returnconn; }... Slightly}Copy the code

The TransactionFactory and DataSource are wrapped into the Environment, and the Environment is inserted into the Configuration, using the Builder mode. If you are interested in the Builder mode, check out my blog juejin.cn/post/698594…

Parsing Mapper

There are about four kinds of Mapper configuration, know this, look at the source code is very good to understand the content is much the same, let’s look at the resource parsing

public void parse(a) {
  // Check whether the mapping file has been loaded
  if(! configuration.isResourceLoaded(resource)) {// Bind SQL with XML
    configurationElement(parser.evalNode("/mapper"));// Process the mapper node
    // Add parsed XML to loadedResources
    configuration.addLoadedResource(resource);
    // Scan annotation binding SQL
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}
private void mapperElement(XNode parent) throws Exception {
  if(parent ! =null) {
    for (XNode child : parent.getChildren()) {
      //package is handled separately
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        // Parse the corresponding configuration
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if(resource ! =null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
            XMLMapperBuilder mapperParser = newXMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); }}else if (resource == null&& url ! =null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          try(InputStream inputStream = Resources.getUrlAsStream(url)){
            XMLMapperBuilder mapperParser = newXMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); }}else if (resource == null && url == null&& mapperClass ! =null) { Class<? > mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); }else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}
Copy the code

Mapperparser.parse (); mybatis_config specifies the path of mapper, which will load the specified resource. Will call the following method parses the select tag, add the parsed results configuration: : mappedStatements

public void parseStatementNode(a) {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if(! databaseIdMatchesCurrent(id, databaseId,this.requiredDatabaseId)) {
    return;
  }

  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered".false);

  // Process the include node first
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  String parameterType = context.getStringAttribute("parameterType"); Class<? > parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    // Configure according to useGeneratedKeys in global
    // Whether it is an INSERT statement, which determines whether to use the KeyGenerator interface
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }
  // Parse the original SQL statement and replace #{} with? The parameter
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  // Get the resultType value in the select tag
  String resultType = context.getStringAttribute("resultType");
  // If there is no corresponding class object in typeAliasRegistry, reflect the corresponding class objectClass<? > resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");
  // Generate mappedStatements and add mappedStatements to configuration
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
Copy the code

I believe you also see notes version of it is in the annotations by MapperRegistry: : addMapper specific process and the process of similar, It also parses the values of the above methods and adds them to the Configuration through the MapperBuilderAssistant because all the parses are crammed into the Configuration. Finally, You’re done inserting configuration into the DefaultSqlSessionFactory parsing section

Get SqlSession

Mybatis will give you DefaultSqlSession by default.

// Get the database connection from the data source
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    // Get the transactionFactory in the environment without creating one
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // Execute the SQL statement
    final Executor executor = configuration.newExecutor(tx, execType);
    / / create a sqlsession
    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(); }}Copy the code

When you get the Mapper, this is through dynamic proxy, Give you generate a proxy object final AutoConstructorMapper mapper. = sqlSession getMapper (AutoConstructorMapper. Class); The source code is as follows

@Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } 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); }} public T newInstance(SqlSession SqlSession) {// Create a proxy object final MapperProxy<T> MapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }Copy the code

The mapper method executes

And when we do that we just have to look at how the proxy is executed which is MapperProxy which internally encapsulates a PlainMethodInvoker, and the final execution is also the invoke method that calls this inner class, okay

private static class PlainMethodInvoker implements MapperMethodInvoker {
      private final MapperMethod mapperMethod;

      public PlainMethodInvoker(MapperMethod mapperMethod) {
        super(a);this.mapperMethod = mapperMethod;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
        //
        returnmapperMethod.execute(sqlSession, args); }}Copy the code

At execution time, we use the previously parsed parameters and construct a MapperMethod


    public class MapperMethod {

    // Specific implementation
    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;
      switch (command.getType()) {// Call different methods based on the SQL type
        case INSERT: {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.insert(command.getName(), param));
          break;
        }
        case UPDATE: {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.update(command.getName(), param));
          break;
        }
        case DELETE: {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.delete(command.getName(), param));
          break;
        }
        case SELECT:
          if (method.returnsVoid() && method.hasResultHandler()) {
            executeWithResultHandler(sqlSession, args);
            result = null;
          } else if (method.returnsMany()) {
            result = executeForMany(sqlSession, args);
          } else if (method.returnsMap()) {
            result = executeForMap(sqlSession, args);
          } else if (method.returnsCursor()) {
            result = executeForCursor(sqlSession, args);
          } else {
            // Parse the parameters
            Object param = method.convertArgsToSqlCommandParam(args);
            // Call sqlSession execution
            result = sqlSession.selectOne(command.getName(), param);
            if (method.returnsOptional()
                && (result == null || !method.getReturnType().equals(result.getClass()))) {
              result = Optional.ofNullable(result);
            }
          }
          break;
        case FLUSH:
          result = sqlSession.flushStatements();
          break;
        default:
          throw new BindingException("Unknown execution method for: " + command.getName());
      }
      if (result == null&& method.getReturnType().isPrimitive() && ! method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()
            + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
      }
      returnresult; }}Copy the code
@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.selectList(statement, parameter);
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  } else {
    return null; }}// Finally call this one
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    // Mybatis_config. XML was loaded into the configuration file
    MappedStatement ms = configuration.getMappedStatement(statement);
    //
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  } finally{ ErrorContext.instance().reset(); }}Copy the code

CachingExecutor (parameterObject); SqlSource (parameterObject); Sqlsource.getboundsql (parameterObject) is finally called; I’m going to create a new BoundSql, and in this SQL, I’ve got all the parameters

//
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Copy the code

The query operation is then performed, and eventually BaseExecutor is called to perform the query operation

@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    // Query level-1 cache
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if(list ! =null) {
      // Processing for stored procedures
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else{ list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
    // The query is complete
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482clearLocalCache(); }}return list;
}

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // Add placeholders to the cache
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    / / call the doQuery
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // Delete the placeholder
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}


@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally{ closeStatement(stmt); }}/ / for the Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}

/ / get the Connection
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    returnconnection; }}// Get this from BaseStatementHandler
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    statement = instantiateStatement(connection);
    setStatementTimeout(statement, transactionTimeout);
    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); }}/ / create a PreparedStatement
@Override
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 {
      returnconnection.prepareStatement(sql, keyColumnNames); }}else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    return connection.prepareStatement(sql);
  } else {
    returnconnection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); }}/ / PreparedStatementHandler execute queries, this should be very kind
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.handleResultSets(ps);
}

Copy the code

At this point, the whole query process of native MyBatis is over. Here, the value introduces the query process, and the process of adding, deleting and changing is almost the same. There is no further details here.

Reference — MyBatis Technology Insider