A trial of customizing mybaits framework

Article directory

Preface

In the first part, the author analyzes the execution principle of mybatis and makes a custom orm mapping framework based on mybatis. First, write down the functions to be implemented. This document will be updated continuously, and the corresponding code will be updated continuously.

  • Support xml and annotation to get our mapper definition sql, namely the following two configurations
<!--Use xml write sql-->
<!--<mapper resource="mybatis/DeptEmpMapper.xml"></mapper>-->

<!--annotation sql-->
<mapper class="fast.cloud.nacos.mybatis.mapper.DeptEmpMapper"></mapper>
  • Add jdk dynamic proxy to the xxxMapper.java file. This is to use jdbc to query in a unified way, instead of writing each implementation.
  • Implement database fields and entity properties, which are implemented with annotations.
  • Try to follow the design style of mybatis framework.

Realization

pom dependence

<!-- Log coordinates -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!-- analysis xml Of dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- mysql drive -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!-- dom4j Dependency package jaxen -->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>```

##  Define global Configuration ` Configuration`
//When viewing the 'mybatis' source code, it will map all' xml 'Configuration files to a' Configuration 'object. So here's another one. The code is as follows:

```java
import fast.cloud.nacos.custom.mybatis.mapper.Mapper;

import java.util.Map;

/**
 * Core configuration class
 * 1.database information 
 * 2.sql map set of
 */
public class Configuration {

    private String username; //User name
    private String password;//Password
    private String url;//address
    private String driver;//drive
    //Map set map<Unique identification, Mapper> Used to save the sql Identification and sql Sentence
    private Map<String, Mapper> mappers;
    //key is column, value is properties
    private Map<String,String> typeAliasesMap;

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public Map<String, Mapper> getMappers() {
        return mappers;
    }

    public void setMappers(Map<String, Mapper> mappers) {
        this.mappers = mappers;
    }

    public Map<String, String> getTypeAliasesMap() {
        return typeAliasesMap;
    }

    public void setTypeAliasesMap(Map<String, String> typeAliasesMap) {
        this.typeAliasesMap = typeAliasesMap;
    }
}

xml parsing class XMLConfigBuilder

There are two ways to parse the Configuration XML into Configuration. The first is to read the sql in our own xxxMapper.xml, and the other is to read the xxxMapper.java file. In this way, we can read the sql in the annotation. Different approaches lead to the same goal, both of which should conform to one standard.

  • Take the class's fully qualified name +. + method name as the unique key (mybatis added uuid in order to be more unique, which is not so complicated here)
  • Encapsulate the read sql and return type as the most value. This object is called Mapper.java

The code is as follows:

/**
 * Used to parse configuration files
 */
public class XMLConfigBuilder {
    /**
     * Parse the main configuration file and fill in the contents to the place required by DefaultSqlSession
     * Technology used:
     * dom4j+xpath
     *
     * @param session
     */

    public static void loadConfiguration(DefaultSqlSession session, InputStream
            config) {
        try {
            //Define the configuration object that encapsulates the connection information (mybatis configuration object)
            Configuration cfg = new Configuration();
            //1. Get SAXReader object
            SAXReader reader = new SAXReader();
            //2. Get the Document object according to the byte input stream
            Document document = reader.read(config);
            //3. Get root node
            Element root = document.getRootElement();
            //4. Use the method of selecting the specified node in xpath to obtain all property nodes
            List<Element> propertyElements = root.selectNodes("//property");
            //5. Traverse nodes
            for (Element propertyElement : propertyElements) {
                //Determine which part of the information the node is connecting to the database
                //Get the value of the name attribute
                String name = propertyElement.attributeValue("name");
                if ("driver".equals(name)) {
                    //Presentation driver
                    //Get the value of the property tag value property
                    String driver = propertyElement.attributeValue("value");
                    cfg.setDriver(driver);
                }
                if ("url".equals(name)) {
                    //Represents a connection string
                    //Get the value of the property tag value property
                    String url = propertyElement.attributeValue("value");
                    cfg.setUrl(url);
                }
                if ("username".equals(name)) {
                    //Represents the user name
                    //Get the value of the property tag value property
                    String username = propertyElement.attributeValue("value");
                    cfg.setUsername(username);
                }
                if ("password".equals(name)) {
                    //Express password
                    //Get the value of the property tag value property
                    String password = propertyElement.attributeValue("value");
                    cfg.setPassword(password);
                }
            }
            //Get the package label in mappers
            List<Element> packageElements = root.selectNodes("//mappers/package");
            if (!CollectionUtils.isEmpty(packageElements)) {
                Element element = packageElements.get(0);
                String packageName = element.attribute("name").getValue();
                Set<Class<?>> mapperSet = ClassUtil.getClasses(packageName);
                mapperSet.forEach(clz -> {
                    String className = clz.getName();
                    Method method = clz.getMethods()[0];
                    String methodName = method.getName();
                    String returnType = method.getReturnType().getName();
                    String key = className + "." + methodName;

//                    String id = selectElement.attributeValue("id");
//                    //Take the value of the resultType property to form the value part of the map
//                    String resultType = selectElement.attributeValue("resultType");
//                    //Extract the text content to form the value part of the map
//                    String queryString = selectElement.getText();
//                    / / create Key
//                    String key = namespace + "." + id;
//                    //Create Value
//                    Mapper mapper = new Mapper();
//                    mapper.setQueryString(queryString);
//                    mapper.setResultType(resultType);
//                    //Store key and value in mappers
//                    mappers.put(key, mapper);
                });

            } else {
                //Take out all mapper tags in mappers, and judge whether they use resource or class attribute
                List<Element> mapperElements = root.selectNodes("//mappers/mapper");
                //Ergodic set
                for (Element mapperElement : mapperElements) {
                    //Determine which attribute mapperElement uses
                    Attribute attribute = mapperElement.attribute("resource");
                    if (attribute != null) {
                        System.out.println("Used is XML");
                        //Indicates that there is a resource attribute, which is XML
                        //Get the value of the property
                        String mapperPath = attribute.getValue();// Get the value of the attribute "com/dao/IUserDao.xml"
                        //Extract the content of the mapping configuration file and encapsulate it into a map
                        Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
                        //Assign values to mappers in configuration
                        cfg.setMappers(mappers);
                    } else {
                        System.out.println("Using annotations");
                        //Indicates that there is no resource attribute. Annotation is used
                        //Get the value of the class property
                        String daoClassPath = mapperElement.attributeValue("class");
                        //Obtain the necessary information for encapsulation according to daoClassPath
                        Map<String, Mapper> mappers = loadMapperAnnotation(daoClassPath);
                        //Assign values to mappers in configuration
                        cfg.setMappers(mappers);
                    }
                }
            }

            List<Element> typeAliasesElements = root.selectNodes("//typeAliases/package");
            //2. Parse the annotation in the entity class to get the mapping data
            if (!CollectionUtils.isEmpty(typeAliasesElements)) {
                String packageName = typeAliasesElements.get(0).attribute("name").getValue();
                Set<Class<?>> entitySet = ClassUtil.getClasses(packageName);
                Map<String, String> colProMap = new HashMap<>();
                entitySet.forEach(clz -> {
                    Field[] fields = clz.getDeclaredFields();
                    Stream.of(fields).forEach(field -> {
                        String proName = field.getName();
                        ORMColumn annotation = field.getAnnotation(ORMColumn.class);
                        if (annotation != null) {
                            String colName = annotation.name();
                            colProMap.put(colName, proName);
                        }
                    });
                });
                cfg.setTypeAliasesMap(colProMap);
            }


            //Pass the configuration object to DefaultSqlSession
            session.setCfg(cfg);

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                config.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Parses the XML based on the passed in parameters and encapsulates it in the Map
     *
     * @param mapperPath Location of the mapping profile
     * @return map It contains the unique ID obtained (key is composed of the fully qualified class name and method name of dao)
     * And the necessary information for execution (value is a Mapper object, which stores the executed SQL statements and
     * Fully qualified class name of entity class to be encapsulated)
     */
    private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) throws IOException {
        InputStream in = null;
        try {
            //Define return value object
            Map<String, Mapper> mappers = new HashMap<String, Mapper>();
            //1. Get byte input stream according to path
            in = Resources.getResourceAsStream(mapperPath);
            //2. Get the Document object according to the byte input stream
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            //3. Get root node
            Element root = document.getRootElement();
            //4. Get the value of the namespace attribute of the root node
            String namespace = root.attributeValue("namespace");//It is the key part of the map
            //5. Get all select nodes
            List<Element> selectElements = root.selectNodes("//select");
            //6. Traverse the select node set
            for (Element selectElement : selectElements) {
                //Take the value of id attribute to form the key part of map
                String id = selectElement.attributeValue("id");
                //Take the value of the resultType property to form the value part of the map
                String resultType = selectElement.attributeValue("resultType");
                //Extract the text content to form the value part of the map
                String queryString = selectElement.getText();
                //Create Key
                String key = namespace + "." + id;
                //Create Value
                Mapper mapper = new Mapper();
                mapper.setQueryString(queryString);
                mapper.setResultType(resultType);
                //Store key and value in mappers
                mappers.put(key, mapper);
            }
            return mappers;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            in.close();
        }
    }

    /**
     * According to the parameters passed in, we get all the methods annotated by the select annotation in dao.
     * According to the method name and class name, as well as the value of the annotated value attribute on the method, the necessary information of Mapper is formed
     *
     * @param daoClassPath
     * @return
     */
    private static Map<String, Mapper> loadMapperAnnotation(String daoClassPath) throws Exception {
        //Define return value object
        Map<String, Mapper> mappers = new HashMap<String, Mapper>();
        //1. Get bytecode object of dao interface
        Class daoClass = Class.forName(daoClassPath);
        //2. Get the method array in dao interface
        Method[] methods = daoClass.getMethods();
        //3. Traverse Method array
        for (Method method : methods) {
            //Take out each method and judge whether there is a select annotation
            boolean isAnnotated = method.isAnnotationPresent(Select.class);
            if (isAnnotated) {
                //Create Mapper object
                Mapper mapper = new Mapper();
                //Get the value attribute value of the annotation
                Select selectAnno = method.getAnnotation(Select.class);
                String queryString = selectAnno.value();
                mapper.setQueryString(queryString);
                //Gets the return value of the current method, which also requires generic information
                Type type = method.getGenericReturnType();//List<User>
                //Judge whether type is a parameterized type
                if (type instanceof ParameterizedType) {
                    //Strong turn
                    ParameterizedType ptype = (ParameterizedType) type;
                    //Get the actual type parameters in the parameterized type
                    Type[] types = ptype.getActualTypeArguments();
                    //Take out the first one
                    Class domainClass = (Class) types[0];
                    //Get the class name of domainClass
                    String resultType = domainClass.getName();
                    //Assign value to Mapper
                    mapper.setResultType(resultType);
                }
                //Assembly key information
                //Get the name of the method
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String key = className + "." + methodName;
                //Assign map
                mappers.put(key, mapper);
            }
        }
        return mappers;
    }
}

SqlSession series of database transaction

Abstract factory SqlSessionFactory

/**
 * SqlSessionFactory Interface
 */
public interface SqlSessionFactory {
    /**
     * Create a new SqlSession object
     */
    SqlSession openSession();
}

Default implementation factory DefaultSqlSessionFactory

/**
 * SqlSessionFactory Default implementation of
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private InputStream config = null;

    public void setConfig(InputStream config) {
        this.config = config;
    }

    @Override
    public SqlSession openSession() {
        DefaultSqlSession session = new DefaultSqlSession();
        //Calling tool class to parse xml file
        XMLConfigBuilder.loadConfiguration(session, config);
        return session;
    }
}

Default implementation of DefaultSqlSession

public class DefaultSqlSession implements SqlSession {
    //Core configuration object
    private Configuration cfg;

    public void setCfg(Configuration cfg) {
        this.cfg = cfg;
    }

    //Connection object
    private Connection conn;

    //Call DataSourceUtils tool class to get the connection
    public Connection getConn() {

        try {
            conn = DataSourceUtil.getConnection(cfg);
            return conn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Dynamic agent:
     * Class involved: Proxy
     * Method used: newProxyInstance
     * Method parameters:
     * ClassLoader: Use the same classloader as the proxied object, usually fixed
     * Class[]: Proxy objects and proxied objects require the same behavior. (same method)
     * InvocationHandler: How to proxy. We need our own code for enhancements
     */
    @Override
    public <T> T getMapper(Class<T> daoClass) {
        conn = getConn();
        System.out.println(conn);
        T daoProxy = (T) Proxy.newProxyInstance(daoClass.getClassLoader(), new
                Class[]{daoClass}, new MapperProxyFactory(cfg.getMappers(), conn, cfg.getTypeAliasesMap()));
        return daoProxy;
    }

    //Release resources
    @Override
    public void close() {
        try {
            System.out.println(conn);
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //Query all methods
    public <E> List<E> selectList(String statement) {
        Mapper mapper = cfg.getMappers().get(statement);
        Map<String, String> typeAliasesMap = cfg.getTypeAliasesMap();
        return new Executor().selectList(mapper, typeAliasesMap, conn);
    }
}

Builder mode SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {
    /**
     * Create SqlSessionFactory according to the incoming flow
     *
     * @param in It is the configuration of SqlMapConfig.xml and IUserDao.xml contained in it
     * @return
     */
    public SqlSessionFactory build(InputStream in) {
        DefaultSqlSessionFactory factory = new DefaultSqlSessionFactory();
        //Assign value to config in factory
        factory.setConfig(in);
        return factory;
    }
}

Dynamic agent MapperProxyFactory

This is the play. Take the name of the currently executed method and the class of the currently executed method, find the corresponding mapper to execute sql uniformly, encapsulate the results, and return.

public class MapperProxyFactory implements InvocationHandler {
    private Map<String, Mapper> mappers;
    private Connection conn;
    private Map<String, String> typeAliasesMap;

    public MapperProxyFactory(Map<String, Mapper> mappers, Connection conn, Map<String, String> typeAliasesMap) {
        this.mappers = mappers;
        this.conn = conn;
        this.typeAliasesMap = typeAliasesMap;
    }

    /**
     * Enhance the currently executing method
     * Fetch the currently executed method name
     * Get the class of the currently executed method
     * Splicing into key
     * Get Value from Map (Mapper)
     * Select list method x using tool class Executor
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1. Take out the method name
        String methodName = method.getName();
        //2. Get the class name of the method
        String className = method.getDeclaringClass().getName();
        //3. Splicing into Key
        String key = className + "." + methodName;
        //4. Use key to remove mapper
        Mapper mapper = mappers.get(key);
        if (mapper == null) {
            throw new IllegalArgumentException("Error in the parameter passed in, unable to get necessary conditions for execution ");
        }
        //5. Create Executor object
        Executor executor = new Executor();
        return executor.selectList(mapper, typeAliasesMap, conn);
    }
}

Executor

For the encapsulation of jdbc, here is a typeAliasesMap, which maps the fields and attributes.

/**
 * Responsible for executing SQL statements and encapsulating the result set
 */
public class Executor {
    public <E> List<E> selectList(Mapper mapper, Map<String, String> typeAliasesMap, Connection conn) {
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            //1. Take out the data in mapper
            String queryString = mapper.getQueryString();//select * from user
            String resultType = mapper.getResultType();//com.domain.User
            Class domainClass = Class.forName(resultType);//User.class
            //2. Get PreparedStatement object
            pstm = conn.prepareStatement(queryString);
            //3. Execute SQL statement to get result set
            rs = pstm.executeQuery();
            //4. Encapsulation result set
            List<E> list = new ArrayList<E>();//Define return value
            while (rs.next()) {
                //Instantiate the entity class object to encapsulate
                E obj = (E) domainClass.newInstance();//User object
                //Get meta information of result set: ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //Take out the total number of columns
                int columnCount = rsmd.getColumnCount();
                //Total number of traversal columns
                for (int i = 1; i <= columnCount; i++) {
                    //Gets the name of each column. The sequence number of the column name starts from 1
                    String columnName = rsmd.getColumnName(i);
                    //Get the value of each column according to the column name
                    Object columnValue = rs.getObject(columnName);
                    String proName = typeAliasesMap.getOrDefault(columnName, columnName);
                    //Assign value to obj: use Java introspection mechanism (implement property encapsulation with PropertyDescriptor)
                    PropertyDescriptor pd = new
                            PropertyDescriptor(proName, domainClass);//Requirement: the attribute of entity class and the column name of database table should be the same
                    //Get its write method
                    Method writeMethod = pd.getWriteMethod();//setUsername(String username);
                    //Assign the value of the obtained column to the object
                    writeMethod.invoke(obj, columnValue);
                }
                //Adding a valued object to a collection
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            release(pstm, rs);
        }
    }

    private void release(PreparedStatement pstm, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (pstm != null) {
            try {
                pstm.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

test

Before testing, we need to rely on jar, configure xml, define entity, mapper, and continue the code in the previous section.

Test xml mode

  • xml configuration
<?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>
    <typeAliases>  <!--Configure alias of entity class-->
        <package name="fast.cloud.nacos.mybatis.entity"/>
    </typeAliases>

    <environments default="MySQLDevelopment">
        <environment id="MySQLDevelopment">
            <transactionManager type="JDBC"></transactionManager> <!--affair-->
            <dataSource type="POOLED">  <!--Database connection-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--Use xml write sql-->
        <mapper resource="mybatis/DeptEmpMapper.xml"></mapper>
    </mappers>

</configuration>
  • test method
@Test
    public void testMybatis() throws IOException {
        //1. Load core configuration file
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        //2. Parse core configuration file and create SqlSessionFactory object
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);

        //3. Create core objects
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //4. Get Mapper proxy object
        DeptEmpMapper deptEmpMapper = sqlSession.getMapper(DeptEmpMapper.class);

        //5. Call the user-defined method to realize the query function
        List<DeptEmp> list = deptEmpMapper.getEmpTotalByDept();
        for (DeptEmp de : list) {
            System.out.println(de);
        }

        //6. Close sqlSession
        sqlSession.close();

    }

Operation result

Test annotation method

  • Annotated sql
//Mapper interface does not need to write implementation class
public interface DeptEmpMapper {

    @Select("SELECT deptno, COUNT(*) AS total  FROM  emp  GROUP BY deptno")
    List<DeptEmp> getEmpTotalByDept();

}
  • xml configuration
<?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>
    <typeAliases>  <!--Configure alias of entity class-->
        <package name="fast.cloud.nacos.mybatis.entity"/>
    </typeAliases>

    <environments default="MySQLDevelopment">
        <environment id="MySQLDevelopment">
            <transactionManager type="JDBC"></transactionManager> <!--affair-->
            <dataSource type="POOLED">  <!--Database connection-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--Use xml write sql-->
        <!--<mapper resource="mybatis/DeptEmpMapper.xml"></mapper>-->

        <!--annotation sql-->
        <mapper class="fast.cloud.nacos.mybatis.mapper.DeptEmpMapper"></mapper>
        <!--<package name="fast.cloud.nacos.mybatis.mapper"/>-->
    </mappers>

</configuration>
  • The test method remains unchanged, and the running results are as follows:

80 original articles published, 78 praised, 10000 visitors+
Private letter follow

Tags: xml Mybatis Attribute SQL

Posted on Sat, 18 Jan 2020 07:20:39 -0500 by konrados