WeChat number: public bugstack wormhole stack | blog: bugstack. Cn precipitation, share, grow, and focus on the original project cases, to share knowledge in the most easy to learn programming way, let oneself and others to learn something. Projects completed so far are; Netty4.x practical case, implementing JVM with Java, full-link monitoring based on JavaAgent, handwritten RPC framework, architectural design case, source analysis, etc. You use the sword 🗡, I use the knife 🔪, good code is burning 😏, hope you don’t stingy move 💨!

I. Introduction

In front of an analysis of mybatis source code, from its why after the interface but no implementation class can perform database operations for entrance, the whole source code core process completely explained again. For a programmer of more than 3 years, the learning process of new knowledge should be from the beginning of HelloWorld to proficient use of APIS to complete business functions. The next step in order to in-depth understanding of the need to read part of the core source code, so that problems can be quickly located, quickly troubleshooting. Thus reducing the duration of online accidents and increasing personal influence. But!!! This is not the end of learning, because no matter the source of any framework, it is difficult to learn its practical techniques if you just look at them. Paper comes to shallow, only actual combat and practice.

So, in this chapter we will simply implement a JDBC-based demo version of Mybatis, so as to better understand the design of such a framework. At the same time this idea will allow you to use it in other scenarios, such as writing an EsBatis for ES queries. Realized the mood or;

Case project

Expand the previous source code analysis project; Itstack-demo-mybatis, add like package, imitate Mybatis project. Complete download procedures, pay attention to the public number: bugstack wormhole stack | response: source code analysis

Itstack - demo - mybatis └ ─ ─ the SRC ├ ─ ─ the main │ ├ ─ ─ Java │ │ └ ─ ─ org. Itstack. The demo │ │ ├ ─ ─ dao │ │ │ ├ ─ ─ ISchool. Java │ │ │ └ ─ ─ Java │ ├─ like │ │ ├─ Configuration. Java │ │ ├─ Java │ │ ├─ DefaultSqlSessionFactory. Java │ │ │ ├ ─ ─ Resources. Java │ │ │ ├ ─ ─ SqlSession. Java │ │ │ ├ ─ ─ SqlSessionFactory. Java │ │ │ ├ ─ ─ the SqlSessionFactoryBuilder is. Java │ │ │ └ ─ ─ the SqlSessionFactoryBuilder is the Java │ │ └ ─ ─ interfaces │ │ ├ ─ ─ School. Java │ │ └ ─ ─ User. Java │ ├ ─ ─ resources │ │ ├ ─ ─ mapper │ │ │ ├ ─ ─ School_Mapper. XML │ │ │ └ ─ ─ User_Mapper. XML │ │ ├ ─ ─ props │ │ │ └ ─ ─ JDBC. Properties │ │ ├ ─ ─ spring │ │ │ ├ ─ ─ mybatis config - datasource. The XML │ │ │ └ ─ ─ spring - config - a datasource. XML │ │ ├ ─ ─ Logback. XML │ │ ├ ─ ─ mybatis - config. XML │ │ └ ─ ─ spring - config. XML │ └ ─ ─ webapp │ └ ─ ─ WEB - INF └ ─ ─ the test └ ─ ─ Java └ ─ ─ ├─ ├─ Java ├─ Java ├.org.itStack.demo. Test ├─ Java ├─ Java ├.org.itStack.Demo. Test ├─ Java ├─ Java ├─ Java ├.org.itStack.DemoCopy the code

Three, environment configuration

  1. JDK1.8
  2. The IDEA of 2019.3.1
  3. Dom4j 1.6.1

Iv. Code description

As for the whole Demo version, it is not to implement all Mybatis, but to show the core content to you. You will feel exactly the same in use, but the implementation classes have been replaced, and the core classes include;

  • Configuration
  • DefaultSqlSession
  • DefaultSqlSessionFactory
  • Resources
  • SqlSession
  • SqlSessionFactory
  • SqlSessionFactoryBuilder
  • XNode

1. Test the entire DemoJdbc framework

ApiLikeTest.test_queryUserInfoById()

@Test
public void test_queryUserInfoById(a) {
    String resource = "spring/mybatis-config-datasource.xml";
    Reader reader;
    try {
        reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sqlMapper.openSession();
		
        try {
            User user = session.selectOne("org.itstack.demo.dao.IUserDao.queryUserInfoById".1L);
            System.out.println(JSON.toJSONString(user));
        } finally{ session.close(); reader.close(); }}catch(Exception e) { e.printStackTrace(); }}Copy the code

This is what happens when everything goes well.

{"age":18."createTime":1576944000000."id":1."name":"Water"."updateTime":1576944000000}

Process finished with exit code 0
Copy the code

At first glance, this test class may look exactly like the MybatisApiTest. Java test code, and you won’t be able to tell the difference. In fact, their introduced packages are different;

MybatisApiTest. Java to introduce the package

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
Copy the code

A package introduced in apiliketest. Java

import org.itstack.demo.like.Resources;
import org.itstack.demo.like.SqlSession;
import org.itstack.demo.like.SqlSessionFactory;
import org.itstack.demo.like.SqlSessionFactoryBuilder;
Copy the code

Good! Let’s start analyzing the core code.

2. Load the XML configuration file

Here we use mybatis configuration file structure for analysis, in the case of not destroying the original structure, the maximum possible close to the source code. Mybatis uses two configuration files when used alone; Data source configuration and Mapper mapping configuration are as follows;

Mybatis -config-datasource. XML & datasource configuration

<? The XML version = "1.0" encoding = "utf-8"? > <! DOCTYPE configuration PUBLIC "- / / mybatis.org//DTD Config / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" Value = "JDBC: mysql: / / 127.0.0.1:3306 / itstack? UseUnicode = true" / > < property name = "username" value = "root" / > < property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/User_Mapper.xml"/> <mapper resource="mapper/School_Mapper.xml"/> </mappers> </configuration>Copy the code

User_mapper. XML and Mapper mapping configuration

<? The XML version = "1.0" encoding = "utf-8"? > <! DOCTYPE mapper PUBLIC "- / / mybatis.org//DTD mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > < mapper namespace="org.itstack.demo.dao.IUserDao"> <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.po.User"> SELECT id, name, age, createTime, updateTime FROM user where id = #{id} </select> <select id="queryUserList" parameterType="org.itstack.demo.po.User" resultType="org.itstack.demo.po.User"> SELECT id, name, age, createTime, updateTime FROM user where age = #{age} </select> </mapper>Copy the code

The loading process here is different from myBaits and we use dom4J. In the case you will see the initial acquisition of resources as follows;

Apiliketest.test_queryuserinfobyid () & partial interception

String resource = "spring/mybatis-config-datasource.xml";
	Reader reader;
	try{ reader = Resources.getResourceAsReader(resource); .Copy the code

As you can see above, this is the process of obtaining the read stream through the configuration file address, thus setting the basis for parsing later. So let’s start with the Resources class, the whole thing is our resource class.

Resources.java & resource classes

No. * * * public | bugstack wormhole stack * * bo guest | https://bugstack.cn Create by small Fu Ge @ 2020 * /
public class Resources {

    public static Reader getResourceAsReader(String resource) throws IOException {
        return new InputStreamReader(getResourceAsStream(resource));
    }

    private static InputStream getResourceAsStream(String resource) throws IOException {
        ClassLoader[] classLoaders = getClassLoaders();
        for (ClassLoader classLoader : classLoaders) {
            InputStream inputStream = classLoader.getResourceAsStream(resource);
            if (null! = inputStream) {returninputStream; }}throw new IOException("Could not find resource " + resource);
    }

    private static ClassLoader[] getClassLoaders() {
        return newClassLoader[]{ ClassLoader.getSystemClassLoader(), Thread.currentThread().getContextClassLoader()}; }}Copy the code

The entry to this code method is getResourceAsReader, until this is done further down;

  1. Get a ClassLoader collection to maximize the search for configuration files
  2. Through this. GetResourceAsStream reading configuration resources, return immediately after find out, otherwise, throw an exception

3. Parse the XML configuration file

After loading the configuration file, start parsing operation, here we also imitate mybatis but simplify, as follows;

SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
Copy the code

The SqlSessionFactoryBuilder is. The build () & building entrance class

public DefaultSqlSessionFactory build(Reader reader) {
    SAXReader saxReader = new SAXReader();
    try {
        Document document = saxReader.read(new InputSource(reader));
        Configuration configuration = parseConfiguration(document.getRootElement());
        return new DefaultSqlSessionFactory(configuration);
    } catch (DocumentException e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code
  • Create the XML parsed Document class by reading the stream
  • ParseConfiguration parses XML files and sets the results to configuration classes, including; Connection pool, data source, mapper relationship

The SqlSessionFactoryBuilder is parseConfiguration () & the parsing process

private Configuration parseConfiguration(Element root) {
    Configuration configuration = new Configuration();
    configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
    configuration.setConnection(connection(configuration.dataSource));
    configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
    return configuration;
}
Copy the code
  • As you can see from the previous XML content, we need to parse out the database connection pool information datasource, as well as the database statement mapping relationship mappers

The SqlSessionFactoryBuilder is. The dataSource () and decode the data source

private Map<String, String> dataSource(List<Element> list) {
    Map<String, String> dataSource = new HashMap<>(4);
    Element element = list.get(0);
    List content = element.content();
    for (Object o : content) {
        Element e = (Element) o;
        String name = e.attributeValue("name");
        String value = e.attributeValue("value");
        dataSource.put(name, value);
    }
    return dataSource;
}
Copy the code
  • This process is relatively simple, just need to get the data source information

The SqlSessionFactoryBuilder is. Connection () & access to the database connection

private Connection connection(Map<String, String> dataSource) {
    try {
        Class.forName(dataSource.get("driver"));
        return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
    } catch (ClassNotFoundException | SQLException e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code
  • This is the original JDBC code that gets the database connection pool

The SqlSessionFactoryBuilder is mapperElement () & parse SQL statements

private Map<String, XNode> mapperElement(List<Element> list) {
    Map<String, XNode> map = new HashMap<>();
    Element element = list.get(0);
    List content = element.content();
    for (Object o : content) {
        Element e = (Element) o;
        String resource = e.attributeValue("resource");
        try {
            Reader reader = Resources.getResourceAsReader(resource);
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(new InputSource(reader));
            Element root = document.getRootElement();
            // Namespace
            String namespace = root.attributeValue("namespace");
            // SELECT
            List<Element> selectNodes = root.selectNodes("select");
            for (Element node : selectNodes) {
                String id = node.attributeValue("id");
                String parameterType = node.attributeValue("parameterType");
                String resultType = node.attributeValue("resultType");
                String sql = node.getText();
                / /? matching
                Map<Integer, String> parameter = new HashMap<>();
                Pattern pattern = Pattern.compile("(# \ \ {(. *?) })");
                Matcher matcher = pattern.matcher(sql);
                for (int i = 1; matcher.find(); i++) {
                    String g1 = matcher.group(1);
                    String g2 = matcher.group(2);
                    parameter.put(i, g2);
                    sql = sql.replace(g1, "?");
                }
                XNode xNode = new XNode();
                xNode.setNamespace(namespace);
                xNode.setId(id);
                xNode.setParameterType(parameterType);
                xNode.setResultType(resultType);
                xNode.setSql(sql);
                xNode.setParameter(parameter);
                
                map.put(namespace + "."+ id, xNode); }}catch(Exception ex) { ex.printStackTrace(); }}return map;
}
Copy the code
  • This process first involves parsing all SQL statements, currently only the SELECT correlation for testing purposes
  • All SQL statements are used to confirm uniqueness; Ids in namespace + SELECT are concatenated as keys and stored in the MAP together with SQL.
  • In the SQL statement configuration of Mybaits, there are placeholders for passing parameters. Where id = #{id} so we need to set the placeholder as a question mark. In addition, we need to store the sequence information and name of the placeholder in the map structure, so as to facilitate the subsequent setting of input parameters in the query.

4. Create DefaultSqlSessionFactory

Finally, the Configuration class after initialization is used as a parameter to create DefaultSqlSessionFactory, as follows:

public DefaultSqlSessionFactory build(Reader reader) {
    SAXReader saxReader = new SAXReader();
    try {
        Document document = saxReader.read(new InputSource(reader));
        Configuration configuration = parseConfiguration(document.getRootElement());
        return new DefaultSqlSessionFactory(configuration);
    } catch (DocumentException e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code

DefaultSqlSessionFactory. Java & SqlSessionFactory implementation class

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
	private final Configuration configuration;
    
	public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
	
    @Override
    public SqlSession openSession(a) {
        return newDefaultSqlSession(configuration.connection, configuration.mapperElement); }}Copy the code
  • This process is relatively simple; the constructor provides only configuration class entry arguments
  • OpenSession () implements SqlSessionFactory to create DefaultSqlSession, which can perform SQL operations

5. Open the SqlSession

SqlSession session = sqlMapper.openSession();
Copy the code

This step creates DefaultSqlSession, which is relatively easy. The following;

@Override
public SqlSession openSession(a) {
    return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
}
Copy the code

6. Execute the SQL statement

User user = session.selectOne("org.itstack.demo.dao.IUserDao.queryUserInfoById".1L);
Copy the code

DefaultSqlSession by implementing SqlSession, provide database statement query and close connection pool, as follows;

SqlSession. Java & definition

public interface SqlSession {

    <T> T selectOne(String statement);

    <T> T selectOne(String statement, Object parameter);

    <T> List<T> selectList(String statement);

    <T> List<T> selectList(String statement, Object parameter);

    void close(a);
}
Copy the code

Let’s look at the implementation process, session.selectOne

DefaultSqlSession. SelectOne () & executing queries

public <T> T selectOne(String statement, Object parameter) {
    XNode xNode = mapperElement.get(statement);
    Map<Integer, String> parameterMap = xNode.getParameter();
    try {
        PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
        buildParameter(preparedStatement, parameter, parameterMap);
        ResultSet resultSet = preparedStatement.executeQuery();
        List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
        return objects.get(0);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code
  • SelectOne is objects. The get (0); , the selectList is all returned

  • The statement gets the select tag information stored when the XML was originally parsed.

    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.po.User">
    	SELECT id, name, age, createTime, updateTime
    	FROM user
    	where id = #{id}
    </select>
    Copy the code
  • Get the SQL statement and hand it to JDBC’s PreparedStatement class for execution

  • Here we also need to set the input parameter, and we extract the input parameter Settings as follows;

    private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {
    
        int size = parameterMap.size();
        // Single parameter
        if (parameter instanceof Long) {
            for (int i = 1; i <= size; i++) {
                preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
            }
            return;
        }
    
        if (parameter instanceof Integer) {
            for (int i = 1; i <= size; i++) {
                preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));
            }
            return;
        }
    
        if (parameter instanceof String) {
            for (int i = 1; i <= size; i++) {
                preparedStatement.setString(i, parameter.toString());
            }
            return;
        }
    
        Map<String, Object> fieldMap = new HashMap<>();
        // Object parameters
        Field[] declaredFields = parameter.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            String name = field.getName();
            field.setAccessible(true);
            Object obj = field.get(parameter);
            field.setAccessible(false);
            fieldMap.put(name, obj);
        }
    
        for (int i = 1; i <= size; i++) {
            String parameterDefine = parameterMap.get(i);
            Object obj = fieldMap.get(parameterDefine);
    
            if (obj instanceof Short) {
                preparedStatement.setShort(i, Short.parseShort(obj.toString()));
                continue;
            }
    
            if (obj instanceof Integer) {
                preparedStatement.setInt(i, Integer.parseInt(obj.toString()));
                continue;
            }
    
            if (obj instanceof Long) {
                preparedStatement.setLong(i, Long.parseLong(obj.toString()));
                continue;
            }
    
            if (obj instanceof String) {
                preparedStatement.setString(i, obj.toString());
                continue;
            }
    
            if (obj instanceofDate) { preparedStatement.setDate(i, (java.sql.Date) obj); }}}Copy the code
    • A single parameter is easy to set: Long, Integer, String…
    • If it is a class object, you need to set it to match the Map parameter by getting the Field attribute
  • Set parameters after executing the query preparedStatement. ExecuteQuery ()

  • ResultSet2Obj (resultSet, class.forname (xNode.getresultType ()));

    private <T> List<T> resultSet2Obj(ResultSet resultSet, Class
              clazz) {
    	List<T> list = new ArrayList<>();
    	try {
    		ResultSetMetaData metaData = resultSet.getMetaData();
    		int columnCount = metaData.getColumnCount();
    		// Iterate over the row values each time
    		while (resultSet.next()) {
    			T obj = (T) clazz.newInstance();
    			for (int i = 1; i <= columnCount; i++) {
    				Object value = resultSet.getObject(i);
    				String columnName = metaData.getColumnName(i);
    				String setMethod = "set" + columnName.substring(0.1).toUpperCase() + columnName.substring(1);
    				Method method;
    				if (value instanceof Timestamp) {
    					method = clazz.getMethod(setMethod, Date.class);
    				} else{ method = clazz.getMethod(setMethod, value.getClass()); } method.invoke(obj, value); } list.add(obj); }}catch (Exception e) {
    		e.printStackTrace();
    	}
    	return list;
    }
    Copy the code
    • Mainly through reflection, we generate our class object, whose type is defined on the SQL tag
    • Timestamp is not the same type as Java

7. Supplementary description of Sql queries

SQL query has input parameters, there is no need to input parameters, there is a query, there is a query set, only need reasonable packaging, such as the following query set, the input parameter is the object type;

ApiLikeTest.test_queryUserList()

@Test
public void test_queryUserList(a) {
    String resource = "spring/mybatis-config-datasource.xml";
    Reader reader;
    try {
        reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sqlMapper.openSession();
        
		try {
            User req = new User();
            req.setAge(18);
            List<User> userList = session.selectList("org.itstack.demo.dao.IUserDao.queryUserList", req);
            System.out.println(JSON.toJSONString(userList));
        } finally{ session.close(); reader.close(); }}catch(Exception e) { e.printStackTrace(); }}Copy the code

Test results:

[{"age":18."createTime":1576944000000."id":1."name":"Water"."updateTime":1576944000000}, {"age":18."createTime":1576944000000."id":2."name":"Doug"."updateTime":1576944000000}]

Process finished with exit code 0
Copy the code

Five, to sum up

  • After learning Mybaits core source code, and then realize the core process, then it will be very clear how the process is a process, will not feel that their knowledge stack has loopholes
  • Only further learning can enable such techniques to be applied to other developments, such as adding such query packages to ES to make ES easier to use. There’s a lot more to be done
  • Knowledge is often the use of comprehensive, comprehensive use of each knowledge point, in order to be more skilled. Do not always see do not do, otherwise the flow of a full set of what impression can not flow in their own brain

Six, surprise at the end of the article

Small Fu Ge | precipitation, to share, grow, let oneself and others can have the harvest!