• J3 – leitian
  • MyBatis # Mapper analysis

Recently idle fast have more than half a month, has not been able to lift interest to see a few books again (without the previous crazy desire to suck knowledge 😓).

However, I don’t know what is wrong in the past couple of days and I really want to write something, but I don’t know what to write. Therefore, I started with some techniques that I often used in my work but did not have in-depth knowledge. Finally, I chose the knowledge content of Mapper file in MyBatis after thinking it over.

I used to know that if YOU write a Mapper interface, you write a mapper. XML file and then you associate the location of the Mapper interface with the location of the mapper. XML file through the mybatisconfig.xml configuration file and you can easily access the database. But the reason is hard to pin down.

Now that we have figured out the antecedent cause, we can learn from it together!

Basic use of MyBatis

It all starts with the simplest, so let’s go over the basics (no, no, no, basic Hello World).

Steps:

1. First we will create a Maven project

Add MyBatis dependency and MySQL dependency as follows:

  • <! -- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    Copy the code

3. Add another test unit dependency, which will be tested by the test unit later

  • <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    Copy the code

OK, by this step, the basic environment of the project has been set up, the following is the formal use of MyBatis framework related content.

1.1 Writing a Configuration File

Create the following two configuration files under the resources directory:

  1. Here we prepare the configuration class for database connection information: jdbc.properties

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=JDBC: mysql: / / 127.0.0.1:3306 / mybatistest? useUnicode=true& characterEncoding=utf-8
    jdbc.username=root
    jdbc.password=root
    Copy the code
  2. Next comes the most important configuration class: myBatisconfig.xml

    
            
    <! DOCTYPEconfiguration
        PUBLIC "- / / mybatis.org//DTD Config / 3.0 / EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <! Import database configuration file information -->
        <properties resource="jdbc.properties"></properties>
        <! -- Set the setting property -->
        <settings>
            <! -- Enabled a hump naming rule -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
            <! - log - >
            <setting name="logImpl" value="STDOUT_LOGGING"></setting>
        </settings>
        <! -- Configure database -->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <! -- Configure connection pool -->
                <dataSource type="POOLED">
                    <property name="driver" value="${jdbc.driver}"/>
                    <property name="url" value="${jdbc.url}"/>
                    <property name="username" value="${jdbc.username}"/>
                    <property name="password" value="${jdbc.password}"/>
                </dataSource>
            </environment>
        </environments>
    
        <! -- Mappers implementation (map) file to register all of our written DAO interfaces -->
        <mappers>
            <mapper resource="/... /IUserMapper.xml"/>
            <! <package name=" cn.liuliang.dao "></package> </package>
        </mappers>
    </configuration>
    Copy the code

1.2 Compile Mapper interface and test method

  1. Mapper interface class

    public interface IUserMapper {
    
        /** * Query all users *@return* /
         List<User> findAll(a);
    
    }
    Copy the code
  2. To begin testing

    public class MyBatisTest {
    
        @Test
        public void test01(a) throws IOException {
    
            // Read the configuration file
            InputStream in= Resources.getResourceAsStream("MyBatisConfig.xml");
            / / create a sqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
            // Get the session
            SqlSession session=sqlSessionFactory.openSession();
            // Get the proxy
            IUserMapper iUserMapper =session.getMapper(IUserMapper.class);
            // Query the database
            List<User> userList= iUserMapper.findAll();
            for(User user : userList) { System.out.println(user); }}}Copy the code

1.3 the results

The SQL and results are printed out 😁.

In the future, as long as it is the operation of the database, we only need to write Mapper interface and its corresponding XML file can be very fast operation database, compared to the previous native JDBC operation what Mosaic SQL, result set mapping, resource close a lot of operations to let us developers to deal with, is too chicken! So for this MyBatis persistence framework I just want to say (awesome).

The following will be full energy oh! But in fact, it is also very simple, it is just the native operation of THE JDBC package, exposed in accordance with its definition of simple rules go, more not to say, you are qualified to see MyBatis source code.

Second, source code analysis

Since to analyze the source code, so from where to start! – Test method

MyBatis will load the resource file (mybatisconfig.xml) first, because this file is the beginning of everything, you can use this file to know the data source, features (log, camel name…). , Mapper files and a series of information.

2.1 Build SqlSessionFactory through configuration file

The first class name appears: SqlSessionFactory, whose class diagram looks like this:

Familiarize yourself with the names shown below:

SqlSessionFactory interface: The SqlSessionFactory is responsible for creating the SqlSession object, which contains only overloads of openSession () methods, with parameters that specify the isolation level of the transaction, the type of Executor used underneath, and whether or not the transaction is automatically committed.

DefaultSqlSessionFactory class: A concrete factory that implements the SqlSessionFactory interface. It provides two main ways to create DefaultSqlSession objects:

  1. Get the database connection from the data source and create the Executor object and DefaultSqlSession.
  2. Using a user-provided data connection object, DefaultSqlSessionFactory uses the database connection object to create an Executor object and DefaultSqlSession.

SqlSessionManager class: At the same time, SqlSession interface and SqlSessionFactory interface are realized, which also provides SqlSessionFactory to create SqlSession and SqlSession to manipulate the database function.

SqlSession interface: the core operation class of Mybatis, in which the CRUD of the database are encapsulated in this, is a top-level interface, the default implementation class is DefaultSqlSession this class.

  1. DefaultSqlSession classDefaultSqlsession: CRUD implementation class for the SqlSession interface, DefaultSqlsession is not thread-safe (for thread-safe, focus on session and Connnect)

So let’s start with the first line of code:

// Read the configuration file
InputStream in= Resources.getResourceAsStream("MyBatisConfig.xml");
/ / create a sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
Copy the code

SqlSessionFactoryBuilder # build

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    // ...
    // From the file stream, create an XMLConfigBuilder object
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // Parse the configuration file and build the SqlSessionFactory object
    return build(parser.parse());
    // ...
}
Copy the code

Eventually a DefaultSqlSessionFactory object is created and returned

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

The process is as follows:

2.2 Obtaining the SqlSession Object

After obtaining the session factory, it is time to obtain the specific session based on the factory.

Code entry:

// Get the session
SqlSession session=sqlSessionFactory.openSession();
Copy the code

DefaultSqlSessionFactory # openSession()

public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);  }Copy the code

Finally arrived at: # DefaultSqlSessionFactory openSessionFromDataSource ()

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // Obtain the corresponding session environment (including objects and data sources) according to the configuration file.
        final Environment environment = configuration.getEnvironment();
        // Get the transaction factory
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // Configure things according to the data source, autoCommit: whether to commit things automatically
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // Get the executor according to the configuration (which ultimately performs the corresponding database operation)
        final Executor executor = configuration.newExecutor(tx, execType);
        // Once the above information is ready, encapsulate it into the default session object and return it
        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

During the process of obtaining the SqlSession object, the corresponding session is obtained from the default session factory. In my opinion, this is very good, because there are a lot of properties that need to be configured to get a database operation session, including data source configuration, transaction configuration, etc. But with this create a session after the factory class, then everything becomes easier, the factory covers all the details, we need to adjust one external API, we can obtain corresponding SqlSession objects (factory to help us do the details), and database operation, read the code above is a very good show 😀.

A bit of:

The configuration file (mybatisconfig.xml) constructs the default session Factory (SqlSessionFactory), which then creates the specific operation database session (SqlSession).

2.3 Obtaining the Mapper Agent Based on SqlSession

Mapper = Mapper = Mapper = Mapper = Mapper = Mapper = Mapper = Mapper = Mapper = Mapper = Mapper

// Get the proxy
IUserMapper iUserMapper =session.getMapper(IUserMapper.class);
Copy the code

Click in and see

Because the Session object is DefaultSqlSession created by DefaultSqlSessionFactory, the code is in this class

public <T> T getMapper(Class<T> type) {
    // According to the configuration class, get Mapper
    return configuration.getMapper(type, this);
}
Copy the code

Click on: Configuration # getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // Get the specific Mapper from mapperRegistry
    return mapperRegistry.getMapper(type, sqlSession);
}
Copy the code

MapperRegistry: it can be understood as the Mapper interface registry, which stores all Mapper interface attributes.

MapperRegistry# getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // knownMappers, a Map, houses the Mapper agent factory
    // All configured Mapper interfaces have been registered with it according to the configuration file during initialization
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // Concrete proxy generation
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code

Click on the concrete proxy: MapperProxyFactory # newInstance

public T newInstance(SqlSession sqlSession) {
    // Generate proxy objects based on SqlSession and Mapper interfaces
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // Real agent, as follows
    return newInstance(mapperProxy);
}
// The JDK native API is used to delegate objects to the user
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
Copy the code

This is the whole process of the proxy of the Mapper interface, in which the corresponding Mapper is obtained according to the session but the internal call is the MapperRegistry, which contains all the configured Mapper interfaces. A Map key is maintained in MapperRegistry with the Class value of MapperProxyFactory, so that we can obtain the proxy factory for the Mapper interface, and finally use this factory to generate the Mapper we want to return to the user.

The process is not complicated, but there are a lot of MapperXXX related classes, so I comb these class diagram as follows:

The implementation of the specific proxy class comes down to the implementation part, where the user will be directed to the classes in the diagram when executing the corresponding method through our returned proxy class (Mapper interface).

As usual, let’s have a flow chart.

2.5 Use the Mapper agent to perform operations on the database

All of the above analysis is to wait until a concrete operation of a database bridge, that is, the Mapper agent (iUserMapper).

The next step is to analyze the final step, the actual operation of the database, the code is as follows:

// Query the database
List<User> userList= iUserMapper.findAll();
for (User user : userList) {
    System.out.println(user);
}
Copy the code

For the iUserMapper object, we know that it is executed by the agent, so it is not possible to directly click on it, so we can Debug it.

MapperProxy # invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // The object class of the method is executed directly through the original JDK
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            // Execute the proxy method after obtaining the executor of the method
            returncachedInvoker(method).invoke(proxy, method, args, sqlSession); }}catch (Throwable t) {
        throwExceptionUtil.unwrapThrowable(t); }}Copy the code

So let’s go into MapperProxy # cachedInvoker

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        // Check cache first, return if there is, create if there is no
        MapperMethodInvoker invoker = methodCache.get(method);
        if(invoker ! =null) {
            return invoker;
        }

        return methodCache.computeIfAbsent(method, m -> {
            // ...
            // Returns a Mapper method executor of type PlainMethodInvoker
            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            // ...
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null? re : cause; }}Copy the code

Next, enter the method PlainMethodInvoker# invoke

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    // Call the mapperMethod object's execute method to actually execute
    return mapperMethod.execute(sqlSession, args);
}
Copy the code

The actual start execute

MapperMethod # execute

public Object execute(SqlSession sqlSession, Object[] args) {
    // There are a lot of things in it
    // 1) Encapsulate parameters
    // 2) Execute the corresponding method according to the corresponding execution type (INSERT, UPDATE, DELETE, SELECT)
    // 3) Execute type encapsulate corresponding SQL according to the parameter
    // 4) Operate the native JDBC API to perform database operations
    // 5) Encapsulate the result set and return it
}
Copy the code

Let’s Debug the last step of this method and see the result:

To this, our Mapper interface and file effective principle, all over the side, is not that it is not very difficult!

In the analysis of this piece of source code, I understand the steps are:

  1. Step by step into the source code.
  2. Draw a flow chart, if not clear Debug.
  3. It is important to draw a class diagram for many classes with similar names to understand the relationship going forward (to help you understand the responsibilities of each class).
  4. In the end, it’s time to take notes. After all, a good memory is worse than a bad one.

2.5 Overall Flow Chart

Well, that's it for today, so follow me and we'll see you next time


  • Due to the lack of knowledge of the blogger, there will be mistakes, if you find mistakes or bias, please comment to me, I will correct it.

  • If you think the article is good, your retweets, shares, likes and comments are the biggest encouragement to me.

  • Thank you for reading. Welcome and thank you for your attention.

^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^

CSDN:J3 – leitian

This is a technical average, but keen to share; Inexperienced but thick-skinned; Young and good-looking programmers who insist on talent for a living.

^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^