ORM (Object relational Mapping) : A technique designed to solve the mismatch between object-oriented and relational databases. The Persistence layer framework (ORM) in Java is mainly to solve: JDCB operation SQL hardcoding problems and performance, difficult to maintain problems.

Problems with JDBC operations:

  1. The configuration information of the database is hard coded
  2. Frequently create and release database connections
  3. SQL statements, setting parameters, and obtaining result sets are hard coded
  4. Manual encapsulation of result sets is tedious

The persistence layer framework is designed to solve the above problems by automatically persisting the objects in the program to the relational database.

Persistent layer framework design ideas

Solutions to JDBC operation problems:

  • Fault 1: The database configuration information is hard coded. -> Add the database configuration information to the configuration file.
  • Problem 2: Frequently creating and releasing database connections -> Using connection pooling techniques
  • Problem 3: SQL statement, parameter setting, result set hard coding problem -> configuration file
  • Problem 4: Manual encapsulation of result sets is tedious -> reflection, introspection techniques

Design the persistence layer framework according to the above solution:

  1. Usage end: The introduction of custom persistence layer framework JAR package, providing two parts of configuration information: database configuration file (sqlMapConfig.xml), SQL configuration fileMapper.xml(SQL, parameters, return values, etc.)
  2. Architecture itself: Encapsulates JDBC code
    1. Loading the configuration file:ResourceClass,InputSteam getResourceAsSteam(path)Method, based on the configuration file path, loads the configuration file and returns a stream of byte input stored in memory. In 1 we designed two configuration files, the core configuration filesqlMapConfig.xmlAnd the SQL mapping configuration fileMapper.xmlIn actual development, we cannot write all the mapping configuration files in one configuration file. Instead, there are multiple mapping configuration files based on functions. Therefore, all mapping configuration files need to be added to the core configuration file in the form of XML tagssqlMapConfig.xmlSo thatResourceClass load onlysqlMapConfig.xmlInstead of adding a lot of mapping configuration files.
    2. Create two JavaBean objects: These two Java objects are mainly used to hold the content parsed out of the configuration file.ConfigurationCore configuration classes,MappedStatementMap configuration classes.
    3. Parsing configuration files: Usedom4jParse to create a classSqlSessionFactoryBuilderDefine methodsbuild(InputSteam in).buildThe functions of the method of:
      1. usedom4jParse what is returned in step AInputSteam, place the content in the two JavaBean objects created in step B;
      2. createSqlSessionFactoryObject, productionSqlSessionSession object (Factory mode)
    4. createSqlSessionFactoryInterfaces and implementation classesDefaultSqlSessionFactory, main methodsopenSession()productionSqlSession
    5. createSqlSessionInterfaces and implementation classesDefaultSession, CRUD operation method for database:selectList() selectOne() update() delete()
    6. createExecutorInterfaces and implementation classesSimpleExecutormethodsquery(Configuration,MappedStatement,Object... params)

The specific flow chart is as follows:OK, so we’ve got the framework of the persistence layer sorted out and then we’re going to roll out the code and create a project that’s packaged as a JAR

 <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>
Copy the code

Configuration file definition and parsing implementation

Create a maven project on the user side and create a configuration file:

  • Core configuration file

      
<configuration>
    <! -- Data source configuration -->
    <datasource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_test"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </datasource>

    <! -- Mapper configuration -->
    <mapper resource="UserMapper.xml"/>
</configuration>
Copy the code
  • Mapping configuration file

      
<mapper namespace="com.pbatis.dao.IUserDao">
    <select id="findAll" resultType="com.pbatis.pojo.User">
        select * from tb_user
    </select>

    <select id="findById" resultType="com.pbatis.pojo.User" parameterType="com.pbatis.pojo.User">
        select * from tb_user where id = #{id} and username=#{username}
    </select>

    <insert id="save" parameterType="com.pbatis.pojo.User">
        insert into tb_user(id,username,password,birthday,version,is_delete)
        values(null,#{username},#{password},#{birthday},#{version},#{is_delete})
    </insert>

    <update id="update" parameterType="com.pbatis.pojo.User">
        update tb_user set username=#{username} where id=#{id}
    </update>

    <delete id="delete" parameterType="com.pbatis.pojo.User">
        delete from tb_user where id=#{id}
    </delete>
</mapper>
Copy the code

Parsing the configuration file, in parsing the configuration file we need to store the configuration data, so first create the storage class. Configuration Information: Configuration Stores mapping information about a DataSource and a Mapper

public class Configuration {
    /** * Data source object */
    private DataSource dataSource;

    /** * Store mapper mapping * key: statementid = (Namespace + ID) * value: MappedStatement - Parameter type, return value type, SQL statement */
    private Map<String, MappedStatement> mapper = new HashMap<>();

    //get/set....
}
Copy the code

Mapping information of Mapper files: MappedStatement stores mapping information of each tag in each mapping file, such as < SELECT > < INSERT >

< DELETE > tags, attributes for setting tags, and SQL statements.

public class MappedStatement implements Serializable {
    / / id identification
    private String id;
    // Return value type
    private String resultType;
    // Parameter value type
    private String parameterType;
    / / SQL statements
    private String sql;
    / / the type of mapped
    private MappedType mappedType;

    //get/set...
}
// The mapper type corresponds to the  < INSERT /> 
       
       tag
public enum MappedType {
    SELECT, INSERT, UPDATE, DELETE
}
Copy the code

XmlConfigBuild parses the core configuration file, and XMLMapperBuilder parses the mapper mapping configuration file. Dom4j is used for parsing, data source is parsed, data source information is stored in c3p0 connection pool, mapper mapping file is parsed, inputStream inputStream is given to XMLMapperBuilder for parsing.

public class XmlConfigBuilder {

    private Configuration configuration;

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

    /** * Parse the Configuration using dom4j and encapsulate it as a Configuration object **@param inputStream
     * @return Configuration
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(inputStream);
        // Root tag 
      
        Element rootElement = document.getRootElement();
        // Find the property tag
        List<Element> list = rootElement.selectNodes("//property");
        // Key-value pair storage
        Properties properties = new Properties();
        // Iterate over the tag to get the tag attribute value
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
        }

        // Database connection pool data source object
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("url"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));
        // Save the data source to Configuration
        configuration.setDataSource(comboPooledDataSource);

        // Parse mapper. XML to get the path
        List<Element> mapperElement = rootElement.selectNodes("//mapper");
        for (Element element : mapperElement) {
            String resource = element.attributeValue("resource");
            System.out.println("resource:" + resource);
            // Get the input stream for mapper.xml
            InputStream resourceAsSteam = Resources.getResourceAsSteam(resource);
            / / parsing mapper XML
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parseMapper(resourceAsSteam);
        }
        returnconfiguration; }}Copy the code

The XMLMapperBuilder class implements the following code: Parse namesPeace with the id attribute of the tag to form a unique identity statementid. This information is eventually stored in the Configuration mapper.

public class XMLMapperBuilder {
    private Configuration configuration;

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

    public void parseMapper(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        / / the select tag
        List<Element> selectNodes = rootElement.selectNodes("//select");
        extracted(namespace, selectNodes, MappedType.SELECT);

        / / insert tags
        List<Element> insertNodes = rootElement.selectNodes("//insert");
        extracted(namespace, insertNodes, MappedType.INSERT);

        / / update the label
        List<Element> updateNodes = rootElement.selectNodes("//update");
        extracted(namespace, updateNodes, MappedType.UPDATE);

        / / delete tags
        List<Element> deleteNodes = rootElement.selectNodes("//delete");
        extracted(namespace, deleteNodes, MappedType.DELETE);
    }

    private void extracted(String namespace, List<Element> selectNodes, MappedType mappedType) {
        for (Element selectNode : selectNodes) {
            String id = selectNode.attributeValue("id");
            // Get the return value type
            String resultType = selectNode.attributeValue("resultType");
            // Get the parameter type
            String parameterType = selectNode.attributeValue("parameterType");
            // Get the SQL statement
            String sql = selectNode.getText();
            //statementId
            String statementid = namespace + "." + id;
            // Store it in MappedStatement
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setSql(sql);
            mappedStatement.setMappedType(mappedType);
            // Stores data to configurationconfiguration.getMapper().put(statementid, mappedStatement); }}}Copy the code

OK, after the above steps we have got all the configuration information, now we get the configuration information for SQL operation.

Build the SqlSession

SqlSession is built by SqlSessionFactory, and SqlSessionFactory is created by SqlSessionFactoryBuild. The entry class that parses the configuration file can be said to be the entry class for the custom persistence layer framework.

SqlSessionFactoryBuild

The implementation is as follows: XmlConfigBuilder is called to parse the Configuration file, and DefaultSqlSessionFactory is created to pass the Configuration in

public class SqlSessionFactoryBuilder {
    /**
     * SqlSessionFactory
     *
     * @param inputStream
     * @return* /
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
        //1. Dom4j parses the Configuration file and encapsulates the parsed memory into Configuration
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);

        //2. Create SqlSessionFactory: produce sqlSession session objects
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        returndefaultSqlSessionFactory; }}Copy the code

SqlSessionFactory

DefaultSqlSessionFactory implements the SqlSessionFactory interface and produces SqlSession objects by calling the openSession method

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

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

    @Override
    public SqlSession openSession(a) {
        return newDefaultSqlSession(configuration); }}Copy the code

SqlSession

The SqlSession interface is as follows:

public interface SqlSession {
    /** * query a single ** based on the condition@paramStatementid = Namespace. id Uniquely identifies the SQL statement *@paramParams parameters * /
    <E> E selectOne(String statementid, Object... params);

    /** * query all **@paramStatementid = Namespace. id Uniquely identifies the SQL statement *@paramParams parameters * /
    <E> List<E> selectList(String statementid, Object... params);

    /** * add */
    int insert(String statement, Object params);

    /** * modify */
    int update(String statement, Object params);
}
Copy the code

We need an interface to implement SqlSession. Note that the implementation class of SqlSession does not perform SQL operations. The Configuration, along with mappedStatement and params, is passed to Executor to perform the real SQL operations

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

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

    @Override
    public <E> E selectOne(String statementid, Object... params) {
        Call the selectList method
        List<Object> objects = selectList(statementid, params);
        //size = 1 indicates that there is only one record
        if (objects.size() == 1) {
            return (E) objects.get(0);
        } else {
            throw new RuntimeException("Query result is empty or too many results are returned"); }}@Override
    public <E> List<E> selectList(String statementid, Object... params) {
        // Call the Executor query method
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        // Obtain Mapper mapping information stored in configuration
        MappedStatement mappedStatement = configuration.getMapper().get(statementid);
        List<Object> list = null;
        try {
            list = simpleExecutor.query(configuration, mappedStatement, params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (List<E>) list;
    }

    @Override
    public int insert(String statement, Object params) {
        return update(statement, params);
    }

    @Override
    public int update(String statement, Object params) {
        try {
            // Insert into Executor
            SimpleExecutor simpleExecutor = new SimpleExecutor();
            // Obtain Mapper mapping information stored in configuration
            MappedStatement mappedStatement = configuration.getMapper().get(statement);
            // Perform the update operation
            int row = simpleExecutor.update(configuration, mappedStatement, params);
            return row;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public int delete(String statement, Object params) {
        returnupdate(statement, params); }}Copy the code

Executor implementation

Executor Executor interface

public interface Executor {
    // Query operation
    <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, Exception;
	// Add, update, delete operations
    int update(Configuration configuration, MappedStatement mappedStatement, Object params);
}
Copy the code

To create SimpleExecutor to implement the Executor interface, let’s first look at the implementation of the Query operation. How do you do that? In fact, the bottom is to call JDBC implementation, DO not know whether you still remember the IMPLEMENTATION of JDBC, see the following code:

		// 1. Get the connection
		Connection connection = configuration.getDataSource().getConnection();
		// 2. SQL statementString SQL = select * from user where id =? And the username =?// 3. Obtain the prepared object preparedStatement and pass the parsed SQL
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
		// 4. Set parameters
		preparedStatement.setObject(1.1);
		preparedStatement.setObject(2."tom");
		// 5. Get the result set
		ResultSet resultSet = preparedStatement.executeQuery();
Copy the code

Select * from user where id =? And the username =? Select * from tb_user where id =#{id} and username=#{username}

  1. will# {}replace?
  2. record#{id}Property values insideidBecause we’re going to take that property value, go to the parameter object, find the corresponding parameter value, and set it to a preparedStatement

First we parse the SQL statement and record the property values in the BoundSql class

public class BoundSql {
    // Parsed SQL
    private String sqlText;

    private List<ParameterMapping> parameterMappingList = new ArrayList<>();
}
Copy the code

Parsing SQL implementation method is a utility class, the code is as follows:

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

The main purpose of TokenHandler is to replace what content with? And write it down

public class ParameterMappingTokenHandler implements TokenHandler {
    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

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

    @Override
    public String handleToken(String content) {
        // Store the parameter name of #{}
        parameterMappings.add(buildParameterMapping(content));
        // Replace #{} with? No.
        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

Ok, so our implementation class for the entire actuator is as follows:

public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // Execute the JDBC code
        // 1. Register the driver and obtain the connection
        Connection connection = configuration.getDataSource().getConnection();

        // 2. Obtain the SQL statement
        // select * from user where id = #{id} and username=#{username}
        // The JDBC recognized placeholder can only be? Why use #{}? You can find the value of the attribute that passes the parameter entity type based on the parameter name inside
        String sql = mappedStatement.getSql();
        Select * from user where id =? And the username =?
        // We need to parse the value of #{} to find the corresponding attribute value of the entity object to add
        BoundSql boundSql = getBoundSql(sql);

        // 3. Obtain the prepared object preparedStatement and pass the parsed SQL
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. Set parameters
        // The full class name of the parameter
        String parameterType = mappedStatement.getParameterType();
        // Get the Class of the argumentClass<? > parameterClass = getClassType(parameterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            // Reflection gets the value of the attribute in the entity object based on the parameter name
            Field field = parameterClass.getDeclaredField(content);
            // Violent access
            field.setAccessible(true);
            Object o = field.get(params[0]);
            // Set parameter values
            preparedStatement.setObject(i + 1, o);
        }

        // 5. Execute SQL
        ResultSet resultSet = preparedStatement.executeQuery();
        // 6. Encapsulate the returned result set
        String resultType = mappedStatement.getResultType();
        // Get the specific class of the returned resultClass<? > resultClass = getClassType(resultType);// Create a collection
        ArrayList<Object> results = new ArrayList<>();
        while (resultSet.next()) {
            // Create an object instance of the resulting class
            Object instance = resultClass.newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            / / cycle
            for (int i = 1; i < metaData.getColumnCount(); i++) {
                / / the field name
                String columnName = metaData.getColumnName(i);
                / / field values
                Object object = resultSet.getObject(columnName);
// System.out.println(columnName + ":" + object);
                // Use reflection introspection to complete encapsulation according to the correspondence between database tables and entities
                //PropertyDescriptor provides an implementation of introspection techniques
                // The method that reads and writes the columnName attribute in the resultClass
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClass);
                // Write method
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // Write the specific value to the object
                writeMethod.invoke(instance, object);
            }
            results.add(instance);
        }

        return (List<E>) results;
    }
    
    /** * to parse #{} : * 1. 2. Parse the value inside #{} to store * *@param sql
     * @return* /
    private BoundSql getBoundSql(String sql) {
        // Tag handler class: configure the tag parser to handle placeholders
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("# {"."}", parameterMappingTokenHandler);
        // Parsed SQL
        String parseSql = genericTokenParser.parse(sql);
        //#{} The parsed parameter name
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        returnboundSql; }}Copy the code

test

After the long coding, we finally write a simple test: to see if the custom persistence layer framework we designed can run the logic properly, go to a new project, rely on the custom persistence layer framework to write the configuration file first:


      
<configuration>
    <! -- Data source configuration -->
    <datasource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_test"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </datasource>

    <! -- Mapper configuration -->
    <mapper resource="UserMapper.xml"/>
</configuration>
Copy the code

Writing a mapping file:


      
<mapper namespace="com.pbatis.dao.IUserDao">
    <select id="findAll" resultType="com.pbatis.pojo.User">
        select * from tb_user
    </select>

    <select id="findById" resultType="com.pbatis.pojo.User" parameterType="com.pbatis.pojo.User">
        select * from tb_user where id = #{id} and username=#{username}
    </select>
 </mapper>
Copy the code

The test code is as follows:

public class IPersistenceTest {
    public static void main(String[] args) throws DocumentException, PropertyVetoException {
        InputStream inputStream = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = new User();
        user.setId(3);
        user.setUsername("jake");
        // Pass statementId = namespace + "." + id
        User findUser = sqlSession.selectOne("com.pbatis.dao.IUserDao.findById", user);
        System.out.println(findUser);
        // Pass statementId = namespace + "." + id
        List<User> list = sqlSession.selectList("com.pbatis.dao.IUserDao.findAll");
        for(User user1 : list) { System.out.println(user1); }}}Copy the code

Run the query result:

Dynamic proxy Mapper

In the use of MyBatis development, will go to create the INTERFACE of the DAO layer, so the custom persistence layer framework can also be implemented? The code is as follows: IUserDao interface

public interface IUserDao {
    // Query all users
    List<User> findAll(a);

    // Perform user queries based on conditions
    User findById(User user);
}
Copy the code

You also need to implement the interface:

public class UserDaoImpl implements IUserDao {
    @Override
    public List<User> findAll(a) {
        try {
            // there is a template with duplicate code:
            InputStream inputStream = Resources.getResourceAsSteam("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();

            // statementId is hard coded:
            List<User> list = sqlSession.selectList("user.findAll");
            for (User user1 : list) {
                System.out.println(user1);
            }
            return list;
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public User findById(User user) {
        try {
            // there is a template with duplicate code:
            InputStream inputStream = Resources.getResourceAsSteam("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();

            // statementId is hard coded:
            User findUser = sqlSession.selectOne("user.findById", user);
            System.out.println(findUser);
            return findUser;
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        return null; }}Copy the code

When using the IUserDao, you need to create the IUserDao instance directly

        IUserDao iUserDao = new IUserDaoImpl();
        List<User> users = iUserDao.findAll();
        for (User user1 : users) {
            System.out.println(user1);
        }
        User user1 = iUserDao.findById(user);
        System.out.println(user1);
Copy the code

There are several problems when we use DAO, custom persistence layer framework problem analysis:

  1. The Dao layer uses the custom persistence layer framework, which has repeated code and templates throughout the operation process (loading configuration file, creating sqlSessionFactory, generating SqlSession).
  2. Statementid is hard coded. If the STATementid is changed, the specified ID cannot be found for SQL statement execution

When we use Mybatis, we can directly declare the interface class, so Mybatis how to avoid the implementation of some repeated code interface? Solution: Use proxy mode to generate the proxy implementation class of Dao layer interface. The method of proxy interface implementation ideas:

  1. Use the JDK dynamic proxy to generate proxy objects for the DAO interface classes
  2. The DAO interface method name must be the same as the ID configured in the Mapper mapping file, and the DAO interface’s full class name must be Namespce so that statementId is formed according to certain rules to obtain the MappedStatement
  3. Get the label type based on MappedStatement and call the SqlSession corresponding method

The generation of the proxy class is implemented in SqlSession, and the code logic is very simple as follows:

@Override
    public <T> T getMapper(Class
        daoClass) {
        // Use the JDK dynamic proxy to generate proxy objects for the DAO interface and return
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{daoClass}, new InvocationHandler() {
            / * * * *@paramProxy Reference * of the current proxy object@paramMethod A reference to the currently called method@paramThe argument passed by the args method *@return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // Call selectList and selectOne depending on the situation
                //1. Prepare parameter 1: statementid = namespace + ID but this cannot be obtained, so you need to define a specified rule: namespace.id = fully qualified interface name. The method name
                String id = method.getName();
                String namespace = method.getDeclaringClass().getName();
                String statementId = namespace + "." + id;
                MappedStatement mappedStatement = configuration.getMapper().get(statementId);
                if (mappedStatement.getMappedType() == MappedType.SELECT) {
                    //2. Prepare parameter 2 Object... Call selectList to determine the type of return value if it is a collection
                    // Get the method return value type
                    Type genericReturnType = method.getGenericReturnType();
                    // Check whether generic type parameterization is performed
                    if (genericReturnType instanceof ParameterizedType) {
                        List<Object> objects = selectList(statementId, args);
                        return objects;
                    }
                    return selectOne(statementId, args);
                } else if (mappedStatement.getMappedType() == MappedType.INSERT) {
                    int row = insert(statementId, args[0]);
                    return row;
                } else if (mappedStatement.getMappedType() == MappedType.UPDATE) {
                    int row = update(statementId, args[0]);
                    return row;
                } else if (mappedStatement.getMappedType() == MappedType.DELETE) {
                    int row = delete(statementId, args[0]);
                    return row;
                }
                return null; }});return (T) proxyInstance;
    }
Copy the code

This way we just need to pass in the interface class, get the proxy object, and call the method directly, saving a lot of repetitive code and the problem of hard coding statemetId

        InputStream inputStream = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        IUserDao iUserDao = sqlSession.getMapper(IUserDao.class);
        // Query all data
        List<User> users = iUserDao.findAll();
        for (User user1 : users) {
            System.out.println(user1);
        }
        // Query by condition
        User user = new User();
        user.setId(3);
        user.setUsername("jake");
        User user1 = iUserDao.findById(user);
        System.out.println(user1);
Copy the code

OK, now we have implemented a simplified version of Mybatis. In fact, when Mybatis is integrated with Spring, Spring stores Mapper’s proxy objects in the IOC container. So when we use Mapper, we just need to add an @AutoWired annotation to take the Mapper proxy object out of the IOC container and use it directly.

In addition, the rest of the new, update, delete operations are actually the same principle. SQL > select * from SqlSession;

    @Override
    public int insert(String statement, Object params) {
        return update(statement, params);
    }

    @Override
    public int update(String statement, Object params) {
        try {
            // Insert into Executor
            SimpleExecutor simpleExecutor = new SimpleExecutor();
            // Obtain Mapper mapping information stored in configuration
            MappedStatement mappedStatement = configuration.getMapper().get(statement);
            // Perform the update operation
            int row = simpleExecutor.update(configuration, mappedStatement, params);
            return row;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public int delete(String statement, Object params) {
        return update(statement, params);
    }
Copy the code

Executor implements concrete SQL execution:

 @Override
    public int update(Configuration configuration, MappedStatement mappedStatement, Object params) {
        try {
            // Execute the JDBC code
            // 1. Register the driver and obtain the connection
            Connection connection = configuration.getDataSource().getConnection();
            // 2. Obtain the SQL statement
            // select * from user where id = #{id} and username=#{username}
            // The JDBC recognized placeholder can only be? Why use #{}? You can find the value of the attribute that passes the parameter entity type based on the parameter name inside
            String sql = mappedStatement.getSql();
            Select * from user where id =? And the username =?
            // We need to parse the value of #{} to find the corresponding attribute value of the entity object to add
            BoundSql boundSql = getBoundSql(sql);
            // 3. Obtain the prepared object preparedStatement and pass the parsed SQL
            PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
            // The full class name of the parameter
            String parameterType = mappedStatement.getParameterType();
            // Get the Class of the argument
            if(parameterType ! =null) { Class<? > parameterClass = getClassType(parameterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappingList.get(i);
                    String content = parameterMapping.getContent();
                    // Reflection gets the value of the attribute in the entity object based on the parameter name
                    Field field = parameterClass.getDeclaredField(content);
                    // Violent access
                    field.setAccessible(true);
                    Object o = field.get(params);
                    // Set parameter values
                    preparedStatement.setObject(i + 1, o); }}/ / SQL execution
            int row = preparedStatement.executeUpdate();
            return row;
        } catch (Exception throwables) {
            throwables.printStackTrace();
        }
        return 0;
    }
Copy the code

The next article, according to the persistence layer framework design ideas, to analyze Mybatis source code will be easier to understand.