Introduction to mybatis source code analysis series

Time:2021-10-23

1. A quick overview of this article

This article is a guide for the next mybatis source code analysis series. This article explains and demonstrates mybatis from three perspectives: what, why and how. Due to the large length of the article, a chapter is specially taken out to introduce the structure and content of this paper. Let’s take a look at the chapter arrangement of this article:

Introduction to mybatis source code analysis series

As shown in the figure above, most of this paper focuses on chapters 3 and 4. Chapter 3 demonstrates the usage of several persistence layer technologies, and on this basis, analyzes the use scenarios of various technologies. By analyzing the usage scenario of mybatis, this paper explains why to use mybatis. Chapter 4 is mainly used to introduce two different uses of mybatis. In Section 4.1, the process of using mybatis alone is demonstrated, and the demonstration example involvesone-on-oneandOne to manyQuery scenario. Section 4.2 introduces the integration process of mybatis and spring, and finally demonstrates how to use mybatis in spring. In addition to these two chapters, Chapter 2 and Chapter 5 of this paper are relatively few, so they will not be introduced.

The above is a preview of the content of this article. If you master these contents, you don’t have to look down. Of course, if you don’t master it or are interested, you might as well continue reading. Well, I won’t say much else. Let’s get to the point.

2. What is mybatis

Mybatis, formerly known as ibatis, is an open source project under the Apache Software Foundation. In 2010, the project moved out of the Apache foundation and was renamed mybatis. During the same period, ibatis stopped maintenance.

Mybatis is a semi-automatic Java persistence framework, which associates objects with SQL through annotations or XML. The reason why it is semi-automatic is that compared with hibernate and other ORM (object relational mapping) frameworks that can automatically generate SQL, using mybatis requires users to maintain SQL by themselves. Maintaining SQL is cumbersome, but it also has advantages. For example, we can control SQL logic and optimize it to improve efficiency.

Mybatis is an easy-to-use persistence layer framework. Users can master the usage of its common features through simple learning. This is one reason why mybatis is widely used.

3. Why use mybatis

We have many choices when using Java programs to access the database. For example, we can access the database by writing the most original JDBC code, or through the jdbctemplate provided by spring. In addition, we can also choose hibernate, or mybatis, the protagonist of this article. Why do we choose mybatis when there are multiple options? To answer this question, we need to compare mybatis with these database access methods. Of course, there is usually no difference between technology and technology. From the perspective of application scenarios, technologies that meet the needs of application scenarios are the appropriate choice. Next, I will compare the advantages and disadvantages of these database access technologies by writing code, and explain the applicable scenarios of mybatis at the end.

Here, first post some public classes and configurations used in this section. Later, you can check where these resources are used. The classes used in this chapter are as follows:

public class Article {
    private Integer id;
    private String title;
    private String author;
    private String content;
    private Date createTime;
    
    //Omit getter / setter and toString
}

The database related configuration is placed in the jdbc.properties file. The details are as follows:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/coolblog?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=TRUE
jdbc.username=root
jdbc.password=****

The table records as follows:

Introduction to mybatis source code analysis series

Let’s first demonstrate the process of mybatis accessing the database.

3.1 accessing the database using mybatis

As mentioned earlier, mybatis is a semi-automatic Java persistence framework. Using mybatis requires users to maintain SQL by themselves. Here, we put SQL in XML, and the file name is articlemapper.xml. The relevant configurations are as follows:

<mapper namespace="xyz.coolblog.dao.ArticleDao">
    <resultMap id="articleResult" type="xyz.coolblog.model.Article">
        <id property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="author" column="author"/>
        <result property="content" column="content"/>
        <result property="createTime" column="create_time"/>
    </resultMap>
    
    <select id="findByAuthorAndCreateTime" resultMap="articleResult">
        SELECT
            `id`, `title`, `author`, `content`, `create_time`
        FROM
            `article`
        WHERE
            `author` = #{author} AND `create_time` > #{createTime}
    </select>
</mapper>

The above SQL is used toarticleQuery the article records written by an author from a certain time to the present in the table. In mybatis, the SQL mapping file needs to correspond to the data access interface, such as the above configurationxyz.coolblog.dao.ArticleDaoInterface, which is defined as follows:

public interface ArticleDao {
    List<Article> findByAuthorAndCreateTime(@Param("author") String author, @Param("createTime") String createTime);
}

In order for mybatis to run, some configuration is required. For example, configure the data source and the location information of the SQL mapping file. The configuration used in this section is as follows:

<configuration>
    <properties resource="jdbc.properties"/>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <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>
        <mapper resource="mapper/ArticleMapper.xml"/>
    </mappers>
</configuration>

At this point, the environment required by mybatis is configured. Next, run mybatis. The relevant test codes are as follows:

public class MyBatisTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void prepare() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        inputStream.close();
    }

    @Test
    public void testMyBatis() throws IOException {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            ArticleDao articleDao = session.getMapper(ArticleDao.class);
            List<Article> articles = articleDao.findByAuthorAndCreateTime("coolblog.xyz", "2018-06-10");
        } finally {
            session.commit();
            session.close();
        }
    }
}

In the above test code, the prepare method is used to createSqlSessionFactoryFactory, which is used to createSqlSession。 Through sqlsession, we can provide our database access interfaceArticleDaoInterface to generate a proxy object. Mybatis will interface methodsfindByAuthorAndCreateTimeAssociated with the SQL configured in the SQL mapping file, so calling this method is equivalent to executing the relevant SQL.

The running results of the above test code are as follows:

Introduction to mybatis source code analysis series

As mentioned above, when learning the mybatis framework, you can configure the mybatis log, so that you can print the debugging information of mybatis to facilitate the observation of the SQL execution process. In the above results,==>The row where the symbol is located represents the SQL and related parameters entered into the database.<==The line where the symbol is located represents the execution result of SQL. The above input and output is not difficult to understand, so I won’t say more here.

The advantages and disadvantages of mybatis are not summarized here. Other frameworks will be demonstrated later.

After demonstrating mybatis, let’s take a look at the process of directly accessing the database through the original JDBC.

3.2 accessing database using JDBC

3.2.1 process demonstration of JDBC accessing database

At the beginning of Java programming, most friends should access the database by directly writing JDBC code. I say so, everyone should have no objection. The code flow of this method is generally to load database drivers, create database connection objects, create SQL execution statement objects, execute SQL and process result sets. The process is relatively fixed. Now let’s write JDBC code again and recall the scene of learning Java.

public class JdbcTest {

    @Test
    public void testJdbc() {
        String url = "jdbc:mysql://localhost:3306/myblog?user=root&password=1234&useUnicode=true&characterEncoding=UTF8&useSSL=false";

        Connection conn = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(url);

            String author = "coolblog.xyz";
            String date = "2018.06.10";
            String sql = "SELECT id, title, author, content, create_time FROM article WHERE author = '" + author + "' AND create_time > '" + date + "'";

            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql);
            List<Article> articles = new ArrayList<>(rs.getRow());
            while (rs.next()) {
                Article article = new Article();
                article.setId(rs.getInt("id"));
                article.setTitle(rs.getString("title"));
                article.setAuthor(rs.getString("author"));
                article.setContent(rs.getString("content"));
                article.setCreateTime(rs.getDate("create_time"));
                articles.add(article);
            }
            System.out.println("Query SQL ==> " + sql);
            System.out.println("Query Result: ");
            articles.forEach(System.out::println);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

The code is relatively simple, so I won’t say more. Let’s take a look at the test results:

Introduction to mybatis source code analysis series

There are many steps in the above code, but there are only two core steps: executing SQL and processing query results. From the developer’s point of view, we only care about these two steps. If you have to write a lot of extra code every time to execute a certain SQL. For example, it is very cumbersome to open the driver and create a database connection. Of course, we can encapsulate these additional steps so that each time we call the encapsulated method. This can really solve the problem of cumbersome and redundant code. However, using JDBC will not only lead to cumbersome and redundant code. In the above code, we splice SQL through strings. This will lead to two problems. The first is that splicing SQL may lead to SQL errors, such as missing commas or multiple single quotes. The second is to write SQL in the code. If you want to change SQL, you need to change it in the code. This is not appropriate, because changing java code requires recompiling java files before packaging and publishing. At the same time, mixing SQL and Java code will reduce the readability of the code and is not conducive to maintenance. There are corresponding processing methods for splicing SQL. For example, you can use Preparedstatement and solve the problem of SQL injection.

In addition to the problems mentioned above, what are the problems with accessing the database directly using JDBC? This time, we turn our attention to the processing logic of execution results. As can be seen from the above code, we need to manually fetch the data from the resultset and then set it into the article object. Fortunately, we don’t have many article attributes, so it doesn’t seem to matter. If the article object has dozens of attributes, it will be very troublesome to receive the query results in the above way. Moreover, there may be too many attributes, resulting in forgetting to set some attributes. There is another problem with the above code. Users need to handle the detected exceptions by themselves, which is also a reason for the cumbersome code. Oh, there’s another question. I almost forgot. The user also needs to manually manage the database connection and start to manually obtain the database connection. After use, close the database connection manually. I have to say, it’s really troublesome.

I didn’t expect that there would be so many problems accessing the database directly using JDBC. If you use JDBC directly in the production environment, you may be killed by the leader. Of course, it depends. If the project is very small and has low dependence on the database. It’s also convenient to use JDBC directly. You don’t have to do a lot of configuration like mybatis.

3.2.2 MyBatis VS JDBC

A lot of bad words about JDBC make complaints about it. So let’s talk about MyBatis. Compared with JDBC, mybatis has obvious disadvantages. It has more configurations, especially SQL mapping files. If there are dozens or hundreds of Dao interfaces in a large project, you need to have the same number of SQL mapping files, which need to be maintained by the user. However, compared with JDBC, maintaining mapping files is not a problem. Otherwise, if the same amount of SQL is written in the code like JDBC, the cost of maintenance will be large, and maybe the car will roll over. In addition to the problem of configuration files, you will find that using mybatis to access the database seems to be a cumbersome process. Its steps are as follows:

  1. Read configuration file
  2. Create sqlsessionfactorybuilder object
  3. Create a sqlsessionfactory from the sqlsessionfactorybuilder object
  4. Create sqlsession through sqlsessionfactory
  5. Generate proxy class for Dao interface
  6. Call the interface method to access the database

As mentioned above, if you have to go through the above steps to execute SQL each time, it has no advantage compared with JDBC. However, you should note that the scope and life cycle of sqlsessionfactorybuilder, sqlsessionfactory, sqlsession and other objects are different. This is clearly stated in the official mybatis document, and I will copy it here. Sqlsessionfactorybuilder object is used to build sqlsessionfactory. As long as it is built, this object can be discarded. Sqlsessionfactory is a factory class. Once created, it should always exist during the running of the application and should not be discarded or rebuilt. Sqlsession is not thread safe and should not be shared by multiple threads. The official recommended method of use is to create on demand and destroy when used up. Therefore, in the above steps, steps 1, 2 and 3 only need to be performed once. Steps 4 and 5 require multiple creation. As for step 6, this step is necessary. So by comparison, mybatis is simpler to use than JDBC. At the same time, using mybatis does not need to handle checked exceptions, such as sqlexception. In addition, SQL is written in the configuration file for centralized management, which is conducive to maintenance. At the same time, SQL is stripped from the code, which not only improves the readability of the code, but also avoids the errors that may be caused by splicing SQL. In addition to the above, mybatis will convert the query results into corresponding objects without the user processing the resultset.

In general, mybatis is much better than JDBC in ease of use. However, it is not appropriate to compare mybatis with JDBC here. As the database access specification of Java platform, JDBC only provides one ability to access the database. As for the problems that users think JDBC process is cumbersome and have to deal with exceptions by themselves, it’s really no wonder JDBC. For example, the exception sqlexception cannot be handled by JDBC, and it is natural to throw it to the caller for processing. As for the complicated steps, this is only considered from the perspective of users. From the perspective of JDBC, each step here is necessary to complete a data access request. As for mybatis, it is built on JDBC technology, which simplifies the operation of accessing the database and is convenient for users. To sum up, JDBC can be regarded as a basic service, while mybatis is a framework built on basic services, and their goals are different.

3.3 accessing database using spring JDBC

The previous section demonstrated the process of JDBC accessing data. Through demonstration and analysis, you should feel some pain points of using JDBC directly. In order to solve some of these pain points, spring JDBC came into being. Spring JDBC has been thinly packaged on the basis of JDBC, and its ease of use has been greatly improved. Let’s take a look at how to use spring JDBC.

We need to make some configuration before using spring JDBC. Here, I put the configuration information in the application.xml file. When writing the test code later, let the container load the configuration. The configuration contents are as follows:

<context:property-placeholder location="jdbc.properties"/>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driver}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource" />
</bean>

As above,JdbcTemplateEncapsulates some methods to access the database. Next, we will access the database through this object. The demonstration code is as follows:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class SpringJdbcTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testSpringJdbc() {
        String author = "coolblog.xyz";
        String date = "2018.06.10";
        String sql = "SELECT id, title, author, content, create_time FROM article WHERE author = '" + author + "' AND create_time > '" + date + "'";
        List<Article> articles = jdbcTemplate.query(sql, (rs, rowNum) -> {
                    Article article = new Article();
                    article.setId(rs.getInt("id"));
                    article.setTitle(rs.getString("title"));
                    article.setAuthor(rs.getString("author"));
                    article.setContent(rs.getString("content"));
                    article.setCreateTime(rs.getDate("create_time"));
                    return article;
            });

        System.out.println("Query SQL ==> " + sql);
        System.out.println("Spring JDBC Query Result: ");
        articles.forEach(System.out::println);
    }
}

The test results are as follows:

Introduction to mybatis source code analysis series

As can be seen from the above code, spring JDBC is relatively easy to use. However, it also has some defects. For example, SQL is still written in code. For another example, for more complex results (the records returned by the database contain multiple columns of data), users need to process the resultset by themselves. However, compared with JDBC, spring JDBC does not require manual loading of database drivers, obtaining database connections, and creating statement objects. In general, ease of use has been greatly improved.

The advantages and disadvantages of spring JDBC and mybatis are not compared here. Spring JDBC only encapsulates JDBC in a thin layer. For comparison, please refer to the analysis in the previous section, which will not be repeated here.

3.4 accessing database using hibernate

This section will be the same as the previous chapters. I will first write code for demonstration, and then compare the differences between hibernate and mybatis. In particular, I haven’t used hibernate in my work, and I only understand hibernate. The test code in this section is learned and sold now. There may be problems in some places, or it is not a best practice. So let’s take a look at the test code. If there is anything wrong, you are also welcome to point out.

3.4.1 process demonstration of Hibernate accessing database

To use hibernate, you need to configure the environment first, mainly about database configuration. Here, for demonstration, let’s simply configure it. As follows:

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/myblog?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;rewriteBatchedStatements=TRUE</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">****</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <property name="hibernate.show_sql">true</property>

        <mapping resource="mapping/Article.hbm.xml" />
    </session-factory>
</hibernate-configuration>

Next, configure the mapping relationship between entity classes and tables, that is, the mapping relationship in the above configurationArticle.hbm.xml。 However, this configuration is not required and can be replaced with annotations.

<hibernate-mapping package="xyz.coolblog.model">
    <class table="article" name="Article">
        <id name="id" column="id">
            <generator class="native" />
        </id>
        <property name="title" column="title" />
        <property name="author" column="author" />
        <property name="content" column="content" />
        <property name="createTime" column="create_time" />
    </class>
</hibernate-mapping>

The test code is as follows:

public class HibernateTest {

    private SessionFactory buildSessionFactory;

    @Before
    public void init() {
        Configuration configuration = new Configuration();
        configuration.configure("hibernate.cfg.xml");
        buildSessionFactory = configuration.buildSessionFactory();
    }

    @After
    public void destroy() {
        buildSessionFactory.close();
    }

    @Test
    public void testORM() {
        System.out.println("-----------------------------✨ ORM Query ✨--------------------------");

        Session session = null;
        try {
            session = buildSessionFactory.openSession();
            int id = 6;
            Article article = session.get(Article.class, id);
            System.out.println("ORM Query Result: ");
            System.out.println(article);
            System.out.println();
        } finally {
            if (Objects.nonNull(session)) {
                session.close();
            }
        }

    }

    @Test
    public void testHQL() {
        System.out.println("-----------------------------✨ HQL Query ✨+--------------------------");
        Session session = null;
        try {
            session = buildSessionFactory.openSession();
            String hql = "from Article where author = :author and create_time > :createTime";
            Query query = session.createQuery(hql);
            query.setParameter("author", "coolblog.xyz");
            query.setParameter("createTime", "2018.06.10");

            List<Article> articles = query.list();
            System.out.println("HQL Query Result: ");
            articles.forEach(System.out::println);
            System.out.println();
        } finally {
            if (Objects.nonNull(session)) {
                session.close();
            }
        }
    }

    @Test
    public void testJpaCriteria() throws ParseException {
        System.out.println("---------------------------✨ JPA Criteria ✨------------------------");

        Session session = null;
        try {
            session = buildSessionFactory.openSession();
            CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
            CriteriaQuery<Article> criteriaQuery = criteriaBuilder.createQuery(Article.class);
    
            //Define from clause
            Root<Article> article = criteriaQuery.from(Article.class);
    
            //Build query criteria
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd");
            Predicate greaterThan = criteriaBuilder.greaterThan(article.get("createTime"), sdf.parse("2018.06.10"));
            Predicate equal = criteriaBuilder.equal(article.get("author"), "coolblog.xyz");
    
            //Build SQL through semantic methods, which is equivalent to select... From article where... And
            criteriaQuery.select(article).where(equal, greaterThan);
    
            Query<Article> query = session.createQuery(criteriaQuery);
            List<Article> articles = query.getResultList();
    
            System.out.println("JPA Criteria Query Result: ");
            articles.forEach(System.out::println);
        } finally {
            if (Objects.nonNull(session)) {
                session.close();
            }
        }

    }
}

Here I have written three different query methods. For relatively simple queries, you can useOIDIn a way, that istestORMMethod. This method does not need to write SQL and is completely generated by hibernate. The generated SQL is as follows:

select 
    article0_.id as id1_0_0_, 
    article0_.title as title2_0_0_, 
    article0_.author as author3_0_0_, 
    article0_.content as content4_0_0_, 
    article0_.create_time as create_t5_0_0_ 
from 
    article article0_ 
where 
    article0_.id=?

The second way is throughHQLThe query process corresponds to the query in the test classtestHQLmethod. In this way, you need to write a little HQL and set corresponding parameters for it. The final generated SQL is as follows:

select 
    article0_.id as id1_0_, 
    article0_.title as title2_0_, 
    article0_.author as author3_0_, 
    article0_.content as content4_0_, 
    article0_.create_time as create_t5_0_ 
from 
    article article0_ 
where 
    article0_.author=? and create_time>?

The third way is to query through JPA criteria, which is type safe, object-oriented and semantic. Using JPA criteria, we can write java code for database operations without handwritten SQL. The second method and the third method perform the same query, so the generated SQL is not very different, so it will not be posted here.

Let’s take a look at the running results of the test code:

Introduction to mybatis source code analysis series

3.4.2 MyBatis VS Hibernate

In Java, as far as the persistence layer framework is concerned, mybatis and Hibernate are very popular frameworks. There are also extensive discussions on the Internet about which of the two frameworks is good or bad. However, as I said earlier, there is usually no difference between high and low technologies, and suitability is the point that should be paid attention to. There is a big difference between the two frameworks. Let’s talk about it.

In terms of mapping relationship, hibernate associates entity classes (POJOs) with tables. It is a complete ORM (O / R mapping) framework. Mybatis associates the data access interface (DAO) with SQL, which is essentially an SQL mapping. From the perspective of use, you usually don’t need to write SQL to use hibernate. Just let the framework generate itself. But mybatis can’t. no matter how simple the database access operation is, it needs to have the corresponding SQL. On the other hand, because hibernate can automatically generate SQL, the cost of database migration is a little less. Since the use of mybatis requires handwritten SQL, there are some differences in SQL between different databases. This leads to the situation that SQL may need to be changed during database migration. Fortunately, however, database migration is rare and can be ignored.

Above, I compared hibernate and mybatis from two dimensions, but so far I just said some differences between them. Let’s analyze the applicable scenarios of these two frameworks.

Hibernate can automatically generate SQL and reduce the use cost. But at the same time, we should also realize that doing so also has a price, which will damage the activity of failure. For example, if we need to manually optimize SQL, it is difficult for us to change the SQL generated by hibernate. Therefore, for hibernate, it is applicable to some projects with stable requirements and small changes, such as OA, CRM, etc.

In contrast to hibernate, mybatis needs to maintain SQL manually, which will increase the use cost. But at the same time, users can flexibly control the behavior of SQL, which makes it possible to change and optimize SQL. Therefore, mybatis is suitable for some projects that require rapid iteration and large demand changes, which is why mybatis is widely used in Internet companies. In addition, mybatis also provides a plug-in mechanism, and users can customize plug-ins on demand. This is also an embodiment of mybatis flexibility.

After analysis, you should be clear about the differences between the two frameworks and the applicable scenarios. The landlord is currently in an automobile related Internet company. The company is developing rapidly, the project iteration is also relatively fast, and there are many small demands. Therefore, in contrast, mybatis is a more appropriate choice.

3.5 summary of this chapter

This section devotes a lot of space to introduce the usage of common persistence layer frameworks, and makes a more detailed analysis and comparison. After reading these, I believe you should also know more about these frameworks. Well, I won’t say much about the others. Let’s continue to look down.

4. How to use mybatis

In this chapter, let’s take a look at how mybatis is used. In the last chapter, I briefly demonstrated how to use mybatis. However, that’s too simple. In this chapter, let’s demonstrate a slightly more complex example. However, there is still a gap between the complexity of this example and the real project. It is only used for demonstration.

This chapter contains two sections. The first section demonstrates the process of using mybatis alone, and the second section demonstrates how mybatis is integrated with spring. I won’t say much about the others. Let’s start the demonstration.

4.1 use alone

The scenario presented in this section is the association scenario between the author of the personal website and the article. In a website, one article corresponds to one author, and one author corresponds to multiple articles. Let’s take a lookauthorandarticleThe definition of is as follows:

public class AuthorDO implements Serializable {
    private Integer id;
    private String name;
    private Integer age;
    private SexEnum sex;
    private String email;
    private List<ArticleDO> articles;

    //Omit getter / setter and toString
}

public class ArticleDO implements Serializable {
    private Integer id;
    private String title;
    private ArticleTypeEnum type;
    private AuthorDO author;
    private String content;
    private Date createTime;

    //Omit getter / setter and toString
}

As mentioned above, authordo contains references to a group of articledos, which is a one to many relationship. Articledo contains a reference to authordo, which is a one-to-one relationship. In addition, two constants are used here, one for gender and the other for article type. They are defined as follows:

public enum SexEnum {
    MAN,
    FEMALE,
    UNKNOWN;
}

public enum ArticleTypeEnum {
    JAVA(1),
    DUBBO(2),
    SPRING(4),
    MYBATIS(8);

    private int code;

    ArticleTypeEnum(int code) {
        this.code = code;
    }

    public int code() {
        return code;
    }

    public static ArticleTypeEnum find(int code) {
        for (ArticleTypeEnum at : ArticleTypeEnum.values()) {
            if (at.code == code) {
                return at;
            }
        }

        return null;
    }
}

This article uses two tables to store article and author information respectively. The contents of these two tables are as follows:

Introduction to mybatis source code analysis series

Let’s take a look at the interface definition of the database access layer, as follows:

public interface ArticleDao {
    ArticleDO findOne(@Param("id") int id);
}

public interface AuthorDao {
    AuthorDO findOne(@Param("id") int id);
}

The SQL corresponding to these two interfaces is configured in the following two mapping files. Let’s take a look at the contents of the first mapping file, AuthorMapper. XML.

<!-- AuthorMapper.xml -->
<mapper namespace="xyz.coolblog.dao.AuthorDao">

    <resultMap id="articleResult" type="Article">
        <id property="id" column="article_id" />
        <result property="title" column="title"/>
        <result property="type" column="type"/>
        <result property="content" column="content"/>
        <result property="createTime" column="create_time"/>
    </resultMap>

    <resultMap id="authorResult" type="Author">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
        <result property="email" column="email"/>
        <collection property="articles" ofType="Article" resultMap="articleResult"/>
    </resultMap>

    <select id="findOne" resultMap="authorResult">
        SELECT
            au.id, au.name, au.age, au.sex, au.email,
            ar.id as article_id, ar.title, ar.type, ar.content, ar.create_time
        FROM
            author au, article ar
        WHERE
            au.id = ar.author_id AND au.id = #{id}
    </select>
</mapper>

Look at the above<resultMap/>Configuration. This tab contains a one to many configuration<collection/>, this configuration refers to a configuration with IDarticleResult< resultmap / >. In addition to paying attention to the one to many configuration, the following line of configuration is also required:

<result property="sex" column="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>

Authordo was mentioned earliersexProperty is an enumeration, but this property is stored as an integer value in the data table. Therefore, type conversion is required when writing or querying data to the data table. When writing, you need toSexEnumTurn intoint。 When querying, you need tointTurn intoSexEnum。 Because these two types are completely different and cannot be converted through strong conversion, an intermediate class needs to be used for conversion. This intermediate class isEnumOrdinalTypeHandler。 This class will be converted according to the enumeration order, such as inSexEnumIn,MANThe order is0。 When stored, enumordinaltypehandler willMANReplace with0。 When querying, the0Convert toMAN。 exceptEnumOrdinalTypeHandler, mybatis also provides another enumeration type processorEnumTypeHandler。 This is converted according to the literal value of the enumeration. For example, the processor will enumerateMANAnd the string “man”.

The above briefly analyzes the enumeration type processor. Next, continue to look down. The following is the configuration content of articlemapper.xml:

<!-- ArticleMapper.xml -->
<mapper namespace="xyz.coolblog.dao.ArticleDao">

    <resultMap id="authorResult" type="Author">
        <id property="id" column="author_id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
        <result property="email" column="email"/>
    </resultMap>

    <resultMap id="articleResult" type="Article">
        <id property="id" column="id" />
        <result property="title" column="title"/>
        <result property="type" column="type" typeHandler="xyz.coolblog.mybatis.ArticleTypeHandler"/>
        <result property="content" column="content"/>
        <result property="createTime" column="create_time"/>
        <association property="author" javaType="Author" resultMap="authorResult"/>
    </resultMap>

    <select id="findOne" resultMap="articleResult">
        SELECT
            ar.id, ar.author_id, ar.title, ar.type, ar.content, ar.create_time,
            au.name, au.age, au.sex, au.email
        FROM
            article ar, author au
        WHERE
            ar.author_id = au.id AND ar.id = #{id}
    </select>
</mapper>

As mentioned above, articlemapper.xml contains a one-to-one configuration<association/>, this configuration refers to another configuration with IDauthorResult< resultmap / >. In addition to the one-to-one configuration, there is a custom type processorArticleTypeHandlerWe need your attention. This custom type processor is used to processArticleTypeEnumEnumeration type. If you pay attention to the front postArticleTypeEnumYou will find that each enumeration value has its own number definition. such asJAVAThe number of is1DUBBOThe number of is2SPRINGThe number of is8。 So we can’t use it hereEnumOrdinalTypeHandleryesArticleTypeHandlerFor type conversion, you need to customize a type converter. Let’s take a look at the definition of this type converter.

public class ArticleTypeHandler extends BaseTypeHandler<ArticleTypeEnum> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, ArticleTypeEnum parameter, JdbcType jdbcType)
        throws SQLException {
        //Get the code value of the enumeration and set it in Preparedstatement
        ps.setInt(i, parameter.code());
    }

    @Override
    public ArticleTypeEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        //Get code from resultset
        int code = rs.getInt(columnName);
        //Parse the enumeration corresponding to code and return
        return ArticleTypeEnum.find(code);
    }

    @Override
    public ArticleTypeEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return ArticleTypeEnum.find(code);
    }

    @Override
    public ArticleTypeEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return ArticleTypeEnum.find(code);
    }
}

For custom type processors, you can inherit basetypehandler and implement related abstract methods. The above code is relatively simple, and I also made some comments. It should be easy to understand. I won’t say much here.

Entity classes, data access classes, and SQL mapping files are pasted in front. Finally, there is a mybatis configuration file, which is posted here. As follows:

<!-- mybatis-congif.xml -->
<configuration>
    <properties resource="jdbc.properties"/>

    <typeAliases>
        <typeAlias alias="Article" type="xyz.coolblog.model.ArticleDO"/>
        <typeAlias alias="Author" type="xyz.coolblog.model.AuthorDO"/>
    </typeAliases>

    <typeHandlers>
        <typeHandler handler="xyz.coolblog.mybatis.ArticleTypeHandler" javaType="xyz.coolblog.constant.ArticleTypeEnum"/>
    </typeHandlers>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <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>
        <mapper resource="mapper/AuthorMapper.xml"/>
        <mapper resource="mapper/ArticleMapper.xml"/>
    </mappers>
</configuration>

Here is a table to briefly explain some of the labels that appear in the configuration.

Label name purpose
properties Used to configure global attributes, so that attribute values can be configured through the placeholder ${} in the configuration file
typeAliases Used to define aliases. As shown above, herexyz.coolblog.model.ArticleDOAlias defined asArticleIn this way, the alias can be used directly in the SQL mapping file instead of entering a long fully qualified class name every time
typeHandlers It is used to define the global type processor. If it is configured here, it does not need to be configured again in the SQL mapping file. In order to explain the need, I also configured articletypehandler in the SQL mapping file, which is actually redundant
environments Used to configure transactions and data sources
mappers Location information for configuring SQL mapping files

The above only introduces some commonly used configurations. For more configuration information, I suggest you read the official mybatis document.

Here we have finished all the preparations. So next, write some test code to test it.

public class MyBatisTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void prepare() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        inputStream.close();
    }
    
    @Test
    public void testOne2One() {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            ArticleDao articleDao = session.getMapper(ArticleDao.class);
            ArticleDO article = articleDao.findOne(1);

            AuthorDO author = article.getAuthor();
            article.setAuthor(null);

            System.out.println();
            System.out.println("author info:");
            System.out.println(author);
            System.out.println();
            System.out.println("articles info:");
            System.out.println(article);
        } finally {
            session.close();
        }
    }

    @Test
    public void testOne2Many() {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            AuthorDao authorDao = session.getMapper(AuthorDao.class);
            AuthorDO author = authorDao.findOne(1);

            List<ArticleDO> arts = author.getArticles();
            List<ArticleDO> articles = Arrays.asList(arts.toArray(new ArticleDO[arts.size()]));
            arts.clear();

            System.out.println();
            System.out.println("author info:");
            System.out.println(author);
            System.out.println();
            System.out.println("articles info:");
            articles.forEach(System.out::println);
        } finally {
            session.close();
        }
    }
}

The first test method is used to query an article from the database and the information of the corresponding author. Its operation results are as follows:

Introduction to mybatis source code analysis series

The second test method is used to query the information of an author and all articles written by him. Its operation results are as follows:

Introduction to mybatis source code analysis series

Here is the introduction to the use of mybatis. In my daily work, I also use some common features of mybatis, so the content of this section is also relatively simple. In addition, because the demonstration example is relatively simple, there is no demonstration of an important feature of mybatis–Dynamic SQL。 In addition to the above, some features are not introduced here because there is no good scene to demonstrate. For example, mybatis plug-in mechanism, cache, etc. For some remote features, such as object factory and discriminator. If it weren’t for reading mybatis documents and some books, I really didn’t know their existence and knew nothing about them. Therefore, this article will not explain these features.

To sum up, this section demonstrates a relatively simple example, not a complete example. Please know.

4.2 using in spring

In the previous section, I demonstrated the process of using mybatis alone. In actual development, we usually integrate mybatis and spring together. In this way, we can use various Dao interfaces through bean injection. Mybatis and spring were originally two completely unrelated frameworks. To integrate them, you need an intermediate framework. On the one hand, this framework is responsible for loading and parsing mybatis related configurations. On the other hand, the framework also puts various Dao interfaces and their corresponding objects into the bean factory through the extension points provided by spring. In this way, we can get the beans corresponding to these Dao interfaces through bean injection. So the question is, who is the framework with such capability? The answer ismybatis-spring。 I won’t say much else. Let’s start to demonstrate the integration process.

My test project is based on maven, so let’s take a look at the configuration of POM file first.

<project>
    <!--  Omit project coordinate configuration -- >

    <properties>
        <spring.version>4.3.17.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>

        <!--  Omit other dependencies -- >
    </dependencies>
</project>

In order to reduce the length of the article occupied by the configuration file, the above configuration has been simplified to some extent. Here, only the coordinates of mybatis and spring related packages are listed. Continue to look down. Next, configure some classes in mybatis into the spring configuration file.

<!-- application-mybatis.xml -->
<beans>
    <context:property-placeholder location="jdbc.properties"/>

    <!--  Configure data source -- >
    <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <property name="driver" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <!--  Configure sqlsessionfactory -- >
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--  Configure mybatis-config.xml path -- >
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--  Configure the data source for sqlsessionfactory. The above data source configuration -- >
        <property name="dataSource" ref="dataSource"/>
        <!--  Configure SQL mapping file -- >
        <property name="mapperLocations" value="mapper/*.xml"/>
    </bean>

    <!--  Configure mappercannerconfigurer -- >
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--  Configure the package of Dao interface -- >
        <property name="basePackage" value="xyz.coolblog.dao"/>
    </bean>
</beans>

As mentioned above, the above are some configurations required to integrate mybatis into spring. Here, we configure the data source into the spring configuration file. After configuring the data source, configure sqlsessionfactory. We all know the purpose of sqlsessionfactory. There is no need to explain it too much. Next, configure mappercannerconfigurer. As the name suggests, this class is used to scan the data access interfaces under a package and register these interfaces in the spring container. In this way, we can inject the implementation class of Dao interface into other beans without obtaining the interface implementation class from sqlsession. As for the details of mappercannerconfigurer scanning and registering Dao interface, I won’t explain it here first. I will write an article for analysis later.

After configuring mybatis into spring, in order to make our program run normally, we also need to provide a configuration for mybatis. The relevant configurations are as follows:

<!-- mybatis-config.xml -->
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    <typeAliases>
        <typeAlias alias="Article" type="xyz.coolblog.model.ArticleDO"/>
        <typeAlias alias="Author" type="xyz.coolblog.model.AuthorDO"/>
    </typeAliases>

    <typeHandlers>
        <typeHandler handler="xyz.coolblog.mybatis.ArticleTypeHandler" javaType="xyz.coolblog.constant.ArticleTypeEnum"/>
    </typeHandlers>
</configuration>

The configuration of mybatis-config.xml here is different from that in the previous section. The configuration of data source and SQL mapping file path is removed. It should be noted that for<settings/>Must be configured in mybatis-config.xml. Other configurations are not required, but can be placed in the spring configuration file. I stole a lazy here.

At this point, the configuration of spring integrating mybatis is completed. Next, write some test code and run.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-mybatis.xml")
public class SpringWithMyBatisTest implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    /**Automatically inject authordao without obtaining it through sqlsession*/ 
    @Autowired
    private AuthorDao authorDao;

    @Autowired
    private ArticleDao articleDao;

    @Before
    public void printBeanInfo() {
        ListableBeanFactory lbf = applicationContext;
        String[] beanNames = lbf.getBeanDefinitionNames();
        Arrays.sort(beanNames);

        System.out.println();
        System.out.println("----------------☆ bean name ☆---------------");
        Arrays.asList(beanNames).subList(0, 5).forEach(System.out::println);
        System.out.println();

        AuthorDao authorDao = (AuthorDao) applicationContext.getBean("authorDao");
        ArticleDao articleDao = (ArticleDao) applicationContext.getBean("articleDao");

        System.out.println("-------------☆ bean class info ☆--------------");
        System.out.println("AuthorDao  Class: " + authorDao.getClass());
        System.out.println("ArticleDao Class: " + articleDao.getClass());
        System.out.println("\n--------xxxx---------xxxx---------xxx---------\n");
    }


    @Test
    public void testOne2One() {
        ArticleDO article = articleDao.findOne(1);

        AuthorDO author = article.getAuthor();
        article.setAuthor(null);

        System.out.println();
        System.out.println("author info:");
        System.out.println(author);
        System.out.println();
        System.out.println("articles info:");
        System.out.println(article);
    }

    @Test
    public void testOne2Many() {
        AuthorDO author = authorDao.findOne(1);

        System.out.println();
        System.out.println("author info:");
        System.out.println(author);
        System.out.println();
        System.out.println("articles info:");
        author.getArticles().forEach(System.out::println);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

In the above code, in order to prove that our integration configuration is effective, a method is specially written above for outputApplicationContextinbeanInformation about. Let’s take a looktestOne2OneOutput results of the test method.

Introduction to mybatis source code analysis series

As shown above, the first two lines of bean name are the name of our Dao interface, and their implementation classes are generated by the dynamic agent of JDK. thentestOne2OneThe method also works normally. Therefore, our integrated configuration takes effect.

5. Summary

This is the end of this article. This article expounds and demonstrates what mybatis is, why it should be used, and how to use it. In general, the length of this paper should make these three problems clear. This article is quite long and should be hard to read. But the good thing is that the content is not difficult. There should be no problem understanding it. The length of this article exceeds my previous expectations. If the article is too large, the probability of error will rise. So if there are mistakes in the article, I hope you can point them out.

Well, that’s all for this article. Thank you for reading.

reference resources

  • Mybatis official documents
  • Mybatis from introduction to mastery – Liu Zenghui
  • What are the advantages of mybatis over Hibernate- Know
  • Differences and application scenarios between mybatis and Hibernate – if you can’t determine the author of the article, don’t post the link. Please search by yourself

Appendix: list of mybatis source code analysis series articles

Update time title
2018-07-16 Introduction to mybatis source code analysis series
2018-07-20 Mybatis source code analysis – configuration file parsing process

This article is released under the knowledge sharing license agreement 4.0, and the source shall be indicated in the obvious place for reprint
Author: coolblog.xyz
This article is synchronously published on my personal blog: http://www.coolblog.xyz

Introduction to mybatis source code analysis series
This work is licensed under the knowledge sharing Attribution – non-commercial use – no deduction 4.0 international license agreement.