Cover: Luo Xiaoxi

Author: Pan Pan

In the next half year, I will be honored to study and communicate with my mentors. I will take this opportunity to record what I have learned and felt.

After graduation, I started my own business first and then went to work, floating up and down for nearly 8 years. I was really anxious in my heart. Although I always took the course, I did not calm down to study, think and comb on the technical road.

I hope that the knowledge I have recorded will be of some help to the later learners.

If you have any suggestions or opinions on the content of this article, or hope to exchange and learn about Internet development, or simply love life, please feel free to wechat me: Panshenlian

The first series of articles focused on the “Architect (Java) Technical Thread” and was updated from time to time.

The first article I to “handwriting a set of persistence layer framework” to play a sample, this article we do not introduce MyBatis, will not analyze the source code, we first talk about a Java API: JDBC.

JDBC is Java’s old friend, let’s get to know him again, pick his faults, stand on the perspective of Java’s old friend, give him some optimization advice, and send him a set of “custom persistence layer framework”.

Tips:

If you are in the process of reading, there are questions about some solutions, I suggest that you first read with questions, digestion understanding, because tutors are indeed through the study of Mybatis and other persistent layer framework source code, in turn analysis.

Simply put, “Big factories write like this, and we follow it like this.”

Mybaits Series full solutions (continuously updated)

  • Mybatis series full solution (a) : handwriting a set of persistent layer frame
  • Mybatis series full solution (2) : Mybatis introduction and environment construction
  • Mybatis series full solution (3) : Mybatis simple CRUD use introduction
  • Mybatis series full solution (four) : the most complete network! Mybatis configuration file XML overview
  • Mybatis series full solution (5) : the most complete network! Mybatis Mapper mapping file
  • Mybatis series full solution (6) : Mybatis most core API you know how many?
  • Mybatis series full solution (7) : Dao layer two ways to achieve
  • Mybatis series full solution (8) : Mybatis dynamic SQL
  • Mybatis series full solution (9) : Complex mapping of Mybatis
  • Mybatis series full solution (10) : Mybatis annotation development
  • Mybatis series full solution (11) : Mybatis cache full solution
  • Mybatis plug-in development
  • Mybatis series full solution (13) : Mybatis code generator
  • Spring integrates Mybatis
  • Mybatis series full solution (15) : SpringBoot integrated Mybatis
  • Mybatis series full solution (16) : Mybatis source code analysis

Who is JDBC?

JDBC is who? Stem what? How much can you fight? See what our friends on the Internet have to say.

Java Database Connectivity (JDBC) is an application programming interface in the Java language that regulates how a client program accesses a Database, providing methods such as querying and updating data in a Database.

— From Baidu Encyclopedia

JDBC(Java DataBase Connectivity) is a Java API for executing SQL statements. It provides unified access to multiple relational databases. It consists of a set of classes and interfaces written in the Java language. JDBC provides a benchmark against which more advanced tools and interfaces can be built to enable database developers to write database applications.

— From Encyclopedia 360

. This website cannot be accessed

— From Wikipedia

So That’s basically the introduction of JDBC, the official and strict version of That’s It, and let’s move on to some of its highlights.

Since the Java language was officially announced in May 1995, Java has taken the world by storm. There are a number of programs written in the Java language, including database applications. Without a Java API, programmers had to add ODBC function calls in C to their Java programs. This prevents many of Java’s best features, such as platform independence and object-oriented features, from being fully exploited. With more and more programmers on the Java language increasingly love, more and more companies in Java program development to invest more and more energy, the Java language interface to access the database API requirements more and more intense. Also due to ODBC has its shortcomings, such as it is not easy to use, no object-oriented features and so on, SUN decided to develop a Java language interface for database application development interface. JDBC was an optional component in jdk1.x, and the SQL class package (also known as JDBCAPI) became a standard component of the Java language when JDK1.1 was released.

Later from JDBC1.0 to JDBC4.0, all the way.

— from the Internet

With the introduction to deepen our understanding of JDBC.

But I wonder how he works on a normal day? Take a look at the JDBC Basic Architecture:

With JDBC, sending SQL statements to a variety of relational databases is a breeze.

In other words, instead of having to write a program to access a Sybase database, another program to access an Oracle database, another program to access a Mysql database, and so on, a programmer can just write a program using the JDBC API. It sends SQL calls to the corresponding database.

At the same time, the combination of the Java language and JDBC allows programmers to write a program once and run it on any platform instead of writing different applications for different platforms, which is the “write once, run everywhere” advantage of the Java language.

Let’s look at the details of his work.

After all, it’s been said that the way to understand a person is to understand their work.

How does JDBC work?


The JDBC API allows applications to access tabular data in any form, especially data stored in relational databases.
The execution process is mainly divided into three steps:
  • Connect to the data source.
  • Pass query and update instructions to the database.
  • Process the database response and return the result.
But in reality, every step of the process is extremely detailed:

Use process (details)

1. Load the database driver:

The JVM will look for and load the specified Driver Class, and execute the static code segment of the Driver Class. Prior to JDK1.6, the JDBC specification specified that each Driver Class must register an instance with DriverManager in the static code segment. Driver classes implemented after JDK1.6 no longer need to register instances actively because DriverManager already scans and initializes all jar classes that implement java.sql.Driver during the initialization phase.

  1. Create database connection:

DriverManager iterates through all registered drivers to try to get a connection. If the first one matches, it returns directly and uses the corresponding driver to establish a network connection between the client and the database server.

  1. Create a compiled object:

Once a connection is made, a statement is sent to the database. A single SQL statement is executed. A connection can be executed multiple times, unless the connection is closed. It can also be one-to-many, depending on whether you want to commit multiple request statements as the same transaction, or commit one transaction at a time. JDBC default is that transactions are committed automatically, that is, auto-commit is enabled, so the default is one-to-one.

  1. Set input parameter to execute SQL:

To prevent SQL injection, we use preprocessing in SQL. As placeholders for input parameters, the SQL is compiled into a secure SQL statement that can then be queried (we can talk about why preprocessing prevents SQL injection).

  1. Encapsulate the return result set:

Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util. Also, the ResultSet class itself does not provide hasNext methods, so we are constantly positioning while(rs.next()) to read data through different types of accessors (such as getString,getInteger, etc.).

  1. Release database connection resources:

Considering that the database connection occupies the memory resources of the database server, so it is impossible to establish unlimited connection, release after use, and form a good habit. At present, many mature data connection pool technologies can optimize the data connection problem of the management.

This example uses JDBC to operate the mysql database. Let’s look at the structure of our final project and the structure of the JDBC API in JDK rt.jar:
  • Project Structure:

  • JDBC API rt.jar in JDK

The Java development environment and mysql database are available by default

  1. Create the Mave project and introduce mysql driver dependencies

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.25</version>
    </dependency>
</dependencies>

Copy the code
  1. Create a Java test class
    
package com.panshenlian.jdbc;

import com.panshenlian.po.User;

import java.sql.*;

/ * * *@Author: panshenlian
 * @Description: Demonstrates connecting to the mysql database over JDBC *@Date: Create in 20:11 2020/11/10
 */
public class Test01 {

    public static void main(String[] args) {
        User user = new User();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // Load the database driver
            Class.forName("com.mysql.jdbc.Driver");
            // Get the database connection through the driver management class
            connection = 
               DriverManager.getConnection(
                  "jdbc:mysql://localhost:3306/mybatis"+
                  "? characterEncoding=utf-8"."root"."123456");
            // Define SQL statements? Represents a placeholder
            String sql = " select * from user where username = ? ";
            // Get the preprocessed Statement object
            preparedStatement = connection.prepareStatement(sql);
            // Set parameters
            // The first argument is the sequence number of the arguments in the SQL statement (starting from 1).
            // The second argument is the value of the set argument
            preparedStatement.setString(1."panshenlian");
            // Issue SQL to the database to execute the query and query the result set
            resultSet = preparedStatement.executeQuery();
            // Iterate over the query result set
            while(resultSet.next()){
                int id = resultSet.getInt("id");
                String name = resultSet.getString("username");
                / / encapsulates the Useruser.setId(id); user.setUserName(name); System.out.println(user); }}catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Release resources
           if(resultSet! =null) {try {
                   resultSet.close();
               } catch(SQLException e) { e.printStackTrace(); }}if(preparedStatement! =null) {try {
                  preparedStatement.close();
              } catch(SQLException e) { e.printStackTrace(); }}if(connection! =null) {try {
                  connection.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
           }
        }
    }
}

Copy the code
  1. Create a User class

package com.panshenlian.po;

/ * * *@Author: panshenlian
 * @Description: user entity *@Date: Create in 20:10 2020/11/10
 */
public class User {

    private  Integer id;
    private String userName;

    public Integer getId(a) {
        return id;
    }

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

    public String getUserName(a) {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString(a) {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\' ' +
                '} '; }}Copy the code
  1. Create SQL statement

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `birthday` varchar(50) DEFAULT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1'.'senly'.'123'.'2020-11-10');
INSERT INTO `user` VALUES ('2'.'panshenlian'.'123456'.'2020-11-10');

Copy the code
  1. Execution result, nice, success.

User{id=2, userName='panshenlian'}

Copy the code

After watching this demo, do you see a problem? The whole process of using a JDBC operation database is tedious and awkward, as in this conversation:

The forehead (even though it’s o… JDBC is really annoying to you.

I understand that you need to establish connections to databases, execute SQL statements, process query result sets…

But, this whole process, can’t be optimized?

What are the optimizations for JDBC?


We usually lose weight and increase muscle, work to improve quality and efficiency, let’s break the code, one by one analysis:

			
// Load the database driver
Class.forName("com.mysql.jdbc.Driver");
// Get the database link from the driver management class
connection = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8"."root"."123456");
                    
Copy the code
  • Fault 1: The database configuration information is hard coded.

    Use configuration files!

  • Problem 2: Frequently create or release database connections.

    Use data connection pooling!

			
 // Define SQL statements? Represents a placeholder
 String sql = " select * from user where username = ? ";
 // Get the preprocessed Statement object
 preparedStatement = connection.prepareStatement(sql);
 The first parameter is the serial number (starting from 1) of the parameter in the SQL statement. The second parameter is the value of the parameter
 preparedStatement.setString(1."tom");
 // Issue SQL to the database to execute the query and query the result set
 resultSet = preparedStatement.executeQuery();
                    
Copy the code
  • Problem 3: There are hard coding problems in SQL statements, setting parameters, and obtaining result set parameters.

    Use configuration files!


// Iterate over the query result set
while(resultSet.next()){
   int id = resultSet.getInt("id");
   String userName = resultSet.getString("username");
   / / encapsulates the User
   user.setId(id);
   user.setUserName(userName);
   System.out.println(user);
}
                    
Copy the code
  • Problem 4: Manual encapsulation of returned result sets is cumbersome.

    Use Java reflection, introspection!

In view of the deficiencies in each link of JDBC, we now sort out the corresponding optimization ideas and summarize them uniformly:

There is a problem Optimization idea
The database configuration information is hard coded Using configuration files
Frequent database connection creation and release problems Use data connection pooling
There are hard coding problems in SQL statements, setting parameters and obtaining result set parameters Using configuration files
Manual encapsulation of the returned result set is cumbersome Use Java reflection, introspection

If you had to optimize, how would you design a persistence layer framework based on these optimization ideas?

Four, custom persistence layer framework: thinking analysis


JDBC is a personal battle, everything is done by yourself, inefficient and high risk, load drivers, build their own connections, their own…

The persistence layer framework is like a multi-work collaboration, clear division of labor, efficient execution, dedicated to the analysis of registration drive to establish a connection, dedicated to the management of the data connection pool, dedicated to the execution of SQL statements, dedicated to pre-processing parameters, dedicated to the assembly of result sets…

The purpose of the framework is to help us reduce the heavy development details and redundant code, so that we can focus more on business application development.Copy the code
What is the difference between using JDBC and using the persistence framework?
How comfortable is it for us users (mostly developers) to use frameworks?

Finding it so comfortable to have a durable framework, we just need to do two things:

  • Configure the data source (address/data name/username/password)
  • Writing SQL and parameter preparation (SQL statement/parameter type/return value type)
The framework, in addition to considering its own engineering design, also needs to take into account the actual use scenarios of the project, involving both ends of the stakeholders:
  • Usage side (actual project)

  • The persistence layer frame itself

The above two steps are sorted out through an architecture diagram “Basic Ideas of Handwriting Persistence Layer Framework” :

Core interface/class highlights:
collaboration role The name of the class definition
Read configuration files Resource helper class Resources
Store database connection information Database Resource classes Configuration
Responsible for storing SQL mapping definitions and storing result set mapping definitions SQL and result set resource classes MappedStatement
Responsible for parsing configuration files and creating session factory SqlSessionFactory Session factory builder SqlSessionFactoryBuilder
Create session SqlSession The session factory SqlSessionFactory
Assign an Executor The session SqlSession
Responsible for executing SQL (matching specified resource Mapped Statement) actuator Executor

Normally, the project only corresponds to a set of database environment, generally corresponds to a SqlSessionFactory instance object, we use the singleton mode to create only one SqlSessionFactory instance.

If you need to configure more than one database environment, you need to extend it. For example, Mybatis supports switching between multiple test/production databases using environments.

After sorting out the basic idea of the persistence layer framework and clarifying the division of labor of each role of the framework, we began to sort out the detailed scheme:

A. The project user side calls the framework API. In addition to introducing the JAR package of the persistence layer framework, two additional configuration information should be provided:

Sqlmapconfig. XML: database configuration information (address/data name/user name/password) and the full path of mapper. XML. Mapper. XML: stores INFORMATION about SQL statements, parameter types, and return value types.Copy the code

B. The framework itself is essentially the encapsulation of JDBC code in 6 basic steps:

  1. Loading a configuration file: Loads the configuration file as a byte input stream and stores it in memory based on the configuration file path.
Create a Resource class that provides a method to load a stream: InputStream getResourceAsStream(String path)Copy the code
  1. Create two Javabeans (container objects) that hold the content parsed out of the configuration file
Configuration (core Configuration class) : Stores the contents parsed from SQLMapconfig. XML. MappedStatement (Mapping configuration class) : Stores the contents parsed by mapper. XML.Copy the code
  1. Parse the configuration file (using DOM4J) and create the SqlSession session object
Create class SqlSessionFactoryBuilder Build (InputStream in) > use DOM4J to parse the configuration file, encapsulate the parsed content in the container object > Create SqlSessionFactory object, produce sqlSession session object (factory mode)Copy the code
  1. Create the SqlSessionFactory interface and implement class DefaultSqlSessionFactory
Create openSession() interface method to produce sqlSessionCopy the code
  1. Create SqlSession interface and implement DefaultSqlSession class
Define CRUD operations on the database: > selectList(); > selectOne(); > update(); > delete();Copy the code
  1. Create the Executor interface and implement the SimpleExecutor class
Create query(Configuration conf,MappedStatement MS,Object... Params actually executes the JDBC code.Copy the code

The basic process has been clear, so let’s refine the class diagram to better facilitate our actual coding:

Simple version of

A detailed version

Final handwritten persistent layer frame structure reference:

Packet interface class description
  • The config package
Interface/class role
BoundSql Replace Sql #{} with? Number and stores the parameter name corresponding to #{}
XMLConfigBuilder Sqlmapconfig. XML configuration file parsing utility class
XMLMapperBuilder Mapper. XML configuration file parsing utility class
  • iopackage
Interface/class role
Resource Read sqlMapconfig. XML and mapper. XML utility classes and convert them into input streams inputStream
  • pojopackage
Interface/class role
Configuration Encapsulates the SQLmapconfig.xml configuration parameter
MappedStatement Encapsulate the SQL parameters of the mapper.xml configuration
  • sqlSessionpackage
Interface/class role
SqlSessionFactoryBuilder SqlSessionFactory builder class
SqlSessionFactory The factory interface that produces SQLSessions
DefaultSqlSessionFactory The default implementation class for SqlSessionFactory
SqlSession The SqlSession interface defines the basic CRUD methods of the database
DefaultSqlSession SqlSession implementation class
Executor Executor interface the real Executor of SQL, using JDBC to manipulate databases
SimpleExecutor Executor implementation class
  • utils
Interface/class role
ParameterMapping #{}, ${}, ${}, ${
TokenHandler From Mybatis framework, tag processor interface
ParameterMappingTokenHandler #{}, ${} become?
GenericTokenParser From Mybatis framework, general purpose tag parser, tag #{and} start and end processing

Custom persistence layer framework: coding


Combining UML diagrams with project structure diagrams, something is starting to come to mind, and the tedious coding process, let’s get started.

The framework relies on POM.xml


      
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.panshenlian</groupId>
    <artifactId>MyPersistence</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <! Dependencies required by the persistence layer framework -->
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

</project>
Copy the code

BoundSql class under config package

package com.panshenlian.config;

import com.panshenlian.utils.ParameterMapping;

import java.util.ArrayList;
import java.util.List;

/ * * *@Author: panshenlian
 * @Description: SQL wildcard *@Date: Create in 16:12 2020/11/12
 */
public class BoundSql {

    /** * Parsed SQL statement */
    private String sqlText;

    private List<ParameterMapping> parameterMappingList =
            new ArrayList<ParameterMapping>();

    public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
        this.sqlText = sqlText;
        this.parameterMappingList = parameterMappingList;
    }

    public String getSqlText(a) {
        return sqlText;
    }

    public void setSqlText(String sqlText) {
        this.sqlText = sqlText;
    }

    public List<ParameterMapping> getParameterMappingList(a) {
        return parameterMappingList;
    }

    public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
        this.parameterMappingList = parameterMappingList; }}Copy the code

XMLConfigBuilder class in config package

package com.panshenlian.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.panshenlian.io.Resource;
import com.panshenlian.pojo.Configuration;
import com.sun.javafx.scene.control.skin.EmbeddedTextContextMenuContent;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/ * * *@Author: panshenlian
 * @Description: database configuration information parsing class *@Date: Create in 13:56 2020/11/12
 */
public class XMLConfigBuilder {

    private Configuration configuration;

    public XMLConfigBuilder(a) {
        this.configuration = new Configuration();
    }

    public Configuration parseConfig(InputStream inputStream) throws Exception {

        Document document = new SAXReader().read(inputStream);
        Element configurationRootElement = document.getRootElement();

        // parses the dataSource config dataSource parameter information
        List<Element> elementList = configurationRootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : elementList){
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.put(name,value);
        }

        // Use c3P0 data source
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(properties.getProperty("driverClass"));
        dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        dataSource.setUser(properties.getProperty("userName"));
        dataSource.setPassword(properties.getProperty("password"));

        // Set the data source
        configuration.setDataSource(dataSource);

        // Parse mapper. XML, read the byte input stream according to the path, parse using dom4j
        List<Element> mapperElementList = configurationRootElement.selectNodes("//mapper");
        for (Element element : mapperElementList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parseMapper(resourceAsStream);
        }

        returnconfiguration; }}Copy the code

Config package XMLMapperBuilder class

package com.panshenlian.config;

import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/ * * *@Author: panshenlian
 * @Description: SQL configuration information parsing class *@Date: Create in 14:28 2020/11/12
 */
public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public void parseMapper(InputStream inputStream) throws DocumentException {

        Document mapperDocument = new SAXReader().read(inputStream);
        Element rootElement = mapperDocument.getRootElement();
        String namespace = rootElement.attributeValue("namespace");

        // Parse each select node
        List<Element> selectNodes = mapperDocument.selectNodes("//select");
        for (Element element : selectNodes) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sql = element.getTextTrim();

            // Parse the wrapper into the MapperdStatement object
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setSql(sql);
            String statementId = namespace + "."+ id; configuration.getMappedStatementMap().put(statementId,mappedStatement); }}}Copy the code

IO package Resource utility class

package com.panshenlian.io;

import java.io.InputStream;

/ * * *@Author: panshenlian
 * @Description: Resource *@Date: Create in 9:22 2020/11/12
 */
public class Resource {

    /** * Load the configuration file into a byte input stream and store it in memory * according to the configuration file path@param path
     * @return* /
    public static InputStream getResourceAsStream(String path){
        InputStream inputStream = Resource.class.getClassLoader().getResourceAsStream(path);
        returninputStream; }}Copy the code

Pojo Configuration under the package

package com.panshenlian.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/ * * *@Author: panshenlian
 * @Description: Database configuration *@Date: Create in 13:58 2020/11/12
 */
public class Configuration {

    private DataSource dataSource;

    /**
     * key:statementId
     * value:封装好的mappedStatement对象
     */
    private Map<String,MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();

    public DataSource getDataSource(a) {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap(a) {
        return mappedStatementMap;
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap; }}Copy the code

Pojo MappedStatement under the package

package com.panshenlian.pojo;

/ * * *@Author: panshenlian
 * @Description: SQL and result set resource classes (responsible for storing SQL mapping definitions, storing result set mapping definitions) *@Date: Create in 14:17 2020/11/12
 */
public class MappedStatement {

    /** * ID */
    private String id;

    /** * Return value type */
    private String resultType;

    /** * Parameter value type */
    private String parameterType;

    /** * SQL statement */
    private String sql;

    public String getId(a) {
        return id;
    }

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

    public String getResultType(a) {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getParameterType(a) {
        return parameterType;
    }

    public void setParameterType(String parameterType) {
        this.parameterType = parameterType;
    }

    public String getSql(a) {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql; }}Copy the code

SqlSession DefaultSqlSession under the package

package com.panshenlian.sqlSession;

import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;

import java.lang.reflect.*;
import java.util.List;

/ * * *@Author: panshenlian
 * @Description: SQL session implementation class *@Date: Create in 14:43 2020/11/12
 */
public class DefaultSqlSession implements SqlSession{

    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {

        // Build SQL executable
        SimpleExecutor simpleExecutor = new SimpleExecutor();

        // get the final execution SQL object
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);

        // 3, execute SQL, return result set
        List<Object> queryResultList = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>)queryResultList;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if (null! = objects && objects.size() ==1) {return (T)objects.get(0);
        } else {
           throw  new RuntimeException("Query result is null or more than 1 result is returned"); }}@Override
    public int update(String statementId, Object... params) {
        return 0;
    }

    @Override
    public int delete(String statementId, Object... params) {
        return 0;
    }

    @Override
    public <T> T getMapper(Class
        mapperClass) {

        // Use the JDK dynamic proxy to generate proxy objects for the Dao interface and return the call results
        Object proxyInstance =  Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                // The bottom layer is still executing JDBC
                Call selectList or selectOne, depending on the case
                Parameter statementId = Unique identifier of the SQL statement: namespace.id = Fully qualified name of the interface. The method name
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;

                // 2. Prepare the parameter params (args)
                // Gets the return value type of the called method
                Type genericReturnType = method.getGenericReturnType();
                // Determine whether generic type parameterization is performed
                if ( genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }

                returnselectOne(statementId,args); }});return(T)proxyInstance; }}Copy the code

SqlSession DefaultSqlSessionFactory under the package

package com.panshenlian.sqlSession;

import com.panshenlian.pojo.Configuration;

/ * * *@Author: panshenlian
 * @Description: Default SqlSession factory implementation class *@Date: Create in 14:41 2020/11/12
 */
public class DefaultSqlSessionFactory implements  SqlSessionFactory{

    private Configuration configuration;

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

    @Override
    public SqlSession openSession(a) {
        return newDefaultSqlSession(configuration); }}Copy the code

SqlSession package under the Executor

package com.panshenlian.sqlSession;

import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;

import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.List;

/ * * *@Author: panshenlian
 * @Description: SQL executor interface *@Date: Create in 15:02 2020/11/12
 */
public interface Executor {

    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

}

Copy the code

SqlSession SimpleExecutor under the package

package com.panshenlian.sqlSession;

import com.mysql.jdbc.StringUtils;
import com.panshenlian.config.BoundSql;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import com.panshenlian.utils.GenericTokenParser;
import com.panshenlian.utils.ParameterMapping;
import com.panshenlian.utils.ParameterMappingTokenHandler;

import java.beans.ExceptionListener;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/ * * *@Author: panshenlian
 * @Description: SQL executor interface simple implementation class *@Date: Create in 15:55 2020/11/12
 */
public class SimpleExecutor implements Executor {

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {

        // 1. Register the driver and obtain the database connection
        Connection connection = configuration.getDataSource().getConnection();

        Select * from user where id = #{id}
        Select * from user where id =?
        // The conversion process also needs to parse the values in #{}
        String sql = mappedStatement.getSql();
        BoundSql bounSql = getBoundSql(sql);

        // Get the prepared object preparedStatement
        PreparedStatement preparedStatement =
                connection.prepareStatement(bounSql.getSqlText());

        // 4. Set parametersString parameterType = mappedStatement.getParameterType(); Class<? > parameterTypeClass = getClassType(parameterType); List<ParameterMapping> parameterMappingList = bounSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String filedName = parameterMapping.getContent();

            / / reflection
            Field declaredField = parameterTypeClass.getDeclaredField(filedName);
            // Violent access
            declaredField.setAccessible(true);
            Object declaredFieldValue = declaredField.get(params[0]); Params [0] is the object
            preparedStatement.setObject(i+1,declaredFieldValue);
        }

        // execute SQLResultSet resultSet = preparedStatement.executeQuery(); String resultType = mappedStatement.getResultType(); Class<? > resultTypeClass = getClassType(resultType); List<Object> objects =new ArrayList<Object>();

        // encapsulate the return result set
        while (resultSet.next()){
            Object o = resultTypeClass.newInstance();
            / / metadata
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                / / the field name
                String columnName = metaData.getColumnName(i);
                / / field values
                Object columnValue = resultSet.getObject(columnName);

                // Use introspection (reflection) to complete encapsulation according to the correspondence between database tables and entities
                PropertyDescriptor propertyDescriptor =
                        new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,columnValue);
            }
            objects.add(o);
        }
        return (List<E>)objects;
    }

    /** * Get the class * based on the full-path reflection of the argument@param parameterType
     * @return* /
    privateClass<? > getClassType(String parameterType)throws ClassNotFoundException {
        if (StringUtils.isNullOrEmpty(parameterType)) {
            return null; } Class<? > clazz = Class.forName(parameterType);return clazz;
    }

    /** * select * from '#{}'; 2. Parse the value inside #{} and store *@param sql
     * @return* /
    private BoundSql getBoundSql(String sql) {

        // The tag handler class configures the tag parser to handle placeholders
        ParameterMappingTokenHandler parameterMappingTokenHandler
                = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser =
                new GenericTokenParser("# {"."}",
                        parameterMappingTokenHandler);

        // Parse the SQL
        String parseSql = genericTokenParser.parse(sql);
        // The parsed parameter name
        List<ParameterMapping> parameterMappings =
                parameterMappingTokenHandler.getParameterMappings();

        // Encapsulate the result into wildcard SQL
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        returnboundSql; }}Copy the code

SqlSession sqlSession under the package

package com.panshenlian.sqlSession;

import java.util.List;

/ * * *@Author: panshenlian
 * @Description: Sql session interface *@Date: Create in 14:40 2020/11/12
 */
public interface SqlSession {

    /** * query all *@param statementId
     * @param params
     * @param <E>
     * @return* /
    public <E> List<E> selectList(String statementId , Object ... params) throws Exception;

    /** * query a single * based on the condition@param statementId
     * @param params
     * @param <T>
     * @return* /
    public <T> T selectOne(String statementId , Object ... params) throws Exception;

    /** * update * according to conditions@param statementId
     * @param params
     * @return* /
    public int update(String statementId , Object ... params);

    /** * delete * according to condition@param statementId
     * @param params
     * @return* /
    public int delete(String statementId , Object ... params);

    /** * Generates the proxy implementation class * for the Dao interface@param mapperClass
     * @param <T>
     * @return* /
    public <T> T getMapper(Class
        mapperClass);

}

Copy the code

SqlSession SqlSessionFactory under the package

package com.panshenlian.sqlSession;

/ * * *@Author: panshenlian
 * @Description: SqlSession factory interface *@Date: Create in 13:51 2020/11/12
 */
public interface SqlSessionFactory {

    public SqlSession openSession(a);
}

Copy the code

The SqlSessionFactoryBuilder is under the sqlSession package

package com.panshenlian.sqlSession;

import com.panshenlian.config.XMLConfigBuilder;
import com.panshenlian.pojo.Configuration;

import java.io.InputStream;

/ * * *@Author: panshenlian
 * @Description: SqlSession Session factory build class *@Date: Create in 13:48 2020/11/12
 */
public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream inputStream) throws Exception {

        // Step 1: Use dom4J to parse the Configuration file and encapsulate the parsed content in Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);

        SqlSessionFactory = SqlSessionFactory = SqlSessionFactory
        DefaultSqlSessionFactory defaultSqlSessionFactory =
                new DefaultSqlSessionFactory(configuration);

        returndefaultSqlSessionFactory; }}Copy the code

GenericTokenParser utils package

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

/** * generic tag parser, tags #{and} to start and end processing *@author Clinton Begin
 */
public class GenericTokenParser {

  private final String openToken; // Start the tag
  private final String closeToken; // End tag
  private final TokenHandler handler; // Mark the handler

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

  /** * parse ${} and #{} *@param text
   * @return* This method mainly realizes the configuration file, script and other fragments of placeholder parsing, processing work, and return the final data needed. * Where parsing is done by this method and processing is done by the handler's handleToken() method */
  public String parse(String text) {
    // Validate argument problem, if null, return empty string.
    if (text == null || text.isEmpty()) {
      return "";
    }

    // If the start tag is not included, the default is not a placeholder. If the start tag is not included, the default is not placeholder.
    int start = text.indexOf(openToken, 0);
    if (start == -1) {
      return text;
    }

   // Convert text to character array SRC, and define the default offset=0, store the variable builder that eventually needs to return a string,
    // text specifies the variable name corresponding to the placeholder in the variable expression. Determine if start is greater than -1(that is, if openToken exists in text) and if so, execute the following code
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
     If there is an escape character before the start token, it will not be processed as openToken, otherwise it will continue to be processed
      if (start > 0 && src[start - 1] = ='\ \') {
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // Reset the expression variable to avoid null Pointers or old data interference.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {//// if an end flag exists
          if (end > offset && src[end - 1] = ='\ \') {// If the end tag is preceded by an escape character
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {// There are no escape characters that need to be processed as arguments
            expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break; }}if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          // The parameter is processed according to the key (expression) of the parameter. As a placeholder
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    returnbuilder.toString(); }}Copy the code

ParameterMapping utils package

package com.panshenlian.utils;

/ * * *@Author: panshenlian
 * @DescriptionParameter mapping class (SQL parameter mapping class, store parameter names in #{}, ${}) *@Date: Create in 16:14 2020/11/12
 */
public class ParameterMapping {

    private String content;

    public ParameterMapping(String content) {
        this.content = content;
    }

    public String getContent(a) {
        return content;
    }

    public void setContent(String content) {
        this.content = content; }}Copy the code

ParameterMappingTokenHandler utils package

package com.panshenlian.utils;

import java.util.ArrayList;
import java.util.List;

/** * tags the handler implementation class, parsing #{}, ${} into? * /
public class ParameterMappingTokenHandler implements TokenHandler {
	private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

	// context is the parameter name #{id} #{username}

	public String handleToken(String content) {
		parameterMappings.add(buildParameterMapping(content));
		return "?";
	}

	private ParameterMapping buildParameterMapping(String content) {
		ParameterMapping parameterMapping = new ParameterMapping(content);
		return parameterMapping;
	}

	public List<ParameterMapping> getParameterMappings(a) {
		return parameterMappings;
	}

	public void setParameterMappings(List<ParameterMapping> parameterMappings) {
		this.parameterMappings = parameterMappings; }}Copy the code

TokenHandler utils package

/** * Copyright 2009-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the  License. */
package com.panshenlian.utils;

/** * marks the processor interface *@author Clinton Begin
 */
public interface TokenHandler {
  String handleToken(String content);
}


Copy the code

Once the framework is written, we write a test project to verify the framework. We add a new test project (in the form of a module) under the existing framework to ensure that the test project and the framework project are under the same working group:

Since I have already written the test project, I can directly introduce it, the effect is the same, create and import in module mode can be:

The basic process of test engineering is also explained:

1. Introduce dependency pom.xml


      
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.panshenlian</groupId>
    <artifactId>MyPersistenceTest</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <! -- Introducing custom persistence layer framework dependencies -->
    <dependencies>
        <dependency>
            <groupId>com.panshenlian</groupId>
            <artifactId>MyPersistence</artifactId>
            <version>1.0 the SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
Copy the code

2. Configure the data source SQLmapconfig.xml

<configuration>

    <! -- Database configuration information -->
    <dataSource>
        <property name="driverClass"
                  value="com.mysql.jdbc.Driver" ></property>
        <property name="jdbcUrl"
                  value="jdbc:mysql:///mybatis" ></property>
        <property name="userName"
                  value="root" ></property>
        <property name="password"
                  value="123456" ></property>
    </dataSource>

    <! -- Mapper.xml full path applied -->
    <mapper resource="userMapper.xml"></mapper>
    <mapper resource="orderMapper.xml"></mapper>

</configuration>
Copy the code

3, We take the user table as an example to establish the user SQL configuration usermapper.xml

<mapper namespace="com.panshenlian.dao.IUserDao">

    <! StatementId --> statementId --> statementId
    <select id="findAll"
            resultType="com.panshenlian.pojo.User">
        select * from user
    </select>

    <! -- User user = new User(); user.setId(1); user.setUsername("panshenlian"); -->
    <select id="findByCondition"
            resultType="com.panshenlian.pojo.User"
            parameterType="com.panshenlian.pojo.User">
        select * from user
        where id= #{id} and username = #{username}
        and password= #{password} and birthday = #{birthday}
    </select>


</mapper>
Copy the code

4. User DAO interface

package com.panshenlian.dao;

import com.panshenlian.pojo.User;

import java.util.List;

/ * * *@Author: panshenlian
 * @Description:
 * @Date: Create in 21:35 2020/11/12
 */
public interface IUserDao {

    /** * Query all users *@return
     * @throws Exception
     */
    public List<User> findAll(a) throws Exception;

    /** * Perform user queries based on conditions *@return
     * @throws Exception
     */
    public User findByCondition(User user) throws Exception;

}

Copy the code

5. Entity class of the user DAO

package com.panshenlian.pojo;

/ * * *@Author: panshenlian
 * @Description: user entity *@Date: Create in 9:20 2020/11/12
 */
public class User {

    private Integer id;
    private String username;
    private String password;
    private String birthday;

    public String getPassword(a) {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getBirthday(a) {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    public Integer getId(a) {
        return id;
    }

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

    public String getUsername(a) {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString(a) {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\' ' +
                ", password='" + password + '\' ' +
                ", birthday='" + birthday + '\' ' +
                '} '; }}Copy the code

Note: The namespace in the user SQL configuration file usermapper.xml needs to be the same as the fully qualified name of the user DAO, which is our framework default rule: Namespace = “com. Panshenlian. Dao. IUserDao” at the same time, the select tag id and user dao interface is consistent with the name of the method, the framework is also the default rules, such as id = “the.findall”

6. Eventually we created the test class MyPersistenceTest

package com.panshenlian.test;

import com.panshenlian.dao.IUserDao;
import com.panshenlian.io.Resource;
import com.panshenlian.pojo.User;
import com.panshenlian.sqlSession.SqlSession;
import com.panshenlian.sqlSession.SqlSessionFactory;
import com.panshenlian.sqlSession.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

/ * * *@Author: panshenlian
 * @Description: Persistence layer framework test class *@Date: Create in 9:24 2020/11/12
 */
public class MyPersistenceTest {

    @Test
    public void test(a) throws Exception {
        InputStream resourceAsStream =
                Resource.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // Traditional DAO calls
        User user = new User();
        user.setId(3);
        user.setUsername("panshenlian");
        user.setBirthday("2020-11-12");
        user.setPassword("123456");
        User dbUser = sqlSession.selectOne("com.panshenlian.dao.IUserDao.findByCondition",user);
        System.out.println(dbUser);
        List<User> userList = sqlSession.selectList("com.panshenlian.dao.IUserDao.findAll", user);
        for (User db : userList) {
            System.out.println(db);
        }

        // Proxy mode call
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        List<User> users = userDao.findAll();
        for (User db : users) {
            System.out.println("Proxy call ="+ db); }}}Copy the code

7. Run the test class, and the result meets the expectation

We have basically completed the framework and test verification. In fact, the above is mainly a simple framework introduction for the persistence layer framework. In terms of Mybatis framework analysis, we have basically achieved a simulation prototype, and the process is roughly like this.

The implementation of coding involves several interesting points that we will talk about later, including:

  • Introspection mechanism
  • Reflection mechanism
  • JDK dynamic proxy
  • Design patterns
  • The generic

conclusion

Now large projects typically do not use JDBC directly, or using mature persistence layer solution on the market, either from the persistence layer framework, at the end of the day, or a simple JDBC can not ensure the stability of the high efficient data access and application layer, and the persistence layer framework scheme is growing, not only eliminate a lot of JDBC redundant code, It also provides a very low learning curve, which not only ensures collaboration with traditional databases but also accepts SQL statements, but also provides extended integration support for other frameworks, including connection pool, cache, performance and so on, which has been greatly optimized and improved, so the popularity of the framework is an inevitable trend.

JDBC was also brilliant and great when it was born in the 1990s, but as the technology level jumped and the business scene was updated iteratively, the old technology could not meet the existing demands, everything would be updated in rotation, and we just stood on the shoulders of the great people, along with the change.

Well, that’s all. Good night.

In the next post, we may talk about Mybatis infrastructure and architecture.

BIU ~ the article continues to update, wechat search “Pan Pan and his friends” the first time to read, there are surprises at any time. This article will be included on GitHub github.com/JavaWorld, hot technology, framework, surface, solution, we will be the most beautiful posture in the first time, welcome Star.