mybatis源码学习之自定义持久层框架

mybatis源码学习之自定义持久层框架

  • 学习目标
    • jdbc代码回顾,到底出现了哪些问题
    • 如何自定义一个持久层框架
      • 使用端
        • 核心配置文件
        • 映射配置文件
      • 项目本身(对jdbc代码的一个封装处理)
        • 加载配置文件
        • 创建容器对象
        • 解析配置文件
        • 创建会话工厂接口以及实现类
        • 创建会话接口以及实现类
        • 创建一个执行器以及实现类
        • 剩余代码文件
      • 测试
      • 回顾
    • 项目优化
      • 动态代理
        • 创建dao层
        • 实现动态代理
      • 回到测试
  • 学习收货

学习目标

已经有jdbc了,为什么会出现mybatis等各种持久层框架
怎样自定义一个持久层框架
通过自定义持久层框架深入理解mybatis源码

jdbc代码回顾,到底出现了哪些问题

Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try{
     
    //1 加载数据库驱动
    Class.forName("com.mysql.jdbc.Driver");
    //2 获取数据库连接
    connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis_test?characterEncoding=utf-8", "root", "123456");
    //3 定义sql语句
    String sql = "select * from t_user where user_name = ?";
    //4 参数处理
    preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setString(1,"杰克");
    //5 获取结果集
    resultSet = preparedStatement.executeQuery();
    while (resultSet.next()){
     
        Long userId = resultSet.getLong("user_id");
        String userName = resultSet.getString("user_name");
        User user = new User();
        user.setUserId(userId);
        user.setUserName(userName);
        System.out.println(user);
    }
    //6 封装结果集
}catch (Exception e){
     
    e.printStackTrace();
}finally {
     
    // 7 释放
    try{
     
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }catch (SQLException e){
     
        e.printStackTrace();
    }
}

通过观察jdbc代码我们找到了问题以及解决办法

问题 解决办法
数据库配置信息以及sql存在硬编码 通过配置文件解决
jdbc放在持久层会被反复调用。频繁创建释放数据库链接 连接池解决
手动封装结果集,繁琐 反射内省解决

如何自定义一个持久层框架

将持久层框架看做一个项目,那么只需要考虑两点:
1使用端 2项目本身的实现

项目目的:封装jdbc并解决其产生的问题

使用端

提供两个配置信息:

  • 数据库配置信息(核心配置):命名mapper-config.xml
    • 存放数据库的配置信息,存放mapper.xml的路径

为何要存放mapper.xml的路径?记住这个问题

  • sql配置信息(映射配置):*Mapper.xml
    • 存放sql的配置信息

核心配置文件

<!-- 创建一个父节点 -->
<configuration>

    <!-- 数据库配置信息-->
    <dataSource>
        <property name="driver" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbc" value="jdbc:mysql:///mybatis_test"></property>
        <property name="name" value="root"></property>
        <property name="password" value="123456"></property>
    </dataSource>

    <!-- 存放mapper.xml的全路径 -->
    <mapper resource="mappers/UserMapper.xml"></mapper>
</configuration>

映射配置文件

<!-- namespace 用于区分多个mapper -->
<mapper namespace="user">

    <!-- namespace.id 组成sql的唯一标识  叫做statementId -->
    <!-- resultType 返回类型  查询结果是什么,我们这边是引用类型User
        那么就要给出User的全路径以供后续通过反射完成结果集的一个映射-->
    <select id="selectList" resultType="com.hen84.entity.User">
        select * from t_user
    </select>

    <!-- paramterType   参数类型,这里是多个参数所以是引用类型,同理给出全路径-->
    <!-- 定义一个#{
     } 用来获取到传入的参数是什么 然后将映射到的参数传递进去 -->
    <select id="selectOne" resultType="com.hen84.entity.User" paramterType="com.hen84.entity.User">
        select * from t_user
        where user_id = #{
     userId}
        and user_name = #{
     userName}
    </select>
</mapper>

项目本身(对jdbc代码的一个封装处理)

  • 加载配置文件
    • 创建一个Resources类,方法InputSteam getResourceAsSteam(String path)
      根据配置文件的路径,加载配置文件成字节输入流,存放到内存中
    • 回到上面问题为何要存放mapper.xml的路径?
      Mapper.xml这个配置,需求越多文件也就越多,我们没有必要多次调用 getResourceAsSteam来加载Mapper.xml。所以存放到mapper-config.xml中,一次加载全部获取。
  • 创建容器对象
    • 存放的就是对配置文件解析出来的内容
      Configuration:核心配置类:存放mapper-config.xml解析出来的内容
      MappedStatement:映射配置类存放*Mapper.xml解析出来的内容
  • 解析配置文件:
    • 利用两个模式,建造者模式以及工厂模式
      创建一个构建者类:SqlSessionFactoryBuilder 方法build(InputSteam in)
    • 目的:生产sqlSession 会话对象
    • 过程:使用dom4j解析配置文件。将解析的内容封装到容器对象中,创建一个工厂SqlSessionFactory,生产sqlSession
  • 创建会话工厂接口以及实现类
    • SqlSessionFactory
    • DefaultSqlSessionFactory
    • 方法:openSession() 生产sqlSession
  • 创建会话接口以及实现类
    • SqlSession
    • DefaultSession
    • 方法(对数据库的操作):
      • selectList();
      • selectOne();
      • update();
      • delete();
  • 创建一个执行器以及实现类
    • Executor
    • SimpleExecutor
    • 方法query(Configuration,MappedStatement,Object… params)执行jdbc代码
    • Object… params 请求参数,无法确定参数数量,所以为可变

加载配置文件

package com.hen84.io;

import java.io.InputStream;

public class Resources {
     
    
    // 传入配置文件的路径,将其解析成输入流存到内存中
    public static InputStream getResourceAsSteam(String path){
     
        InputStream inputStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return inputStream;
    }
}

创建容器对象

package com.hen84.entity;


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

// 用来存放对mybatis-config.xml文件的解析
public class Configuration {
     

    // 数据库配置信息
    DataSource dataSource;

    // key: statementId :namespace.id 例如 user.selectList
    // value: 封装好的mappedStatement对象
    Map<String,MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();
    
}
package com.hen84.entity;

// 用来存放对*Mapper.cml文件的解析
public class MappedStatement {
     

    // id标识
    private String id;

    // 返回值类型
    private String resultType;

    // 参数类型
    private String paramterType;

    // sql语句
    private String sql;
}

解析配置文件

package com.hen84.test;

import com.hen84.io.Resources;
import com.hen84.sqlSession.SqlSessionFactory;
import com.hen84.sqlSession.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class Test {
     

    public void mybatisTest(){
     
        try{
     
            // 加载配置文件成字节输入流,存放到内存中
            InputStream inputStream = Resources.getResourceAsSteam("mybatis-config.xml");
            // 创建一个工厂SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.builder(inputStream);
        }catch (Exception e){
     

        }
    }
}

package com.hen84.sqlSession;

import com.hen84.config.XMLConfigBuilder;
import com.hen84.entity.Configuration;
import org.dom4j.DocumentException;

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

// 创建一个SqlSessionFactory构造器
public class SqlSessionFactoryBuilder {
     


    public static SqlSessionFactory builder(InputStream inputStream) throws DocumentException, PropertyVetoException {
     

        // 使用dom4j解析配置文件。将解析的内容封装到容器对象中
        // 将此过程封装
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);


        // 创建一个工厂SqlSessionFactory对象
        return null;
    }
}

package com.hen84.config;

import com.hen84.entity.Configuration;
import com.hen84.io.Resources;
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 XMLConfigBuilder {
     

    private Configuration configuration;


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

    // 使用dom4j对核心配置文件进行解析,封装到configuration中
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
     

        // dom4j将配置文件解析成一个document
        Document document = new SAXReader().read(inputStream);
        // getRootElement 拿到的就是 mybatis-config.xml中的父节点
        Element rootElement = document.getRootElement();
        // selectNodes 全局查询property节点 返回一个list  这里拿到的就是数据库配置信息
        List<Element> propertyList = rootElement.selectNodes("//property");
        // 创建一个Properties 用来存放拿到的数据库配置信息
        Properties properties = new Properties();
        for (Element element : propertyList) {
     
            // name value 标签中的属性
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }
        // 使用 c3p9 连接池
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driver"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbc"));
        comboPooledDataSource.setUser(properties.getProperty("user"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));

        // 核心配置信息存放到其容器中
        configuration.setDataSource(comboPooledDataSource);


        // 解析mapper.xml
        // 拿到路径
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
     
            String mapperResource = element.attributeValue("resource");
            // 获取字节输入流
            InputStream mapperAsSteam = Resources.getResourceAsSteam(mapperResource);
            // 再一次封装解析 传入已经解析好核心配置信息的configuration对象
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(mapperAsSteam);
        }
        return configuration;
    }
}

package com.hen84.config;

import com.hen84.entity.Configuration;
import com.hen84.entity.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;
import java.util.Map;

public class XMLMapperBuilder {
     

    private Configuration configuration;


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

    // 使用dom4j对mapper配置文件进行解析,封装到configuration的MappedStatementMap中
    public void parse(InputStream inputStream) throws DocumentException {
     

        Document document = new SAXReader().read(inputStream);
        // 此处拿到的就是 UserMapper.xml中的标签
        Element rootElement = document.getRootElement();
        // 拿到namespace唯一属性值
        String nameSpace = rootElement.attributeValue("namespace");
        // 获取下面的所有select节点返回list
        List<Element> selectList = rootElement.selectNodes("//select");
        for (Element element : selectList) {
     
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterTyoe = element.attributeValue("paramterTyoe");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setSql(sqlText);
            // namespaces.id 组成sql的唯一标识  叫做statementId
            String statementId =  nameSpace + "." + id;
            // 将拿到的mappedStatement存放到configuration的map中 statementId 就是唯一标识
            configuration.getMappedStatementMap().put(statementId,mappedStatement);
        }
    }
}

创建会话工厂接口以及实现类

package com.hen84.sqlSession;

// 创建一个SqlSession工厂 生产SqlSession
public interface SqlSessionFactory {
     

	// 生产sqlSession
    public SqlSession openSession();
}

package com.hen84.sqlSession;

import com.hen84.entity.Configuration;

// 会话工厂接口的实现类
public class DefaultSqlSessionFactory implements SqlSessionFactory {
     

    private Configuration configuration;

    // 创建会话工厂实例时将configuration带入
    public DefaultSqlSessionFactory(Configuration configuration) {
     
        this.configuration = configuration;
    }

    // 生产sqlSession
    public SqlSession openSession() {
     
        return new DefaultSqlSession();
    }
}

创建会话接口以及实现类

package com.hen84.sqlSession;

// 会话接口 接口有对数据库的CRUD功能
public interface SqlSession {
     
}

package com.hen84.sqlSession;


// 会话接口的实现类
public class DefaultSqlSession implements SqlSession {
     
}

创建一个执行器以及实现类

package com.hen84.sqlSession;

import com.hen84.entity.Configuration;
import com.hen84.entity.MappedStatement;
import java.util.List;

// 执行器
public interface Executor {
     

    // 执行方法 configuration配置信息 mappedStatement 映射文件信息 params可变参数
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
}

package com.hen84.sqlSession;

import com.hen84.config.BoundSql;
import com.hen84.entity.Configuration;
import com.hen84.entity.MappedStatement;
import com.hen84.utils.GenericTokenParser;
import com.hen84.utils.ParameterMapping;
import com.hen84.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;

public class SimpleExecutor implements Executor {
     

    // 执行sql
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
     
        // 获取数据库连接
        Connection connection = configuration.getDataSource().getConnection();

        // 获取sql 语句
        String sql = mappedStatement.getSql();
        // 转换sql 语句 对#{}的值解析存储
        BoundSql boundSql = getBoundSql(sql);
        // 通过 connection的prepareStatement方法 获取预处理对象 preparedStatement
        // 参数就是我们已经解析好的sql
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
        // 获取到参数类型的全路径
        String paramterType = mappedStatement.getParamterType();
        // 设置参数 params就是我们已经实例好的User
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        for (int i = 0 ; i < parameterMappings.size(); i++){
     
            ParameterMapping parameterMapping = parameterMappings.get(i);
            // content 就是#{}里面的值
            String content = parameterMapping.getContent();
            // 通过反射,完成参数的设置
            Class<?> paramterTypeClass = getClassType(paramterType);
            // 获取到属性对象
            Field declaredField = paramterTypeClass.getDeclaredField(content);
            // 暴力访问
            declaredField.setAccessible(true);
            // 拿到参数对象
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i+1,o);
        }

        // 执行sql 结果封装在ResultSet中
        ResultSet resultSet = preparedStatement.executeQuery();
        ArrayList<Object> objects = new ArrayList<Object>();


        String resultType = mappedStatement.getResultType();
        Class<?> resultClass = getClassType(resultType);
        // 封装结果集
        while (resultSet.next()){
     
            // 获取元数据
            Object resultObject = resultClass.newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            for(int i = 1; i <= metaData.getColumnCount(); i++){
     
                // 获取字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object object = resultSet.getObject(columnName);
                // 通过反射 完成实体类和数据库表的映射   camelName 驼峰式命名下划线转换
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(camelName(columnName),resultClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // resultObject 类的实例 object 字段的值
                writeMethod.invoke(resultObject, object);
            }
            objects.add(resultObject);
        }

        return (List<E>) objects;
    }

    // 解析sql语句 直接使用工具类 完成两个操作
    // 1 将#{}转化为?  2将#{}的值进行存储
    public BoundSql getBoundSql(String sql){
     
        // 标记处理类 : 完成对占位符的解析处理工作
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        /**
         * 标记解析器
         *  params: openToken 开始标记 也就是#{
         *          closeToken 结束标记也就是 }
         *          TokenHandler
         */
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", tokenHandler);
        // parse 将我们的sql 传入 最终返回一个解析之后的sql
        String parse = genericTokenParser.parse(sql);
        // getParameterMappings()获取道德ParameterMapping就是#{}里面的值的一个容器
        // 比如 #{userId} 以及 #{userName}
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();

        return new BoundSql(parse,parameterMappings);
    }

    // 通过反射,获取到Class
    public Class<?> getClassType(String paramterType) throws ClassNotFoundException {
     
        if(paramterType != null) return Class.forName(paramterType);
        throw new RuntimeException("ClassNotFoundException:" + paramterType);
    }


    /**
     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。
     * 例如:HELLO_WORLD->HelloWorld
     * @param name 转换前的下划线大写方式命名的字符串
     * @return 转换后的驼峰式命名的字符串
     */
    public static String camelName(String name) {
     
        StringBuilder result = new StringBuilder();
        // 快速检查
        if (name == null || name.isEmpty()) {
     
            // 没必要转换
            return "";
        } else if (!name.contains("_")) {
     
            // 不含下划线,仅将首字母小写
            return name.substring(0, 1).toLowerCase() + name.substring(1);
        }
        // 用下划线将原始字符串分割
        String camels[] = name.split("_");
        for (String camel :  camels) {
     
            // 跳过原始字符串中开头、结尾的下换线或双重下划线
            if (camel.isEmpty()) {
     
                continue;
            }
            // 处理真正的驼峰片段
            if (result.length() == 0) {
     
                // 第一个驼峰片段,全部字母都小写
                result.append(camel.toLowerCase());
            } else {
     
                // 其他的驼峰片段,首字母大写
                result.append(camel.substring(0, 1).toUpperCase());
                result.append(camel.substring(1).toLowerCase());
            }
        }
        return result.toString();
    }

}

剩余代码文件

package com.hen84.config;

import com.hen84.utils.ParameterMapping;

import java.util.ArrayList;
import java.util.List;

// 用来存放解析之后的sql 也就是将#{} 转化为?之后的sql
public class BoundSql {
     

    // 解析之后的sql
    private String sql;

    // #{}里面的值
    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

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

    public String getSql() {
     
        return sql;
    }

    public void setSql(String sql) {
     
        this.sql = sql;
    }

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

    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
     
        this.parameterMappings = parameterMappings;
    }
}

/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.hen84.utils;

/**
 * @author Clinton Begin
 */
public class GenericTokenParser {
     

  private final String openToken; //开始标记
  private final String closeToken; //结束标记
  private final TokenHandler handler; //标记处理器

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

  /**
   * 解析${}和#{}
   * @param text
   * @return
   * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
   * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
   */
  public String parse(String text) {
     
    // 验证参数问题,如果是null,就返回空字符串。
    if (text == null || text.isEmpty()) {
     
      return "";
    }

    // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
    int start = text.indexOf(openToken, 0);
    if (start == -1) {
     
      return text;
    }

   // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
    // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
     
     // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
      if (start > 0 && src[start - 1] == '\\') {
     
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
     
        //重置expression变量,避免空指针或者老数据干扰。
        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 (end > offset && src[end - 1] == '\\') {
     //如果结束标记前面有转义字符时
            // 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 {
     //不存在转义字符,即需要作为参数进行处理
            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 {
     
          //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
          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);
    }
    return builder.toString();
  }
}

package com.hen84.utils;

// 用来存储#{} 里面的值的存储
public class ParameterMapping {
     

    private String content;

    public ParameterMapping(String content) {
     
        this.content = content;
    }

    public String getContent() {
     
        return content;
    }

    public void setContent(String content) {
     
        this.content = content;
    }
}

package com.hen84.utils;

import java.util.ArrayList;
import java.util.List;




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

	// context是参数名称 #{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() {
     
		return parameterMappings;
	}

	public void setParameterMappings(List<ParameterMapping> parameterMappings) {
     
		this.parameterMappings = parameterMappings;
	}

}

/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.hen84.utils;

/**
 * @author Clinton Begin
 */
public interface TokenHandler {
     
  String handleToken(String content);
}


测试

package com.hen84.test;

import com.hen84.entity.User;
import com.hen84.io.Resources;
import com.hen84.sqlSession.SqlSession;
import com.hen84.sqlSession.SqlSessionFactory;
import com.hen84.sqlSession.SqlSessionFactoryBuilder;

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

public class Test {
     

    public static void main(String[] args) {
     
        try{
     
            // 加载配置文件成字节输入流,存放到内存中
            InputStream inputStream = Resources.getResourceAsSteam("mybatis-config.xml");
            // 创建一个工厂SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.builder(inputStream);
            // 拿到会话对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 创建一个User实体 用于传参
            User user = new User();
            user.setUserId(2L);
            user.setUserName("胡歌");
            // 调用查询方法
            List<User> resultUser =  sqlSession.selectList("user.selectList",user);
            // 调用单个查询方法
            User resultUserOne = sqlSession.selectOne("user.selectOne", user);

            System.out.println(resultUser);
            System.out.println(resultUserOne);
        }catch (Exception e){
     
            e.printStackTrace();
        }
    }
}

执行结果

[User{
     userId=1, userName='彭于晏', userPhone='10000009282'}, User{
     userId=2, userName='胡歌', userPhone='1999872625384'}]
User{
     userId=2, userName='胡歌', userPhone='1999872625384'}

回顾

到此算是大致OK了,但是还是会有两个问题

  • statementId还是存在硬编码

项目优化

优化过程 :动态代理返回代理对象
由代理对象去解决我们的硬编码问题

动态代理

创建dao层

package com.hen84.dao;

import com.hen84.entity.User;

import java.util.List;

public interface UserMapper {
     

    List<User> selectList();

    User selectOne(User user);
}

实现动态代理

我们在SqlSession接口以及实现类加入getMapper的方法和实现

	// 返回动态代理的实现类  参数就是我们的dao
    public <T> T getMapper(Class<?> mapperClass);
@Override
    public <T> T getMapper(Class<?> mapperClass) {
     
        // 使用JDK 动态代理 来为接口生成代理对象
        /**
         *
         * newProxyInstance
         * 参数:类加载器
         *      Class数组
         *      InvocationHandler接口
         * InvocationHandler接口实现里面有一个invoke方法
         *
         * invoke
         * 参数: proxy 当前代理对象
         *      method 当前被调用方法
         *      args 传递的参数
         *
          */

        Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{
     mapperClass}, new InvocationHandler() {
     
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     
                // 根据不同情况来调用selectList 或者selectOne
                //准备参数
                // 1、statementId
                /**
                 * 这里我们考虑一个问题
                 * statementId 也就是mapper.xml中的namespace.id 我们目前没法拿到
                 * 于是我们更改namespace的值为我们接口的全路径 例如com.hen84.dao.UserMapper
                 * id  我们这里一定要和方法名保持一致
                 */
                // methodName 也就是id
                String methodName = method.getName();
                // className 也就是namespace
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;
                // 2、params 就是args

                // 如何来确定调用的是selectList 还是selectOne ? 返回值类型
                // 获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                // 判断是否进行了泛型类型参数化
                // 泛型类型参数化:当前返回值类型是否含有泛型   有就是List
                if(genericReturnType instanceof ParameterizedType){
     
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }
                return selectOne(statementId,args);
            }
        });

        return (T) o;
    }

回到测试

public static void main(String[] args) {
     
        try{
     
            // 加载配置文件成字节输入流,存放到内存中
            InputStream inputStream = Resources.getResourceAsSteam("mybatis-config.xml");
            // 创建一个工厂SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.builder(inputStream);
            // 拿到会话对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 创建一个User实体 用于传参
            User user = new User();
            user.setUserId(2L);
            user.setUserName("胡歌");

            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> users = userMapper.selectList();
            System.out.println(users);
            User user1 = userMapper.selectOne(user);
            System.out.println(user1);
        }catch (Exception e){
     
            e.printStackTrace();
        }
    }

打印结果

[User{
     userId=1, userName='彭于晏', userPhone='10000009282'}, User{
     userId=2, userName='胡歌', userPhone='1999872625384'}]
User{
     userId=2, userName='胡歌', userPhone='1999872625384'}

OK 到此自定义持久层框架就结束了
源码在最后

学习收货

1、通过自定义持久层框架加深了对mybatis源码的理解
2、所有的持久层框架都是对JDBC的一个高级封装
3、mybatis中的部分重要接口的作用
4、mybatis设计模式的一个深入理解

源码:
链接: https://pan.baidu.com/s/1Rk0CzAnVol11O3iIHlB3UA 密码: o7s9

你可能感兴趣的:(mybatis源码学习,mybatis,设计模式,源码)