The original link

JDBC Problem Analysis

Existing problems

  1. The database configuration information is hard coded.
  2. Frequently create release database connections.
  3. SQL statement, set parameters, obtain results parameters are hard coding problems.
  4. Manual encapsulation of the returned result set is cumbersome.

The solution

  1. Through the configuration file

    1. The connection pool
    2. SQL configuration file
    3. Reflection, introspection

Analysis of the framework of custom persistence layer

Essentially, it encapsulates JDBC code

1. The train of thought

Client (User)

  • Introduces jar packages for custom persistence frameworks

  • The client provides two parts of configuration information:

    • Database configuration information: driver classes, database connections, accounts, and passwords
    • SQL configuration information: SQL statement, parameter type, return value type

Use the configuration file to provide these two pieces of information:

Sqlmapconfiguration. XML: stores database configuration information. Full path to mapper.xml (only loaded once for later loading of configuration files)

(2) mapper. XML: SQL configuration information exists

  • The client gets the configuration file input stream (Resources) and uses SqlSessionFactoryBuilder to get the SqlSession to execute the query

Customize the persistence layer framework itself

  • Loading a configuration file: Loads the configuration file as a byte input stream and stores it in memory

    • Resources class: InputSteam getResourceAsSteam(String path)
  • Create two Java classes (container objects) that hold the content parsed from the configuration file

    • Configuration: the core Configuration file, which contains the content parsed from SQLmapconfig. XML
    • MappedStatement: mapping configuration class, which stores the contents parsed by mapper. XML
  • Parse the configuration file: dom4j

    • Create class SqlSessionFactoryBuilder: Build (InputSteam in)
      • Use DOM4J to parse the configuration file and encapsulate the parsed content into container objects
      • Create SqlSessionFactory, produce SqlSession session object (factory mode)
  • Create the SqlSessionFactory interface and implement DefaultSqlSessionFactory

    • OpenSession ();
  • Create SqlSession interface and implement DefaultSqlSession

    • All database additions and deletions are encapsulated here
    • Define a curD operation on a database:
      • list()
      • get()
      • update()
      • delete()
  • Create the Executor interface and implementation class DefaultExecutor implementation class

    • query(Configuration config,MappedStatement statement,Object… Params) : executes JDBC code

2. Process used by the client

1. Configure the client

pom.xml

<dependency>
    <groupId>com.otoomo</groupId>
    <artifactId>persistent</artifactId>
    <version>1.0 the SNAPSHOT</version>
</dependency>
Copy the code

SqlMapConfiguration.xml

<configuration>

    <! -- Database configuration -->
    <datasouce>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///persistent_test? useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </datasouce>

    <! SQL > select * from 'SQL';
    <mapper resource="UserMapper.xml"/>

</configuration>
Copy the code

UserMapper.xml

  • The custom persistence framework can locate the specific SQL that needs to be executed based on the rulesstatementIdTo locate.
  • thestatememtIdThe rules are composed of the following parts:namespace.id(namespace +”.”+ SQL configuration ID)namespace: Full path of the package name of UserDaoid: The unique identifier of the SQL configuration, which is the corresponding method name in the UserDao
  • In SqlSession, it is assembled by the class name + method name of the DAO object currently executedstatememtId, and then executed using the JDK dynamic proxy
<mapper namespace="com.otoomo.dao.UserDao">

    <! -- Query all users -->
    <select id="queryAll" resultType="com.otoomo.pojo.User" parameterType="com.otoomo.pojo.User">
        SELECT * FROM user;
    </select>

    <! -- Get user by ID -->
    <select id="getById" resultType="com.otoomo.pojo.User" parameterType="java.lang.Integer">
        SELECT * FROM user WHERE id = #{id};
    </select>

    <select id="getByIdAndName" resultType="com.otoomo.pojo.User" parameterType="java.util.Map">
        SELECT * FROM user WHERE id = #{id} AND username = #{username};
    </select>

    <! Add user -->
    <insert id="add" parameterType="com.otoomo.pojo.User">
        insert into user (`username`) values(#{username})
    </insert>

    <! -- Delete user -->
    <delete id="delete">
        delete from user where id = #{id}
    </delete>

    <! -- Update user -->
    <update id="update" parameterType="com.otoomo.pojo.User">
        update user set username=#{username} where id = #{id}
    </update>

</mapper>
Copy the code

2. Code application

public class TestApplication {

    UserMapper userMapper;

    @Before
    public void initUserMapper(a) throws ParserException {
        // Get the configuration input stream
        InputStream inputStream = Resources.getResourceAsSteam("sqlMapConfiguration.xml");
        // Get the SQL session
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        userMapper = sqlSession.getMapper(UserMapper.class);
    }

    @Test
    public void addUser(a) {
        User user = new User();
        user.setUsername("zhangsan");
        userMapper.add(user);

        // Get all users
        List<User> users = userMapper.queryAll();
        System.out.println(users);

    }

    @Test
    public void update(a) {
        int id = 15;

        User updateUser = new User();
        updateUser.setId(id);
        updateUser.setUsername("Zhang");
        userMapper.update(updateUser);

        // Get the user
        User user = userMapper.getById(id);
        System.out.println(user);
    }

    @Test
    public void delete(a) {
        int delete = userMapper.delete(16);
        System.out.println(delete);
    }

    @Test
    public void getByIdAndName(a) {
        User user = userMapper.getByIdAndName(17."Tom"); System.out.println(user); }}Copy the code

3. Implementation steps

Maven dependency profile:

<dependencies>
    <! -- Database related -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.17</version>
    </dependency>
    <! -- Connection pool -->
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <! --END database related -->

    <! -- Dom4j XML parsing package -->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <! Dom4j uses Xpath to locate elements when using selectNodes, so you need to add JAXEN -->
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.1</version>
    </dependency>
</dependencies>
Copy the code

Create the Resources class

Resources is used to retrieve file configuration information, return an input stream to the caller, and then parse the configuration through DOM4J or other means

public class Resources {

    private Resources(a) {}/** * get the resource InputStream */ for the specified path
    public static InputStream getResourceAsSteam(String path) {
        InputStream resourceInputStream = ClassLoader.getSystemResourceAsStream(path);
        returnresourceInputStream; }}Copy the code

2. Create two container classes

MappedStatement

The Sql configuration mapping container class stores the configuration information in mapper.xml. A configuration item is an MappedStatement object

public class MappedStatement {
    /** * SQL unique id */
    private String id;
    /** * Return value type */
    private String resultType;
    /** * Parameter value type */
    private String paramterType;
}
Copy the code

Configuration Core Configuration container class

Database configuration information container; The MappedStatement and DataSource need to be retrieved frequently.

To reduce the passing of multiple parameters, the MappedStatement information can be stored in the core configuration container mappedStatementMap.

All you need to do is pass the Configuration parameter

public class Configuration {
    /** * Stores the database configuration information object */
    private DataSource dataSource;

    /** * SQL configuration mapping Map * key: statementId * value: The encapsulated SQL configuration mapping class * 

* statementId adds the unique ID identifier of the SQL configuration mapping to the namespace in mapper. XML * statementId = namespace.id */

private Map<String, MappedStatement> mappedStatementMap = new HashMap<>(); } Copy the code

3. Resolve the core configuration

Dom4j is used to parse the file. The core configuration file is SQLMapConfiguration.xml

Divided into three steps:

  1. Create the XmlDataSourceParser to parse the database configuration information
  2. Create an XmlMapperParser to parse mapper.xml
  3. Create a core XmlConfigurationParser that calls both XmlDataSourceParser and XmlMapperParser
public interface Parser<I> {
    <T> T parse(I parseObj) throws ParserException;
}
Copy the code

1. Create the XmlDataSourceParser

Parse the database configuration. Obtain the dataSource of the Configuration container

<datasource>
	<property name="driviceClass" value="..."/>
</datasource>
Copy the code
public class XmlDataSourceConfigParser implements Parser<Document> {

    private Configuration configuration;

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

    @Override
    public DataSource parse(Document document) throws ParserException {
        try {
            //<configuration>
            Element rootElement = document.getRootElement();
            // Get all property configuration items
            List<Element> propertyElements = rootElement.selectNodes("//property");

            // Get all property values
            Properties properties = new Properties();
            propertyElements.forEach(element -> {
                String name = element.attributeValue("name");
                String value = element.attributeValue("value");
                properties.put(name, value);
            });

            // Obtain the data source through the configuration information
            DataSource dataSource = buildDataSource(properties);
            configuration.setDataSource(dataSource);
        } catch (DataSourceConfigParamException | PropertyVetoException e) {
            throw new ParserException(e.getMessage(), e);
        }
        return null;
    }


    /** * Build data source **@param properties
     * @return
     * @throws DataSourceConfigParamException
     * @throws PropertyVetoException
     */
    private DataSource buildDataSource(Properties properties) throws DataSourceConfigParamException, PropertyVetoException {
        // Check whether the database core configuration is valid
        checkDataSourcePropertysValidity(properties);

        String driverClass = properties.getProperty("driverClass");
        String jdbcUrl = properties.getProperty("jdbcUrl");
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
	
        //TODO nulls
        
        // Create a database connection pool after obtaining the configuration information
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(driverClass);
        comboPooledDataSource.setJdbcUrl(jdbcUrl);
        comboPooledDataSource.setUser(username);
        comboPooledDataSource.setPassword(password);
        //TODO improves more parameter configurations

        return comboPooledDataSource;
    }

    /** * Check whether the database configuration information is valid **@param properties
     * @throws DataSourceConfigParamException
     */
    private void checkDataSourcePropertysValidity(Properties properties) throws DataSourceConfigParamException {
        String driverClass = properties.getProperty("driverClass");
        String jdbcUrl = properties.getProperty("jdbcUrl");
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");

        if (StringUtils.isNullOrEmpty(driverClass)) {
            throw new DataSourceConfigParamException("driverClass");
        }
        if (StringUtils.isNullOrEmpty(jdbcUrl)) {
            throw new DataSourceConfigParamException("jdbcUrl");
        }
        if (StringUtils.isNullOrEmpty(username)) {
            throw new DataSourceConfigParamException("username");
        }
        if (StringUtils.isNullOrEmpty(password)) {
            throw new DataSourceConfigParamException("password"); }}}Copy the code

2. Create an XmlMapperParser

The mapper. XML file path for parsing and the specific mapper. XML file are saved in the MappedMatement container.

Save the MappedMatement to the mappedStatementMap in the Configuration container

<mapper resource="UserMapper.xml" />.Copy the code
public class XmlMapperParser implements Parser<InputStream> {

    private Configuration configuration;

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

    /** * parse mapper files **@param inputStream
     * @return* /
    public MappedStatement parse(InputStream inputStream) throws ParserException {
        try {
            Document document = new SAXReader().read(inputStream);
            //<mapper>
            Element rootElement = document.getRootElement();
            / / select, update, insert, and delete statements
            handlerSelectElements(rootElement);
            handlerUpdateElements(rootElement);
            handlerInsertElements(rootElement);
            handlerDeleteElements(rootElement);

        } catch (DocumentException e) {
            throw new ParserException(e.getMessage(), e);
        }
        return null;
    }

    private void handlerSelectElements(Element rootElement) {
        handlerElements(rootElement, "select", SqlCommandType.SELECT);
    }

    private void handlerDeleteElements(Element rootElement) {
        handlerElements(rootElement, "delete", SqlCommandType.DELETE);
    }

    private void handlerInsertElements(Element rootElement) {
        handlerElements(rootElement, "insert", SqlCommandType.INSERT);
    }

    private void handlerUpdateElements(Element rootElement) {
        handlerElements(rootElement, "update", SqlCommandType.UPDATE);
    }

    /** * process SQL configuration **@param rootElement
     * @param type
     * @param sqlCommandType
     */
    private void handlerElements(Element rootElement, String type, SqlCommandType sqlCommandType) {
        // Namespace
        String namespace = rootElement.attributeValue("namespace");
        List<Element> elementList = rootElement.selectNodes("/ /" + type);
        elementList.forEach(element -> {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sql = element.getTextTrim();

            MappedStatement mappedStatement = new MappedStatement(id, resultType, parameterType, sql, sqlCommandType);

            configuration.getMappedStatementMap().put(namespace + "."+ id, mappedStatement); }); }}Copy the code
  • SqlCommandType is used to record the type of THE SQL mapping
public enum SqlCommandType {
    INSERT, / / insert
    UPDATE, / / update
    DELETE,	/ / delete
    SELECT;	/ / query

    private SqlCommandType(a) {}}Copy the code

3. Create the core XmlConfigurationParser

Use to call XmlDataSourceParser, XmlMapperParser two parsers, get DataSource and mappedStatementMap data.

<configuration>
	<datasource >
    	<property name="" value=""/>
    </datasource>
    <mapper resource=""/>
</configuration>
Copy the code
public class XmlConfigurationParser implements Parser<InputStream> {

    private Configuration configuration;

    public XmlConfigurationParser(a) {
        configuration = new Configuration();
    }

    @Override
    public Configuration parse(InputStream inputStream) throws ParserException {

        try {
            Document document = new SAXReader().read(inputStream);
            /*
            解析数据库配置
             */
            Parser xmlDataSourceConfigParser = new XmlDataSourceConfigParser(configuration);
            xmlDataSourceConfigParser.parse(document);

            /* Parse the mapper configuration */
            Parser xmlMapperParser = new XmlMapperParser(configuration);

            / / < configuration > root element
            Element rootElement = document.getRootElement();
            // Get all mapper elements
            List<Element> mapperElements = rootElement.selectNodes("//mapper");
            for (Element mapperPathElement : mapperElements) {
                // Get the specific mapper. XML file path
                String mapperPath = mapperPathElement.attributeValue("resource");
                InputStream mapperInputStream = Resources.getResourceAsSteam(mapperPath);

                // Start parsing the mapper.xml file
                xmlMapperParser.parse(mapperInputStream);
            }

            return configuration;
        } catch (DocumentException e) {
            throw newParserException(e.getMessage(), e); }}}Copy the code

4. Create a SqlSession

Divided into three steps:

  1. Create SqlSession interface and implement DefaultSqlSession
  2. Create the SqlSessionFactory factory class to produce SQLSessions
  3. Create the SqlSessionFactoryBuilder to create the SqlSessionFactory

The Executor class is involved here. This will be implemented in step 5, where we first implement SqlSession related creation

1.SqlSession

SqlSession is used to perform JDBC CURD operations.

public interface SqlSession {
    <T> List<T> query(String statementId, Object... params) throws Exception;

    <T> T get(String statementId, Object... params) throws Exception;

    int update(String statementId, Object... params) throws Exception;

    int delete(String statementId, Object... params) throws Exception;

    int insert(String statementId, Object... params) throws Exception;

    /** * To reduce statementId hardcoding and code duplication of SqlSession. * <p> * Use dynamic proxy methods, using JDK dynamic proxy, to map the execution of the corresponding query, selectOne,update, and other interfaces * *@param clazz
     * @param <T>
     * @return* /
    <T> T getMapper(Class
        clazz);
}

Copy the code
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

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

    @Override
    public <T> List<T> query(String statementId, Object... params) throws Exception {
        DefaultExecutor defaultExecutor = new DefaultExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);

        return defaultExecutor.query(configuration, mappedStatement, params);
    }

    @Override
    public <T> T get(String statementId, Object... params) throws Exception {
        // Query the list interface to get the first value returned
        List<Object> objectList = query(statementId, params);
        if (objectList.size() == 1) {
            return (T) objectList.get(0);
        }
        if (objectList.size() > 1) {
            throw new RuntimeException("Multiple Return Objects Error");
        }
        return null;
    }

    @Override
    public int update(String statementId, Object... params) throws Exception {
        DefaultExecutor defaultExecutor = new DefaultExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return defaultExecutor.update(configuration, mappedStatement, params);
    }

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

    @Override
    public int insert(String statementId, Object... params) throws Exception {
        return update(statementId, params);
    }

    @Override
    public <T> T getMapper(Class
        clazz) {
        // Use the JDK's dynamic proxy technology

        Object handler = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
            // The underlying implementation is JDBC
            StatementId, params,
            // The syntax of statementId is namespace.id.
            // Therefore, the naming convention of mapper. XML is as follows: Namespace is the name of Dao's full-path class, and ID is the name of Dao's function

            //id
            String id = method.getName();

            / / the name of the class
            String namespace = method.getDeclaringClass().getName();
            String statementId = namespace + "."+ id; Class<? > returnType = method.getReturnType();// If the number of arguments is greater than 1, it is converted to a map object and passed as the value of the first argument
            int parameterCount = method.getParameterCount();
            if (parameterCount > 1) {
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                Map<String, Object> paramsMap = new HashMap<>();
                int i = 0;
                for (Annotation[] parameterAnnotation : parameterAnnotations) {
                    for (Annotation annotation : parameterAnnotation) {
                        if (annotation instanceof Param) {
                            Param param = (Param) annotation;
                            String value = param.value();
                            paramsMap.put(value, args[i]);
                            i++;
                        }
                    }
                }
                args[0] = paramsMap;
            }

            MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
            SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
            switch (sqlCommandType) {
                case SELECT:
                    boolean returnMany = returnMany(returnType);
                    if (returnMany) {
                        // set query
                        return query(statementId, args);
                    }
                    // Single query
                    return get(statementId, args);
                case INSERT:
                    return insert(statementId, args);
                case UPDATE:
                    return update(statementId, args);
                case DELETE:
                    return delete(statementId, args);
                default:
                    throw new RuntimeException("Sql command type error"); }});return (T) handler;
    }

    /** * Determine whether the return type is a collection or an array@param returnType
     * @return* /
    private boolean returnMany(Class
        returnType) {
        boolean isCollection = Collection.class.isAssignableFrom(returnType);
        boolean isArray = returnType.isArray();
        returnisCollection || isArray; }}Copy the code
  • The Param annotation class is involved

    The parameter name used to specify the input parameter. If you specify a name for a multi-parameter method, you will not get the #{username} KEY in mapper. XML

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER})
    public @interface Param {
        String value(a);
    }
    
    Copy the code

    Chestnut:

    public int select(@Param("username") String username,@Param("status") Integer status)
    
    Copy the code

2. Create a SqlSessionFactory

Factory schema class for producing SQLSessions

public interface SqlSessionFactory {
    SqlSession openSession(a);
}

Copy the code
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

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

    @Override
    public SqlSession openSession(a) {
        DefaultSqlSession sqlSession = new DefaultSqlSession(configuration);
        returnsqlSession; }}Copy the code

3.SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {

    /** * Create SqlSessionFactory ** by configuring the input stream@author modongning
     * @date 24/09/2020 10:51 PM
     */
    public static SqlSessionFactory build(InputStream inputStream) throws ParserException {
        /* 1. Use dom4j to parse the Configuration file and save it in the Configuration container */
        Parser configurationParser = new XmlConfigurationParser();
        Configuration configuration = (Configuration) configurationParser.parse(inputStream);
        /* Create SqlSessionFactory to implement DefaultSqlSessionFactory. Since the configuration information is always needed later, the */ is passed in using the parameter constructor
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        returnsqlSessionFactory; }}Copy the code

5. Create an SQL Executor

1. Implementation

/** * SQL execution */
public interface Executor {
    /** ** **@param configuration
     * @param mappedStatement
     * @param params
     * @return
     * @throws Exception
     */
    int count(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /** * insert operation **@param configuration
     * @param mappedStatement
     * @param params
     * @return* /
    int insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /** * Delete operation **@param configuration
     * @param mappedStatement
     * @param params
     * @return
     * @throws Exception
     */
    int delete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /** * Update operation **@param configuration
     * @param mappedStatement
     * @param params
     * @return
     * @throws Exception
     */
    int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /** * Perform the query operation **@paramConfiguration Core configuration container *@paramMappedStatement SQL configuration mapping *@paramParams parameters *@param <T>
     * @return* /
    <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

}

Copy the code
/** * Default implementation of the SQL executor */
public class DefaultExecutor implements Executor {

    @Override
    public int count(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        return update(configuration, mappedStatement, params);
    }

    @Override
    public int insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        return update(configuration, mappedStatement, params);
    }

    @Override
    public int delete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        return update(configuration, mappedStatement, params);
    }

    @Override
    public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //TODO inserts, deletes, and statistics all return numeric results. All are performed using the update operation
        return (int) executor(configuration, mappedStatement, params);
    }

    @Override
    public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        ResultSet resultSet = (ResultSet) executor(configuration, mappedStatement, params);

        //6. Encapsulate the return valueString resultType = mappedStatement.getResultType(); Class<? > resultTypeClass = getClassBy(resultType); List<Object> resultList =new ArrayList<>();
        while (resultSet.next()) {
            // Data object
            Object o = resultTypeClass.newInstance();
            // The metadata object contains the field name of the data
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                / / the field name
                String columnName = metaData.getColumnName(i);
                / / field values
                Object value = resultSet.getObject(columnName);

                // Once you have the object field name and value, use reflection or introspection to set the corresponding value of the object
                // Get the attribute description of the columnName of the resultTypeClass class
                //TODO statistical statements such as Select count(*) return a ResultSet. Determine whether a resultType is an object based on the return type of the resultType.
                try {
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                    // Get the write method
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    // Set the value of the object field through the write method of the field
                    writeMethod.invoke(o, value);
                } catch (IntrospectionException e) {
                    // If there is no corresponding field, ignore it
                    continue;
                }
            }
            resultList.add(o);
        }

        return (List<T>) resultList;
    }

    private Object executor(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //1. Obtain the database connection
        Connection connection = configuration.getDataSource().getConnection();
        SELECT * FROM user WHERE id = #{id}; * /
        String mappedStatementSql = mappedStatement.getSql();
        SELECT * FROM user WHERE id =?
        BoundSql boundSql = getBoundSql(mappedStatementSql);
        //4. Set parameters
        // Preprocess the object
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        String parameterType = mappedStatement.getParameterType();
        // Get the type of the parameter classClass<? > parameterTypeClass = getClassBy(parameterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {
            //TODO adds more parameter judgment logic later
            Object param = params[0];
            if (null == parameterTypeClass
                    || (parameterTypeClass.isAssignableFrom(Integer.class)
                    || parameterTypeClass.isAssignableFrom(Long.class)
                    || parameterTypeClass.isAssignableFrom(Short.class)
                    || parameterTypeClass.isAssignableFrom(Number.class)
                    || parameterTypeClass.isAssignableFrom(Double.class)
                    || parameterTypeClass.isAssignableFrom(Float.class)
                    || parameterTypeClass.isAssignableFrom(Character.class)
                    || parameterTypeClass.isAssignableFrom(Boolean.class)
                    || parameterTypeClass.isAssignableFrom(Byte.class))
            ) {
                //TODO needs to be improved. It can be stored in Map according to the parameter name by judging the type. The parameter name is =value, and it can be treated as an object
                preparedStatement.setObject(i + 1, param);
                continue;
            }
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            Object value = null;
            if (param instanceof Map) {
                value = ((Map) param).get(content);
            } else {
                // Get the field of the content name in the parameter by reflection
                Field contentField = parameterTypeClass.getDeclaredField(content);
                contentField.setAccessible(true);
                // Get the parameter value from the input object
                // Get the value of the object content field from the object with argument 0
                value = contentField.get(param);
            }
            preparedStatement.setObject(i + 1, value);
        }
        / / 5. Execute SQL
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if (SqlCommandType.SELECT.equals(sqlCommandType)) {
            return preparedStatement.executeQuery();
        }
        return preparedStatement.executeUpdate();
    }

    privateClass<? > getClassBy(String parameterType)throws ClassNotFoundException {
        if (null! = parameterType) {return Class.forName(parameterType);
        }
        return null;
    }

    /** * use #{} with? 2. Store the parsed value of #{}, which is then reflected to obtain the parameter value * *@param mappedStatementSql
     * @return* /
    private BoundSql getBoundSql(String mappedStatementSql) {
        // The tag handler class configures the tag parser to handle placeholders.
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("# {"."}", parameterMappingTokenHandler);

        SQL: SELECT * FROM user WHERE id =?
        String sql = genericTokenParser.parse(mappedStatementSql);
        //#{} The parsed parameter name
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        return newBoundSql(sql, parameterMappings); }}Copy the code

3. Related classes to be used by Executor

1.BoundSql
  1.  /** * Sql configurates the configuration of the mapping class and preprocesses the Sql after parsing and parameter name container */
     public class BoundSql {
     
         /** * preprocessing SQL */
         private String sqlText;
         /** * List of parsed SQL parameter names */
         private List<ParameterMapping> parameterMappingList;
     
         public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
             this.sqlText = sqlText;
             this.parameterMappingList = parameterMappingList; }}Copy the code
2.ParameterMapping

Store the parsed parameter names of #{}

public class ParameterMapping {
    public ParameterMapping(String content) {
        this.content = content;
    }
    /**
     * 参数名称
     */
    private String content;
}
Copy the code
3.ParameterMappingTokenHandler

Select * from #{username} where username = ‘username’;

public interface TokenHandler {
  String handleToken(String content);
}
Copy the code
/** * stores values between SQL statement placeholders #{} and returns the replacement symbol "?" Parametermapping. content is "useranme" */
public class ParameterMappingTokenHandler implements TokenHandler {
   private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

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

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

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

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

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

General purpose symbol parser, used to parse placeholders for #{}

public class GenericTokenParser {

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

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

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

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

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