Beauty of mybatis source code: 1.3. Find a starting point for learning mybatis source code

Find the starting point of learning mybatis source code

To learn the source code of mybatis, the first step must be to find a starting point for learning. First, we start with a simple Demo, re experience the way of using mybatis, and then we can start from it to learn the source code of mybatis.

Hello World

First of all, we will build a new Hello World project to experience the usage of Mybatis.

Data preparation

Connect to Mysql server, create a new database named mybatis, and create a user table with two fields (id,name).

  • CREATE DATABASE mybatis;
  • Create a new user table:
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'User name',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='user';

>Full script:

CREATE DATABASE mybatis;
USE mybatis;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'User name',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='user';

In the src/test/java/org/apache/learning directory of mybatis project, create a new subpackage named helloworld, which will be used to store our sample code.

Create a new user subpackage under helloworld, and create the following file.

  • User.java:

    import lombok.ToString;
    
    @ToString
    public class User {
        private Integer id;
        private String name;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    
  • UserMapper.java:

    import org.apache.ibatis.annotations.Param;
    
    public interface UserMapper {
    
        /**
         * Get user information according to ID
         *
         * @param id User ID
         * @return User information
         */
        User getById(Integer id);
    
        /**
         * Insert a new user data
         *
         * @param id   User ID
         * @param name User name
         * @return Number of rows affected by this operation
         */
        Integer insert(@Param("id") Integer id, @Param("name") String name);
    }
    
  • UserMapper.xml:

    <!--?xml version="1.0" encoding="UTF-8"?-->
    
    
    <mapper namespace="org.apache.learning.helloworld.user.UserMapper">
    
        <insert id="insert">
            insert into user values(#{id},#{name});
        </insert>
    
        <select id="getById" parametertype="int" resulttype="org.apache.learning.helloworld.user.User">
            SELECT * FROM user WHERE id=#{id};
        </select>
    </mapper>
    

Then under the helloworld package, create the following file:

  • hello-world.xml:

    <!--?xml version="1.0" encoding="UTF-8"?-->
    
    
    <configuration>
        <settings>
            <setting name="cacheEnabled" value="true" />
            <setting name="lazyLoadingEnabled" value="false" />
            <!--Log implementation class, using SLF4J-->
            <setting name="logImpl" value="SLF4J" />
        </settings>
    
        <environments default="development">
            <environment id="development">
                <!-- Declare which transaction management mechanism to use JDBC/MANAGED -->
                <transactionmanager type="JDBC" />
                <!-- Configure database connection information -->
                <datasource type="POOLED">
                    <!-- You need to pay attention here: MYSQL 6(contain)You can use the following configuration, MYSQL 6 The following still need to use the old one com.mysql.jdbc.Driver-->
                    <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                    <!-- Special treatment required & Symbols, converting to&amp; -->
                    <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC" />
                    <property name="username" value="root" />
                    <!-- Special treatment required & Symbols, converting to&amp; -->
                    <property name="password" value="dUP6lKU+c3&amp;s" />
                </datasource>
            </environment>
        </environments>
    
        <mappers>
            <mapper resource="org/apache/learning/helloworld/user/userMapper.xml" />
        </mappers>
    
    </configuration>
    
  • HelloWorldTest.java:

  import lombok.extern.slf4j.Slf4j;
  import org.apache.ibatis.session.SqlSession;
  import org.apache.ibatis.session.SqlSessionFactory;
  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  import org.apache.learning.helloworld.user.User;
  import org.apache.learning.helloworld.user.UserMapper;
  import org.junit.jupiter.api.Test;
  import java.io.InputStream;
  @Slf4j
  public class HelloWorldTest {
      /**
       * Mybatis Global profile
       */
      private static final String GLOBAL_CONFIG_FILE = "org/apache/learning/helloworld/hello-world.xml";

      @Test
      public void helloWord() {
          // Get the builder of Mybatis session factory
          SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
          // Create a session factory for Mybatis
          SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(loadStream(GLOBAL_CONFIG_FILE));

          try (
                  // Get a SqlSession through SqlSessionFactory
                  SqlSession sqlSession = sqlSessionFactory.openSession();
          ) {
              // Get the proxy object of UserMapper from mybatis
              UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

              final int id = 1;
              final String name = "jpanda";

              log.debug("New user data in database:id={},name={}", id, name);
              Integer updateCount = userMapper.insert(id, name);
              assert updateCount == 1;

              // Query this user
              User user = userMapper.getById(id);
              log.debug("Get data user id by{}The user data for is:{}", id, user);
              // RollBACK 
              sqlSession.rollback();
          }
      }

      /**
       * Get the input stream of the specified file
       *
       * @param path File address
       * @return Input stream
       */
      private InputStream loadStream(String path) {
          return HelloWorldTest.class.getClassLoader().getResourceAsStream(path);
      }
  }

Execute helloWord method of HelloWorldTest. If there is no accident, the console will print out the following:

DEBUG [main] - New user data in database:id=1,name=jpanda
DEBUG [main] - ==&gt;  Preparing: insert into user values(?,?);
DEBUG [main] - ==&gt; Parameters: 1(Integer), jpanda(String)
DEBUG [main] - &lt;==    Updates: 1
DEBUG [main] - ==&gt;  Preparing: SELECT * FROM user WHERE id=?;
DEBUG [main] - ==&gt; Parameters: 1(Integer)
DEBUG [main] - &lt;==      Total: 1
DEBUG [main] - Get data user id The user data for 1 is:User(id=1, name=jpanda)

In the above code, we implemented a simple demo to demonstrate the use of Mybatis. >First, initialize an instance of SqlSessionFactory through the main configuration file of Mybatis, then obtain a SqlSession through SqlSessionFactory, and then obtain an instance of the interface we want to perform database operations in this SqlSession, and then perform database operations.

In this Demo, there are five files involved:

  • User.java It is a simple entity class, which corresponds to the table user in the database mybatis. Like this entity class, which corresponds to the database table, we usually call it PO(Persistent Object), that is, persistent object.
  • UserMapper.java It is an interface, which defines the method of operating user table. Like this interface, which defines database operation method, we usually call it DAO(Data Access Object), that is, database access object.
  • UserMapper.xml Corresponding to UserMapper.java Interface, which gives the UserMapper.java The SQL operation for the method defined in.
  • hello-world.xml Is the global configuration file of mybatis, which defines the basic configuration of mybatis.
  • HelloWorldTest.java It's a unit test class, and it's also the entry of our demo code.

In HelloWorldTest's helloWord method, I divide all code into two phases:

  • The first stage is the preparation stage of mybatis running environment

    In this stage, we create an instance of SqlSessionFactoryBuilder object and use it to process the Mybatis global configuration file hello-world.xml Finally, a SqlSessionFactory object is generated

    // Get the builder of Mybatis session factory
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    // Create a session factory for Mybatis
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(loadStream(GLOBAL_CONFIG_FILE));
    
  • The second stage is: mybatis function usage stage

    In this stage, we create an instance of SqlSession object through SqlSessionFactory obtained in the previous stage, and then obtain the UserMapper object through this instance to perform specific database operations.

    try (
                // Get a SqlSession through SqlSessionFactory
                SqlSession sqlSession = sqlSessionFactory.openSession();
        ) {
    
            // Get the proxy object of UserMapper from mybatis
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
            final int id = 1;
            final String name = "jpanda";
    
            log.debug("New user data in database:id={},name={}", id, name);
            Integer updateCount = userMapper.insert(id, name);
            assert updateCount == 1;
    
            // Query this user
            User user = userMapper.getById(id);
            log.debug("Get data user id by{}The user data of is:{}", id, user);
            // RollBACK 
            sqlSession.rollback();
        }
    

The java objects involved in these two stages are mainly SqlSession,SqlSessionFactory and SqlSessionFactoryBuilder. Now we may not know these three classes, but it doesn't matter. Let's learn these three classes in turn.

SqlSession

Among them, SqlSession is the top-level API interface provided by Mybatis to users for database operations. It defines methods for database operations and provides the ability to manipulate transactions. In the Mybatis framework, to some extent, we can think that SqlSession object is used to replace JDBC Connection object.

Different from Connection object, SqlSession object not only encapsulates JDBC operation, but also provides some other interesting things. For example, it has a getMapper(Class) method, which is very interesting. You pass it a DAO operation interface type, and it also gives you a corresponding entity object:

// Get the proxy object of UserMapper from mybatis
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

After that, when you call the method of the acquired object, the database operation effect comes with it:

  log.debug("New user data in database:id={},name={}", id, name);
  Integer updateCount = userMapper.insert(id, name);
  // Query this user
  User user = userMapper.getById(id);
  log.debug("Get data user id by{}The user data of is:{}", id, user);

Operation log:

DEBUG [main] - New user data in database:id=1,name=jpanda
DEBUG [main] - ==&gt;  Preparing: insert into user values(?,?);
DEBUG [main] - ==&gt; Parameters: 1(Integer), jpanda(String)
DEBUG [main] - &lt;==    Updates: 1
DEBUG [main] - ==&gt;  Preparing: SELECT * FROM user WHERE id=?;
DEBUG [main] - ==&gt; Parameters: 1(Integer)
DEBUG [main] - &lt;==      Total: 1
DEBUG [main] - Get data user id The user data for 1 is:User(id=1, name=jpanda)

This ability to operate the database in the form of java objects is the core function of mybatis, so how does this seemingly grey six function come true?

In fact, the implementation of this function is very complex. In the follow-up learning process, we will gradually understand the problem. However, at present, the implementation of this function relies on a commonly used design mode - agent mode.

>The proxy pattern is a structural design pattern. It defines a proxy object to replace the proxy object exposed to the caller of the proxy object. The proxy object functions between the caller and the proxy object, so as to control the access to the real object and perform other operations.

Here, mybatis creates a proxy object for UserMapper. The proxy object intercepts the user's method call to UserMapper, performs the JDBC operation corresponding to the intercepted method, and finally converts the processed JDBC data into a java object and returns it to the caller of the method.

SqlSessionFactory

SqlSessionFactory object is the core object of mybatis. Its main function is to create SqlSession object.

In fact, from the name of this class, we can easily find that it is also the embodiment of a design pattern factory method pattern. >Factory method pattern is a common design pattern of creation type. Factory Method Pattern defines a factory interface for creating objects and delays the specific creation work to its subclasses. There are two application scenarios of factory method pattern: >>1. In the process of writing software, when a large number of objects need to be created and these objects have a common interface, the factory mode can be considered

>>2. In the process of writing the software, it is found that there are many operations of the same type. They are consistent in the process, only slightly different in the details. At this time, we can consider using the factory mode to achieve

As the creation factory of SqlSession object, SqlSessionFactory meets two application scenarios of factory method at the same time. It not only provides the method of creating SqlSession object, but also provides a variety of overload forms of the method for customized creation of SqlSession object instances.

At the same time, a getConfiguration() method is defined in SqlSessionFactory to get the Configuration object.

Configuration object is also the core object of mybatis. It stores almost all the configuration information in mybatis. In the next learning process of mybatis, we will often deal with this class, and soon we will encounter this class. Therefore, we will skip this and continue to look at the SqlSessionFactoryBuilder class responsible for creating SqlSessionFactory objects,

SqlSessionFactoryBuilder

How to say, among the 23 commonly used design patterns, there is a design pattern called builder pattern, which is usually used to build complex objects. Fortunately, SqlSessionFactoryBuilder is a simple implementation of builder pattern here.

>The builder pattern (generator pattern) is also a kind of creation design pattern. It can abstract the creation process of a complex object, so as to realize the realization of different creation processes to obtain objects in different forms.

SqlSessionFactoryBuilder is a typical application of builder mode. He defines a build method to create SqlSessionFactory objects, and provides a variety of different overload methods for building. According to the different input parameters, the process or results of constructing SqlSessionFactory objects are slightly different. His expression just coincides with that of building In the definition of Creator pattern, different objects in different forms are obtained through the implementation of different creation processes.

In fact, the creation process of SqlSessionFactory object is not complicated. The complexity is to create the Configuration object that SqlSessionFactory depends on to store mybatis Configuration information. Most of the building methods in SqlSessionFactoryBuilder are to obtain the implementation class of Configuration object first, and then complete the instantiation of SqlSessionFactory through Configuration object.

The creation of Configuration object is a very complex operation, which we will touch soon. Before that, let's take a look at the class diagrams of SqlSession,SqlSessionFactory and SqlSessionFactoryBuilder:

In the class diagram, two new classes are introduced: DefaultSqlSessionFactory and DefaultSqlSession. These two classes are the default implementations of SqlSessionFactory and SqlSession interface respectively. Just learn about them, because we will talk about them later.

Now, when we look back at HelloWorldTest's helloWord method, we may have some new feelings:

The original code will be analyzed in detail as follows:

// Get the builder of Mybatis session factory
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// Create a session factory for Mybatis
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(loadStream(GLOBAL_CONFIG_FILE));

try (
        // Get a SqlSession through SqlSessionFactory
        SqlSession sqlSession = sqlSessionFactory.openSession();
) {
    // Get the proxy object of UserMapper from mybatis
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    final int id = 1;
    final String name = "jpanda";

    log.debug("New user data in database:id={},name={}", id, name);
    Integer updateCount = userMapper.insert(id, name);
    assert updateCount == 1;

    // Query this user
    User user = userMapper.getById(id);
    log.debug("Get data user id by{}The user data of is:{}", id, user);
    // RollBACK 
    sqlSession.rollback();
}

First, we manually create a SqlSessionFactoryBuilder object:

// Get the builder of Mybatis session factory
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

The purpose of creating this object is very clear. It is used to process the input stream of mybatis configuration file so as to obtain SqlSessionFactory object instance. Therefore, the code representation is as follows:

// Create a session factory for Mybatis
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(loadStream(GLOBAL_CONFIG_FILE));

Here, sqlSessionFactoryBuilder creates DefaultSqlSessionFactory instance by default

After getting the SqlSessionFactory object instance, we need to get an instance of SqlSession object through this instance.

// Get a SqlSession through SqlSessionFactory
SqlSession sqlSession = sqlSessionFactory.openSession();

Here, DefaultSqlSessionFactory creates a DefaultSqlSession instance.

After getting the SqlSession instance, we can create a proxy instance for the UserMapper interface through the getMapper(Class) method of SqlSession.

// Get the proxy object of UserMapper from mybatis
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

Next, you can complete the JDBC operation through the proxy object of UserMapper.

final int id = 1;
final String name = "jpanda";

log.debug("New user data in database:id={},name={}", id, name);
Integer updateCount = userMapper.insert(id, name);
assert updateCount == 1;

// Query this user
User user = userMapper.getById(id);
log.debug("Get data user id by{}The user data of is:{}", id, user);
// RollBACK 
sqlSession.rollback();

Now that I've finished reading the sample code, I'll go back to this class diagram and sort out their relationship in my mind. Is the idea clearer?

Pay attention to me and learn more together

Tags: Programming Mybatis Database Java Apache

Posted on Thu, 21 May 2020 02:36:29 -0400 by gleemonex