当前位置:网站首页>Write a persistence framework yourself

Write a persistence framework yourself

2020-11-10 14:03:28 IsDxh

1. JDBC Problem analysis

Let's take a look JDBC Code for :

public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            //1.  Load database driver 
            Class.forName("com.mysql.jdbc.Drive");
            //2.  Get the database link through the driver management class 
            connection = DriverManager.getConnection("jdbc:mysql://hocalhost:3306/mybatis?characterEncoding=utf-8",
                    "root","root");
            //3.  Definition SQL sentence  ? Represents a placeholder 
            String sql = "SELECT * FROM user WHERE username = ?";
            //4.  Get preprocessing object Statement
            preparedStatement = connection.prepareStatement(sql);
            //5.  Set parameters , The first parameter is zero SQL The sequence number of the parameter in the statement ( from 1 Start ), The second parameter is the set parameter value 
            preparedStatement.setString(1,"tom");
            //6.  Send... To the database SQL Execute the query , Query out result set 
            resultSet = preparedStatement.executeQuery();
            //7.  Traverse the query result set 
            while (resultSet.next()){
                int id = resultSet.getInt("id");
                String userName = resultSet.getString("username");
                // encapsulation User
                user.setId(id);
                user.setUserName(userName);
            }
            System.out.println(user);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

You can see , Use it directly JDBC There are some problems with development , Let's analyze :

Problem analysis :

  1. Database configuration information exists Hard encoding problem
  2. Create... Frequently 、 Release database links
//1.  Load database driver 
Class.forName("com.mysql.jdbc.Drive");
//2.  Get the database link through the driver management class 
connection = DriverManager.getConnection("jdbc:mysql://hocalhost:3306/mybatis?characterEncoding=utf-8","root","root");
  1. sql sentence 、 Set parameters 、 There are hard coding problems in obtaining result sets
//3.  Definition SQL sentence  ? Represents a placeholder 
String sql = "SELECT * FROM user WHERE username = ?";
//4.  Get preprocessing object Statement
preparedStatement = connection.prepareStatement(sql);
//5.  Set parameters , The first parameter is zero SQL The sequence number of the parameter in the statement ( from 1 Start ), The second parameter is the set parameter value 
 preparedStatement.setString(1,"tom");
 //6.  Send... To the database SQL Execute the query , Query out result set 
 resultSet = preparedStatement.executeQuery();

      int id = resultSet.getInt("id");
      String userName = resultSet.getString("username");
  1. Manually encapsulate the return result set More complicated
//7.  Traverse the query result set 
while (resultSet.next()){
    int id = resultSet.getInt("id");
    String userName = resultSet.getString("username");
    // encapsulation User
    user.setId(id);
    user.setUserName(userName);
 }
 System.out.println(user);

Solutions :

  1. Write in the configuration file
  2. Connection pool (c3p0、dbcp、 Druid ...)
  3. The configuration file ( and 1 Put it together ? No, Don't put the frequent changes and the infrequent ones together )
  4. Reflection 、 introspection

Now according to this solution , Write a persistence framework yourself , Analyze what the framework needs to do before writing it


2. User defined framework analysis

End of use ( project ):

  1. Introducing a custom persistence layer framework jar package

  2. Provides two parts of configuration information :

  • Database configuration information
  • SQL Configuration information (SQL sentence )
  1. Use configuration files to provide this information :
    1. sqlMapConfig.xml : Store the configuration information of the database
    2. mapper.xml : Deposit SQL Configuration information

Custom persistence layer framework ( engineering ):

The essence of persistence layer framework is to JDBC The code is encapsulated

  1. Load profile : Load the configuration file into byte input stream according to the path of the configuration file , In memory

    1. establish Resources class Method :getResourceAsStream(String path)

    Q: getResourceAsStearm Method needs to be executed twice and loaded separately sqlMapConfig Sum mapper Do you ?

    A: Yes, but not necessarily , We can do it in sqlMapConfig.xml writes mapper.xml The full path of

  2. Create two javaBean:( Container object ): What is stored is the content parsed out of the configuration file

    1. Configuration: Core configuration class : Deposit sqlMapConfig.xml What's coming out of it
    2. MappedStatement: Mapping configuration class : Deposit mapper.xml What's coming out of it
  3. Parse configuration file : Use dom4j

    1. Create a class :SqlSessionFactoryBuilder Method :build(InputStream in) This stream is just in memory
    2. Use dom4j Parse configuration file , Encapsulate the parsed content into the container object
    3. establish SqlSessionFactory object ; production sqlSession: Conversation object ( Factory mode To reduce the coupling , According to different requirements, produce objects in different states )
  4. establish sqlSessionFactory Interface and implementation class DefaultSqlSessionFactory

    1. openSession(); production sqlSession
  5. establish SqlSession Interface and implementation class DefaultSession

    1. Define the database CRUD operation , for example :
      1. selectList()
      2. selectOne()
      3. update()
      4. delete()
      5. ...
  6. establish Executor Interface and implementation class SimpleExecutor Implementation class

    1. query(Configuration con,MappedStatement ms,Object ...param); perform JDBC Code ,Object ...param Specific parameter values , Variable parameter ;

3. Create tables and write test classes

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'lucy');
INSERT INTO `user` VALUES (2, 'tom');
INSERT INTO `user` VALUES (3, 'jack');

SET FOREIGN_KEY_CHECKS = 1;

1. Create a Maven project —— Ipersistence_test

2. stay resource Created in sqlMapConfig.xml and UserMapper.xml

UserMapper.xml

<mapper namespace="user">
    <!--sql Unique identification of :namespace.id To form a  :statementId-->
    <select id="selectList" resultType="com.dxh.pojo.User">
        select * from user
    </select>
    <select id="selectOne" resultType="com.dxh.pojo.User" paramterType="com.dxh.pojo.User">
        select * from user where id = #{id} and username = #{userName}
    </select>
</mapper>

Q:
Why would there be namespace and id ?
A:
When one *Mapper.xml There are many sql when , It's impossible to distinguish which one it is, so add id
If there is UserMapper.xml and ProductMapper.xml, Suppose their query is id All for ”selectList“, Then it will be impossible to distinguish between specific queries user Or search product Of .
So add namespace
namespace.id form sql Unique identification of , Also known as statementId

sqlMapConfig.xml

<configuration>
    <!-- Database configuration information  -->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>
    <!-- Deposit mapper.xml The full path -->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>

4. Start writing persistence framework

Custom persistence layer framework ( engineering ):

The essence is right JDBC The code is encapsulated

  1. Load profile : Load the configuration file into byte input stream according to the path of the configuration file , In memory

    1. establish Resources class Method :getResourceAsStream(String path)
  2. Create two javaBean:( Container object ): What is stored is the content parsed out of the configuration file

    1. Configuration: Core configuration class : Deposit sqlMapConfig.xml What's coming out of it
    2. MappedStatement: Mapping configuration class : Deposit mapper.xml What's coming out of it
  3. Parse configuration file : Use dom4j

    1. Create a class :SqlSessionFactoryBuilder Method :build(InputStream in) This stream is just in memory
    2. Use dom4j Parse configuration file , Encapsulate the parsed content into the container object
    3. establish SqlSessionFactory object ; production sqlSession: Conversation object ( Factory mode To reduce the coupling , According to different requirements, produce objects in different states )
  4. establish sqlSessionFactory Interface and implementation class DefaultSqlSessionFactory

    1. openSession(); production sqlSession
  5. establish SqlSession Interface and implementation class DefaultSession

    1. Define the database CRUD operation
  6. establish Executor Interface and implementation class SimpleExecutor Implementation class

    1. query(Configuration con,MappedStatement ms,Object ...param); perform JDBC Code ,Object ...param Specific parameter values , Variable parameter ;

We have already analyzed the persistence layer framework , Need to do 6 Part of it is made up of , as follows :

1. Load profile

We want to put the client's configuration file into byte input stream and store it in memory :

newly build Resource class , Provide a static InputStream getResourceAsStream(String path) Method , And back to inputstream

package com.dxh.io;
import java.io.InputStream;

public class Resource {
    // According to the path of the configuration file , Load the configuration file into a byte input stream , Stored in memory 
    public static InputStream getResourceAsStream(String path){
        InputStream resourceAsStream = Resource.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

2. establish JavaBean( Container object )

We talked about , To encapsulate the parsed configuration file into an object .

  • MappedStatement ( Deposit SQL Information )
  • Configuration ( Store database configuration information )
// MappedStatement, We store SQL Information about  
package com.dxh.pojo;
public class MappedStatement {
    // id identification 
    private String id;
    // return type 
    private String resultType;
    // Parameter value type 
    private String paramterType;
    //sql sentence 
    private String sql;
    
 	getset Omit ...
}

Here we put the packaged MappedStatement Objects are also placed in Configuration in , At the same time, we don't store databases url、username... 了 , Store directly DataSource

package com.dxh.pojo;

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

public class Configuration {
    private DataSource dataSource;
    /**
     * key statementId  ( Namely namespace.id)
     * value: Packaged MappedStatement object 
     */
    Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
	
    getset Omit ...
}

3. analysis xml file

In this step, we will analyze two xml file sqlMapConfig.xmlmapper.xml

We first encapsulate the parsing process : newly build XMLConfigBuild.java

package com.dxh.config;

import com.dxh.io.Resource;
import com.dxh.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

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

public class XMLConfigBuild {
    private Configuration configuration;

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

    /**
     *  This method is to parse the configuration file (dom4j), encapsulation Configuration
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(inputStream);
        //<configuration>
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }
		//C3P0 Connection pool 
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(comboPooledDataSource);

        //mapper.xml analysis  : Get the path -- Byte input stream ---dom4j analysis 
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            // Get the path 
            String mapperPath = element.attributeValue("resource");
            // Byte input stream 
            InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath);
            //dom4j analysis 
            //   Because after parsing MappedStatement Put it on Configuration in , So we introduce a configuration go in 
            XMLMapperBuild xmlMapperBuild = new XMLMapperBuild(configuration);
            xmlMapperBuild.parse(resourceAsStream);
        }
        return configuration;
    }
}

3.1 analysis Mapper.xml file

package com.dxh.config;

import com.dxh.pojo.Configuration;
import com.dxh.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;

public class XMLMapperBuild {
    private Configuration configuration;

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

    public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");

        List<Element> list = rootElement.selectNodes("//select");
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sqlText);
            String key = namespace+"."+id;
            configuration.getMappedStatementMap().put(key,mappedStatement);
        }
    }
}

It's easy to understand , Because we're going to return after parsing Configuration object , So we need to declare a Configuration And initialization .

We pass in the stream after loading the file , adopt dom4j analysis , And pass ComboPooledDataSource(C3P0 Connection pool ) Generate what we need DataSource, And deposit in Configuration In the object .

Mapper.xml The analytical method is the same as .

3.2 establish SqlSessionFactoryBuilder class :
With the above two analytical methods , Let's create a class , To call this method , At the same time, this class returns SqlSessionFacetory

SqlSessionFacetory: To produce sqlSession:sqlSession It's the conversation object ( Factory mode To reduce the coupling , According to different requirements, produce objects in different states )

package com.dxh.sqlSession;

import com.dxh.config.XMLConfigBuild;
import com.dxh.pojo.Configuration;
import org.dom4j.DocumentException;

import java.beans.PropertyVetoException;
import java.io.InputStream;

public class SqlSessionFacetoryBuild {
    public SqlSessionFacetory build(InputStream in) throws DocumentException, PropertyVetoException {
        //1.  Use dom4j Parse configuration file , Encapsulate the parsed content to configuration in 
        XMLConfigBuild xmlConfigBuild = new XMLConfigBuild();
        Configuration configuration = xmlConfigBuild.parseConfig(in);

        //2.  establish sqlSessionFactory object   Factory : production sqlSession: Conversation object , The addition, deletion, modification and query of database interaction are encapsulated in sqlSession in 
        DefaultSqlSessionFactory sqlSessionFacetory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFacetory;
    }

}

4. establish SqlSessionFacetory Interface and implementation classes

Based on the open close principle, we create SqlSessionFacetory Interface and implementation classes DefaultSqlSessionFactory

In the interface, we define openSession() Method , Used in the production of SqlSession

package com.dxh.sqlSession;

public interface SqlSessionFacetory {
    public SqlSession openSession();
}
package com.dxh.sqlSession;
import com.dxh.pojo.Configuration;

public class DefaultSqlSessionFactory implements SqlSessionFacetory{
    private Configuration configuration;

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

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}

Again, we are DefaultSqlSessionFactory In the middle of Configuration,Configuration It's going to take us all the way down

5. establish SqlSession Interface and its implementation class

In the interface , I define two methods :

Because we don't know the type and number of parameters , So we use generics , meanwhile , Pass in statementId(namespace、. 、id form )

package com.dxh.sqlSession;
import java.util.List;

public interface SqlSession {
    // Query multiple 
    public <E> List<E> selectList(String statementId,Object... params) throws Exception;
    // Query individual according to the condition 
    public <T> T selectOne(String statementId,Object... params) throws Exception;
}

package com.dxh.sqlSession;
import com.dxh.pojo.Configuration;
import java.util.List;

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 {
        // Will be finished to simpleExecutor Inside query Method call 
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        List<Object> list = simpleExecutor.query(configuration, configuration.getMappedStatementMap().get(statementId), params);
        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if (objects.size()==1){
            return (T) objects.get(0);
        }else{
            throw new RuntimeException(" The query result is empty or too many results are returned ");
        }
    }
}

here selectOne Methods and selectList Methods have the same parameter structure , So we can pass selectList.get(0) To get a return result . and selectList The middle is the point , We need to create an object SimpleExecutor And execute in it SQL

6. establish Executor Interface and implementation class SimpleExecutor Implementation class

package com.dxh.sqlSession;

import com.dxh.pojo.Configuration;
import com.dxh.pojo.MappedStatement;

import java.sql.SQLException;
import java.util.List;

public interface Executor {
    /**
     *
     * @param configuration  Database configuration information 
     * @param mappedStatement SQL Configuration information 
     * @param params  Variable parameter 
     * @return
     */
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement,Object... params) throws SQLException, Exception;
}

package com.dxh.sqlSession;

import com.dxh.config.BoundSql;
import com.dxh.pojo.Configuration;
import com.dxh.pojo.MappedStatement;
import com.dxh.utils.GenericTokenParser;
import com.dxh.utils.ParameterMapping;
import com.dxh.utils.ParameterMappingTokenHandler;

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

/**
 * @author https://github.com/CoderXiaohui
 * @Description
 * @Date 2020-11-07 22:27
 */
public class SimpleExecutor implements Executor{
    /**
     *   Is to carry out JDBC Code for 
     */
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //1.  Registration drive , For a link 
        Connection connection = configuration.getDataSource().getConnection();
        //2.  obtain SQL sentence 
        // Suppose you get SQL yes  : select * from user where id = #{id} and username = #{userName} JDBC It's unrecognizable ,
        //  So we have to change sql : select * from user where id = ? and username = ? , In the process of conversion, we also need to #{} To parse and store the values in 
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        //3.  Get preprocessing object :preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        //4.  Set parameters 
            // Get the full path to the parameter 
        String paramterType = mappedStatement.getParamterType();
        Class<?>  paramterTypeClass = getClassType(paramterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            // Reflection 
            Field declaredField = paramterTypeClass.getDeclaredField(content);
            // Violent visits , Prevent it from being private 
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            // Subscript from 1 Start 
            preparedStatement.setObject(i+1,o);
        }
        //5.  perform sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();
        //6.  Encapsulate the return result set 
        while (resultSet.next()){
            Object o = resultTypeClass.newInstance();
            // Metadata 
            ResultSetMetaData metaData = resultSet.getMetaData();
            //metaData.getColumnCount() : The total number of columns in the query result 
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // Field name 
                String columnName = metaData.getColumnName(i);
                // Value of field 
                Object value = resultSet.getObject(columnName);
                // Use reflection or introspection , According to the corresponding relationship between database table and entity , Finish packaging 
                //PropertyDescriptor  A class in the introspection Library , Is to put resultTypeClass Medium columnName Property to produce read and write methods 
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // Encapsulate specific values to o In this object 
                writeMethod.invoke(o,value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if (paramterType!=null){
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
        return null;
    }

    /**
     *  Finish right #{} Analytical work :
     * 1.  take #{} Use ? Replace 
     * 2.  It is concluded that #{} The values in it are stored 
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        // Tag processing class : Configure the tag parser to complete the processing of the placeholder 
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // Return the parsed sql
        String parseSql = genericTokenParser.parse(sql);
        //#{} The name of the parameter parsed out of it 
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
        return boundSql;
    }
}

package com.dxh.config;
import com.dxh.utils.ParameterMapping;
import java.util.ArrayList;
import java.util.List;
/**
*  The function of this method is explained below 
*/
public class BoundSql {
    private String sqlText;// The resolved sql
    private List<ParameterMapping> parameterMappingList = new ArrayList<>();

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

The implementation here can be roughly divided into 6 part :

  1. Registration drive , For a link : Through the introduction of configuration obtain datasource, And then call getConnection() Get links
  2. obtain SQL sentence
    We mapper.xml Of SQL This is the sentence select * from user where id = #{id} and username = #{username}, Need to be converted to select * from user where id = ? and username =? such JDBC In order to recognize . At the same time, we need to put #{} The parameters in are assigned to ? This is the place where .
    Here we define a getBoundSql Method , adopt Tag processing class ( Configure the tag parser to complete the processing of the placeholder ) It is resolved to be with ? Of sql, At the same time #{} What's in it comes in ParameterMapping in .
  3. adopt connection.prepareStatement(boundSql.getSqlText()) Get the preprocessing object
  4. Set parameters , We are mapper.xml It has been written in the document paramterType, With the full path of the input parameter type, we can get its object through reflection .
    according to ParameterMapping That's deposited in #{} The content in , Get its value by reflection , And then it's bound to the subscript .
  5. perform SQL
  6. Encapsulate the return result set Introspection is used here
  7. return (List<E>) objects

7. end

At this point, the code in our framework has been written .

8. Test class

package com.dxh.test;

import com.dxh.io.Resource;
import com.dxh.pojo.User;
import com.dxh.sqlSession.SqlSession;
import com.dxh.sqlSession.SqlSessionFacetory;
import com.dxh.sqlSession.SqlSessionFacetoryBuild;
import org.dom4j.DocumentException;
import org.junit.Test;

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

public class IPersistenceTest {

    @Test
    public void test() throws Exception {
        InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFacetory.openSession();
        User user = new User();
        user.setId(1);
        user.setUsername("lucy");

        User user2 = sqlSession.selectOne("user.selectOne",user);
        System.out.println(user2.toString());
//        List<User> userList = sqlSession.selectList("user.selectList");
//        for (User user1 : userList) {
//            System.out.println(user1);
//        }
    }
}

Execution results :

User{id=1, username='lucy'}

The final directory structure :

image-20201108015103475

5. Optimization of custom persistence layer framework

Optimize :

Our custom persistence layer framework has been completed , Let's analyze the framework , See if there are any obvious drawbacks .

First , Let's start with a normal project , Create a Dao layer

package com.dxh.dao;
import com.dxh.pojo.User;
import java.util.List;

public interface IUserDao {
    // Query all users 
    public List<User> findAll() throws Exception;
    // Query according to the conditions 
    public User findByCondition(User user) throws Exception;
}

package com.dxh.dao;

import com.dxh.io.Resource;
import com.dxh.pojo.User;
import com.dxh.sqlSession.SqlSession;
import com.dxh.sqlSession.SqlSessionFacetory;
import com.dxh.sqlSession.SqlSessionFacetoryBuild;

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

public class IUserDaoImpl implements IUserDao {
    @Override
    public List<User> findAll() throws Exception {
        InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFacetory.openSession();
        List<User> userList = sqlSession.selectList("user.selectList");
        return userList;
    }

    @Override
    public User findByCondition(User user) throws Exception {
        InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFacetory.openSession();
        User user2 = sqlSession.selectOne("user.selectOne",user);
        return user2;
    }
}

Problem analysis :

  1. Obviously, there is a problem of code duplication , Their first three sentences are the same ( Load profile 、 establish SqlSessionFacetory、 production SqlSeesion)

     InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
     SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
     SqlSession sqlSession = sqlSessionFacetory.openSession();
    
  2. statementId There is a hard coding problem

     List<User> userList = sqlSession.selectList("user.selectList");
     
     User user2 = sqlSession.selectOne("user.selectOne",user);
    

Solutions :

Use the proxy pattern to generate Dao Layer proxy implementation class .

stay SqlSession Add a method to the interface and implement :

// by Dao Interface production agent implementation class 
public <T> T getMapper(Class<?> mapperClass);
    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // Use JDK Dynamic proxy for Dao The interface generates the proxy object , And back to 
        Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return null;
            }
        });
        return (T) o;
    }

We use Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) Method to produce proxy objects . We'll do it later invoke Method .

At this point, if we want to execute the method again, we should do this :

IUserDao iUserDao = sqlSession.getMapper(IUserDao.class);
List<User> all = iUserDao.findAll();

lll

  1. adopt sqlSession.getMapper() Method to get the proxy object
  2. Call through the proxy object findAll() Method
  3. perform invoke Method

Let's see invoke Method :

  • Object proxy : Reference to the current proxy object
  • Method method : Reference to the currently called method
    For example, our current proxy object iUserDao It's called findAll() Method , and method Namely findAll Method reference
  • Object[] args : Parameters passed , For example, we want to query by condition

To write invoke() Method :

Let's be clear first , No matter how it's packaged , The bottom is still executing JDBC Code , Then we have to take different situations into consideration call selectList perhaps selectOne.

Now there is a question :selectList and selectOne All need a parameter ——statementId, And at this point we can't get statementId Of .

But we can base it on method The object gets Method name , and The full class name of the class where the method is located .

So we need to regulate statementId The composition of :

statementId = namespace.id = The full class name of the class where the method is located . Method name

modify UserMapper.xml

image-20201108144050013

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // Use JDK Dynamic proxy for Dao The interface generates the proxy object , And back to 
        Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),
                new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // The bottom is still executing JDBC Code   // According to different situations   call selectList perhaps selectOne
                // Prepare parameters : 1. statementId
                /**
                 * ** Now there is a question :`selectList` and `selectOne` All need a parameter ——`statementId`,
                 *  And at this point we can't get `statementId` Of .
                 *  But we can base it on `method` Object gets the method name , And the full class name of the class where the method is located .
                 *  So we need to regulate statementId The composition of :
                 * **statementId  =  namespace.id  =   The full class name of the class where the method is located . Method name 
                 */
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className+"."+methodName;
                // Prepare parameters :2. args
                // Gets the return value type of the called method 
                Type genericReturnType = method.getGenericReturnType();
                // Determine whether generic type parameterization is performed   It is to determine whether the current return value type has generics 
                if (genericReturnType instanceof ParameterizedType){
                    List<Object> selectList = selectList(statementId, args);
                    return selectList;
                }
                return selectOne(statementId,args);
            }
        });
        return (T) o;
    }

test :

package com.dxh.test;

import com.dxh.dao.IUserDao;
import com.dxh.io.Resource;
import com.dxh.pojo.User;
import com.dxh.sqlSession.SqlSession;
import com.dxh.sqlSession.SqlSessionFacetory;
import com.dxh.sqlSession.SqlSessionFacetoryBuild;
import org.dom4j.DocumentException;
import org.junit.Test;

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

public class IPersistenceTest {

    @Test
    public void test() throws Exception {
        InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFacetory.openSession();
        IUserDao iUserDao = sqlSession.getMapper(IUserDao.class);
        List<User> all = iUserDao.findAll();
        System.out.println(all);
        // Print the results :[User{id=1, username='lucy'}, User{id=2, username=' Li Si '}, User{id=3, username='null'}]
        User user1 = iUserDao.findByCondition(user);
        System.out.println(user1);
       //User{id=1, username='lucy'}
    }
}

版权声明
本文为[IsDxh]所创,转载请带上原文链接,感谢