The way programmers are technologically transformed Handwritten MyBatis framework

The core functionality of the MyBatis framework is not difficult, it's all about dynamic proxy and jdbc operations, it's hard to write code that is scalable, highly cohesive, and less coupled.

The Mybatis function completed in this paper is relatively simple, and there are many areas of code that need to be improved. You can combine Mybatis source code to improve.

1. Introduction to the Mybatis framework process

Before writing your own Mybatis framework, let's take a look at Mybatis, which uses a large number of design patterns in its source code. Read the source code and observe the application of design patterns in it to gain a deeper understanding of the source code (ref:Mybatis Source Interpretation-Design Mode Summary).

We analyze and summarize the above figure:

1. There are two types of configuration files for mybatis

  • mybatisconfig.xml, configuration file name is not fixed, configuration of global parameters is configured, global can only have one configuration file.

  • Mapper.xml configures multiple statementations, or sql s, and there can be multiple Mappe.xml configurations throughout the mybatis framework.

2. Get SqlSessionFactory from mybatis configuration file

3. SqlSession is obtained from SqlSessionFactory, and data can be manipulated with SqlSession.

4. SqlSession is implemented through the underlying Executor, which has two types of implementations:

  • Basic implementation

  • Implementation with Caching

5. MappedStatement is an object generated by defining a statement in Mapper.xml.

6. Parameter input executes and outputs the result set, without manual judgment of parameter type and parameter subscript position, and automatically maps the result set to a Java object

  • HashMap, data type in KV format

  • Basic data types for Java

  • POJO, java object

2. Comb your Mybatis design ideas

Based on the Mybatis process above, I simplified the following steps:

1. Read the xml file and establish a connection

As you can see from the diagram, MyConfiguration is responsible for interacting with people.After reading the xml, encapsulate the operations of attributes and connecting to the database in the MyConfiguration object for subsequent components to invoke.This article uses dom4j to read XML files, which have excellent performance and are very convenient to use.2. Create SqlSession to bridge Configuration to Executor

We often see Session when using frameworks. What exactly is Session?A Session only has one corresponding database connection.Similar to a previous request Request, it can call exec(SQL) directly to execute the SQL statement.

From the arrows in the flowchart, you can see that the member variables of MySqlSession must have MyExecutor and MyConfiguration to be pooled together, and the arrows are like an association.Our own MySqlSession will have a getMapper method, and then use the dynamic proxy to generate the object, you can do the database operation.

3. Create Executor to encapsulate JDBC operational database

Executor is an executor responsible for the generation of SQL statements and maintenance of query caches (cache is not yet complete), that is, the code for jdbc will be completed here. However, this article only implements single tables, and interested students can try to complete multiple tables.

4. Create MapperProxy and use dynamic proxy to generate Mapper objects

We just want to generate an object for the specified interface so that a single sql can run when it is executed, and the interface cannot call the method directly, so we use a dynamic proxy to generate the object, call the query at execution time or back to MySqlSession, and MyExecutor will make the JDBC query.This is designed for a single responsibility and is more scalable.

3. Implement your own Mybatis

Project files and directories:

First, create a new maven project that imports the following dependencies into 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.liugh</groupId>
 <artifactId>liugh-mybatis</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>
 
 <properties>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <maven.compiler.source>1.8</maven.compiler.source>
   <maven.compiler.target>1.8</maven.compiler.target>
   <java.version>1.8</java.version>
 </properties>
 
 <dependencies>
      <!-- read xml file -->
   <dependency>
     <groupId>dom4j</groupId>
     <artifactId>dom4j</artifactId>
     <version>1.6.1</version>
   </dependency>
   
   <!-- MySQL -->
   <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>5.1.29</version>
   </dependency>
   </dependencies>
</project>

Create our database xml configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<database>
 <property name="driverClassName">com.mysql.jdbc.Driver</property>
 <property name="url">jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8</property>
 <property name="username">root</property>
 <property name="password">123456</property>
</database>

Then create a test library in the database and execute the following SQL statements:

CREATE TABLE `user` (
 `id` varchar(64) NOT NULL,
 `password` varchar(255) DEFAULT NULL,
 `username` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `test`.`user` (`id`, `password`, `username`) VALUES ('1', '123456', 'liugh');

Create User entity classes, UserMapper interfaces, and corresponding xml files:

package com.liugh.bean;

public class User {
   private String id;
   private String username;
   private String password;
   //Omit get set toString method...
}
package com.liugh.mapper;

import com.liugh.bean.User;

public interface UserMapper {
 
 public User getUserById(String id);  
}
<?xml version="1.0" encoding="UTF-8"?>
<mapper nameSpace="com.liugh.mapper.UserMapper">
   <select id="getUserById" resultType ="com.liugh.bean.User">
       select * from user where id = ?
   </select>
</mapper>

The basic operation configuration is complete, so let's start implementing MyConfiguration:

package com.liugh.sqlSession;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.liugh.config.Function;
import com.liugh.config.MapperBean;


/**
* Read and parse configuration information and return the processed Environment
*/
public class MyConfiguration {
 private static ClassLoader loader = ClassLoader.getSystemClassLoader();

 /**
  * Read xml information and process it
  */
 public  Connection build(String resource){
     try {
         InputStream stream = loader.getResourceAsStream(resource);
     SAXReader reader = new SAXReader();
     Document document = reader.read(stream);
     Element root = document.getRootElement();
     return evalDataSource(root);
   } catch (Exception e) {
     throw new RuntimeException("error occured while evaling xml " + resource);
   }
 }
 
 private  Connection evalDataSource(Element node) throws ClassNotFoundException {
       if (!node.getName().equals("database")) {
         throw new RuntimeException("root should be <database>");
       }
   String driverClassName = null;
   String url = null;
   String username = null;
   String password = null;
   //Get Property Node
   for (Object item : node.elements("property")) {
     Element i = (Element) item;      
     String value = getValue(i);
     String name = i.attributeValue("name");
     if (name == null || value == null) {
       throw new RuntimeException("[database]: <property> should contain name and value");
     }
     //assignment
     switch (name) {
       case "url" : url = value; break;
       case "username" : username = value; break;
       case "password" : password = value; break;
       case "driverClassName" : driverClassName = value; break; 
       default : throw new RuntimeException("[database]: <property> unknown name"); 
     }
   }
   
    Class.forName(driverClassName); 
    Connection connection = null;
   try {
     //Establish database links
     connection = DriverManager.getConnection(url, username, password);
   } catch (SQLException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
   }
   return connection;
 }
 
 //Gets the value of the property property, and if there is a value, reads. If no value is set, reads
 private  String getValue(Element node) {
   return node.hasContent() ? node.getText() : node.attributeValue("value");
 }
 
 
 
   @SuppressWarnings("rawtypes")
   public  MapperBean readMapper(String path){
       MapperBean mapper = new MapperBean();
       try{
         InputStream stream = loader.getResourceAsStream(path);
      SAXReader reader = new SAXReader();
      Document document = reader.read(stream);
      Element root = document.getRootElement();
           mapper.setInterfaceName(root.attributeValue("nameSpace").trim()); //Save the nameSpace value of the mapper node as the interface name
           List<Function> list = new ArrayList<Function>(); //List to store methods
           for(Iterator rootIter = root.elementIterator();rootIter.hasNext();) {//Traverse all child nodes under the root node
               Function fun = new Function();    //Information used to store a method
               Element e = (Element) rootIter.next(); 
               String sqltype = e.getName().trim();
               String funcName = e.attributeValue("id").trim();
               String sql = e.getText().trim();
               String resultType = e.attributeValue("resultType").trim();
               fun.setSqltype(sqltype);
               fun.setFuncName(funcName);
               Object newInstance=null;
       try {
         newInstance = Class.forName(resultType).newInstance();
       } catch (InstantiationException e1) {
         e1.printStackTrace();
       } catch (IllegalAccessException e1) {
         e1.printStackTrace();
       } catch (ClassNotFoundException e1) {
         e1.printStackTrace();
       }
               fun.setResultType(newInstance);
               fun.setSql(sql);
               list.add(fun);
           }
           mapper.setList(list);
           
       } catch (DocumentException e) {
           e.printStackTrace();
       }
       return mapper;
   }
}

After reading the xml configuration with object-oriented design:

package com.liugh.config;

import java.util.List;
public class MapperBean {

   private String interfaceName; //Interface name
   private List<Function> list; //All methods under interface
   //Omit get set method...

}

Function objects include sql type, method name, sql statement, return type, and parameter type.

package com.liugh.config;

public class Function {
   private String sqltype;  
   private String funcName;  
   private String sql;       
   private Object resultType;  
   private String parameterType; 
 //Omit get set method
}

Next, to implement our MySqlSession, the first member variables must be Excutor and MyConfiguration, and the essence of the code is in the getMapper method.

package com.liugh.sqlSession;

import java.lang.reflect.Proxy;

public class MySqlsession {
 
 private Excutor excutor= new MyExcutor();  
 
 private MyConfiguration myConfiguration = new MyConfiguration();
 
   public <T> T selectOne(String statement,Object parameter){  
       return excutor.query(statement, parameter);  
   }  
       
   @SuppressWarnings("unchecked")
   public <T> T getMapper(Class<T> clas){ 
     //Dynamic Proxy Call
       return (T)Proxy.newProxyInstance(clas.getClassLoader(),new Class[]{clas},
           new MyMapperProxy(myConfiguration,this));  
   }  

}

Next, create Excutor and the implementation class:

package com.liugh.sqlSession;

public interface Excutor {
 public <T> T query(String statement,Object parameter);  
}

JDBC operations are encapsulated in MyExcutor:

package com.liugh.sqlSession;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.liugh.bean.User;


public class MyExcutor implements Excutor{
 
 private MyConfiguration xmlConfiguration = new MyConfiguration();
 
  @Override  
     public <T> T query(String sql, Object parameter) {  
         Connection connection=getConnection();  
         ResultSet set =null;
         PreparedStatement pre =null;
         try {  
             pre = connection.prepareStatement(sql); 
             //Setting parameters
             pre.setString(1, parameter.toString());
             set = pre.executeQuery();  
             User u=new User();  
             //Traversing result set
             while(set.next()){  
                 u.setId(set.getString(1));
                 u.setUsername(set.getString(2)); 
                 u.setPassword(set.getString(3));
             }  
             return (T) u;  
         } catch (SQLException e) {  
             e.printStackTrace();  
         } finally{
                try{  
                    if(set!=null){  
                      set.close();  
                    }if(pre!=null){  
                      pre.close();  
                    }if(connection!=null){  
                      connection.close();  
                    }  
                }catch(Exception e2){  
                    e2.printStackTrace();  
                }  
            }   
         return null;  
     }  
   
     private Connection getConnection() {  
         try {  
             Connection connection =xmlConfiguration.build("config.xml");
             return connection;  
         } catch (Exception e) {  
             e.printStackTrace();  
         }  
         return null;  
     }  
}

The MyMapperProxy proxy class completes the xml and real method correspondence and executes the query:

package com.liugh.sqlSession;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import com.liugh.config.Function;
import com.liugh.config.MapperBean;

public class MyMapperProxy implements InvocationHandler{
 
 private  MySqlsession mySqlsession;  
 
 private MyConfiguration myConfiguration;
    
   public MyMapperProxy(MyConfiguration myConfiguration,MySqlsession mySqlsession) {  
       this.myConfiguration=myConfiguration;  
       this.mySqlsession=mySqlsession;  
   }  

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   MapperBean readMapper = myConfiguration.readMapper("UserMapper.xml");
   //Is it the interface for the xml file
   if(!method.getDeclaringClass().getName().equals(readMapper.getInterfaceName())){
     return null;  
   }
   List<Function> list = readMapper.getList();
   if(null != list || 0 != list.size()){
     for (Function function : list) {
     //Is the id the same as the interface method name
      if(method.getName().equals(function.getFuncName())){  
               return mySqlsession.selectOne(function.getSql(), String.valueOf(args[0]));  
           }  
     }
   }
      return null;  
 }
}

Now that you've finished your Mybatis framework, let's test it:

package com.liugh;

import com.liugh.bean.User;
import com.liugh.mapper.UserMapper;
import com.liugh.sqlSession.MySqlsession;

public class TestMybatis {
 
   public static void main(String[] args) {  
       MySqlsession sqlsession=new MySqlsession();  
       UserMapper mapper = sqlsession.getMapper(UserMapper.class);  
       User user = mapper.getUserById("1");  
       System.out.println(user);
   } 
}

Execution results:

Query a non-existent user to try:


Tags: Programming Java xml SQL Mybatis

Posted on Tue, 03 Dec 2019 06:00:18 -0500 by atyndall