手写MyBatis2.0附带Plugin功能(增强版本)

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 手写MyBatis2.0附带Plugin功能(增强版本)

基于上一篇博客,手写MyBatis1.0末尾提出的几个不足之处与需要新增的地方,这篇博客将完善之前的MyBatis1.0版本,升级为2.0版本~将会新增的功能:

  1. 加入Plugin插件功能。
  2. 加入缓存功能。
  3. 分解Executor指责,分出各个类使其符合单一职责原则。
  4. 使用注解灵活配置mapper与实体。

代码中注释打的很清楚了,文字只简单描述一下。

**源码链接(包括v1.0与v2.0): **

首先是SqlSession的改动,只改了构造器,将持有的executor变为动态新增的。

12345678
    /**     * 用构造器将两个对象形成关系     */    public CustomSqlSession(CustomConfiguration configuration) {        this.configuration = configuration;        //这里需要决定是否开启缓存,则从Configuraton中判断是否需要缓存,创建对应Executor        this.executor = configuration.newExecutor();    }

/**
 * 用构造器将两个对象形成关系
 */
public CustomSqlSession(CustomConfiguration configuration) {
    this.configuration = configuration;
    //这里需要决定是否开启缓存,则从Configuraton中判断是否需要缓存,创建对应Executor
    this.executor = configuration.newExecutor();
}

为了客户端方便调用,做了一个SqlSessionFactory负责接收信息初始化Configuration与创建SqlSession

1234567891011121314151617181920212223242526272829303132
/** * @description: 创建SqlSession工厂 * @author: linyh * @create: 2018-11-01 17:49 **/public class SqlSessionFactory {     private CustomConfiguration configuration;     /**     * 以下build方法将初始化Factory的属性Configuration,具体工作在Configuration构造器中完成     */    public SqlSessionFactory build(String mapperPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {        return this.build(mapperPath, null, false);    }     public SqlSessionFactory build(String mapperPath, String[] pluginPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {        return this.build(mapperPath, pluginPath, false);    }     public SqlSessionFactory build(String mapperPath, String[] pluginPath, boolean enableCache) throws ClassNotFoundException, InstantiationException, IllegalAccessException {        configuration = new CustomConfiguration(mapperPath, pluginPath, enableCache);        return this;    }     /**     * 根据配置信息(Configuration)获取对应的SqlSession     */    public CustomSqlSession openSqlSession(){        return new CustomSqlSession(configuration);    }}

/**

  • @description: 创建SqlSession工厂
  • @author: linyh
  • @create: 2018-11-01 17:49

**/
public class SqlSessionFactory {


private CustomConfiguration configuration;

/**
 * 以下build方法将初始化Factory的属性Configuration,具体工作在Configuration构造器中完成
 */
public SqlSessionFactory build(String mapperPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    return this.build(mapperPath, null, false);
}

public SqlSessionFactory build(String mapperPath, String[] pluginPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    return this.build(mapperPath, pluginPath, false);
}

public SqlSessionFactory build(String mapperPath, String[] pluginPath, boolean enableCache) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    configuration = new CustomConfiguration(mapperPath, pluginPath, enableCache);
    return this;
}

/**
 * 根据配置信息(Configuration)获取对应的SqlSession
 */
public CustomSqlSession openSqlSession(){
    return new CustomSqlSession(configuration);
}

}

然后是Configuration,值得一提的是新增扫描包下注解获取运行时信息功能,新增了插件功能,这里只做了对executor的拦截,所以新增一个创建executor方法,实现动态选择executor。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
/** * @description: * @author: linyh * @create: 2018-10-31 16:32 **/public class CustomConfiguration {     public static final MapperRegistory mapperRegistory = new MapperRegistory();    public static final MapString, String mappedStatements = new HashMap();     private CustomInterceptorChain interceptorChain = new CustomInterceptorChain();    private boolean enableCache = false;    private ListClass? mapperList = new ArrayList();    private ListString classPaths = new ArrayList();     /**     * 初始化时Configuration加载所有Mapper信息、plugin信息、缓存是否开启信息     */    public CustomConfiguration(String mapperPath, String[] pluginPath, boolean enableCache) throws ClassNotFoundException, IllegalAccessException, InstantiationException {        //扫描mapper路径,将必要的mapper信息存入mapperRegistory与mapperStatements        scanPackage(mapperPath);        for (Class? mapper : mapperList) {            //当类为接口时视其为mapper,开始解析它            //Myabtis中判断是否为mapper还用到了isIndependent的方法判断,较为复杂,这里简化,体现思想即可            if (mapper.isInterface()) {                parsingClass(mapper);            }        }        if (pluginPath != null) {            //遍历plugin路径,初始化plugin并放入list中            for (String plugin : pluginPath) {                Interceptor interceptor =  (Interceptor) Class.forName(plugin).newInstance();                interceptorChain.addInterceptor(interceptor);            }        }        //设置缓存是否开启        this.enableCache = enableCache;    }     /**     * MapperProxy根据statementName查找是否有对应SQL     */    public boolean hasStatement(String statementName) {        return mappedStatements.containsKey(statementName);    }     /**     * MapperProxy根据statementID获取SQL     */    public String getMappedStatement(String id) {        return mappedStatements.get(id);    }     public T T getMapper(ClassT clazz, CustomSqlSession sqlSession) {        return mapperRegistory.getMapper(clazz, sqlSession);    }     /**     * 创建一个Executor(因为加入了plugin功能,需要判断是否创建带plugin的executor)     */    public CustomExecutor newExecutor() {        CustomExecutor executor = createExecutor();        if (interceptorChain.hasPlugin()) {            return (CustomExecutor)interceptorChain.pluginAll(executor);        }        return executor;    }     /**     * 创建一个Executor(需要判断是否创建带缓存功能的executor)     */    private CustomExecutor createExecutor(){        if (enableCache) {            return new CacheExecutor(new SimpleExecutor());        }        return new SimpleExecutor();    }     /**     * 解析类中的注解     */    private void parsingClass(Class? mapper) {        //如有Pojo注解,则可以获取到实体类的信息        if (mapper.isAnnotationPresent(Pojo.class)) {            for (Annotation annotation : mapper.getAnnotations()) {                if (annotation.annotationType().equals(Pojo.class)) {                    //将mapper与实体类信息注册进mapperRegistory中                    mapperRegistory.addMapper(mapper, ((Pojo)annotation).value());                }            }        }         Method[] methods = mapper.getMethods();        for (Method method : methods) {            //TODO 新增Update、Delete、Insert注解            //如果有Select注解就解析SQL语句            if (method.isAnnotationPresent(Select.class)) {                for (Annotation annotation : method.getDeclaredAnnotations()) {                    if (annotation.annotationType().equals(Select.class)) {                        //将方法名与SQL语句注册进mappedStatements中                        mappedStatements.put(method.getDeclaringClass().getName() + "." +method.getName(),                                ((Select) annotation).value());                    }                }            }        }    }     /**     * 扫描包名,获取包下的所有.class文件     */    private void scanPackage(String mapperPath) throws ClassNotFoundException {        String classPath = TestMybatis.class.getResource("/").getPath();        mapperPath = mapperPath.replace(".", File.separator);        String mainPath = classPath + mapperPath;        doPath(new File(mainPath));        for (String className : classPaths) {            className = className.replace(classPath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");            Class? clazz = Class.forName(className);            mapperList.add(clazz);        }    }     /**     * 该方法会得到所有的类,将类的绝对路径写入到classPaths中     */    private void doPath(File file) {        if (file.isDirectory()) {//文件夹            //文件夹我们就递归            File[] files = file.listFiles();            for (File f1 : files) {                doPath(f1);            }        } else {//标准文件            //标准文件我们就判断是否是class文件            if (file.getName().endsWith(".class")) {                //如果是class文件我们就放入我们的集合中。                classPaths.add(file.getPath());            }        }    }}

/**

  • @description:
  • @author: linyh
  • @create: 2018-10-31 16:32

**/
public class CustomConfiguration {


public static final MapperRegistory mapperRegistory = new MapperRegistory();
public static final MapString, String mappedStatements = new HashMap();

private CustomInterceptorChain interceptorChain = new CustomInterceptorChain();
private boolean enableCache = false;
private ListClass? mapperList = new ArrayList();
private ListString classPaths = new ArrayList();

/**
 * 初始化时Configuration加载所有Mapper信息、plugin信息、缓存是否开启信息
 */
public CustomConfiguration(String mapperPath, String[] pluginPath, boolean enableCache) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    //扫描mapper路径,将必要的mapper信息存入mapperRegistory与mapperStatements
    scanPackage(mapperPath);
    for (Class? mapper : mapperList) {
        //当类为接口时视其为mapper,开始解析它
        //Myabtis中判断是否为mapper还用到了isIndependent的方法判断,较为复杂,这里简化,体现思想即可
        if (mapper.isInterface()) {
            parsingClass(mapper);
        }
    }
    if (pluginPath != null) {
        //遍历plugin路径,初始化plugin并放入list中
        for (String plugin : pluginPath) {
            Interceptor interceptor =  (Interceptor) Class.forName(plugin).newInstance();
            interceptorChain.addInterceptor(interceptor);
        }
    }
    //设置缓存是否开启
    this.enableCache = enableCache;
}

/**
 * MapperProxy根据statementName查找是否有对应SQL
 */
public boolean hasStatement(String statementName) {
    return mappedStatements.containsKey(statementName);
}

/**
 * MapperProxy根据statementID获取SQL
 */
public String getMappedStatement(String id) {
    return mappedStatements.get(id);
}

public  T getMapper(Class clazz, CustomSqlSession sqlSession) {
    return mapperRegistory.getMapper(clazz, sqlSession);
}

/**
 * 创建一个Executor(因为加入了plugin功能,需要判断是否创建带plugin的executor)
 */
public CustomExecutor newExecutor() {
    CustomExecutor executor = createExecutor();
    if (interceptorChain.hasPlugin()) {
        return (CustomExecutor)interceptorChain.pluginAll(executor);
    }
    return executor;
}

/**
 * 创建一个Executor(需要判断是否创建带缓存功能的executor)
 */
private CustomExecutor createExecutor(){
    if (enableCache) {
        return new CacheExecutor(new SimpleExecutor());
    }
    return new SimpleExecutor();
}

/**
 * 解析类中的注解
 */
private void parsingClass(Class? mapper) {
    //如有Pojo注解,则可以获取到实体类的信息
    if (mapper.isAnnotationPresent(Pojo.class)) {
        for (Annotation annotation : mapper.getAnnotations()) {
            if (annotation.annotationType().equals(Pojo.class)) {
                //将mapper与实体类信息注册进mapperRegistory中
                mapperRegistory.addMapper(mapper, ((Pojo)annotation).value());
            }
        }
    }

    Method[] methods = mapper.getMethods();
    for (Method method : methods) {
        //TODO 新增Update、Delete、Insert注解
        //如果有Select注解就解析SQL语句
        if (method.isAnnotationPresent(Select.class)) {
            for (Annotation annotation : method.getDeclaredAnnotations()) {
                if (annotation.annotationType().equals(Select.class)) {
                    //将方法名与SQL语句注册进mappedStatements中
                    mappedStatements.put(method.getDeclaringClass().getName() + "." +method.getName(),
                            ((Select) annotation).value());
                }
            }
        }
    }
}

/**
 * 扫描包名,获取包下的所有.class文件
 */
private void scanPackage(String mapperPath) throws ClassNotFoundException {
    String classPath = TestMybatis.class.getResource("/").getPath();
    mapperPath = mapperPath.replace(".", File.separator);
    String mainPath = classPath + mapperPath;
    doPath(new File(mainPath));
    for (String className : classPaths) {
        className = className.replace(classPath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");
        Class? clazz = Class.forName(className);
        mapperList.add(clazz);
    }
}

/**
 * 该方法会得到所有的类,将类的绝对路径写入到classPaths中
 */
private void doPath(File file) {
    if (file.isDirectory()) {//文件夹
        //文件夹我们就递归
        File[] files = file.listFiles();
        for (File f1 : files) {
            doPath(f1);
        }
    } else {//标准文件
        //标准文件我们就判断是否是class文件
        if (file.getName().endsWith(".class")) {
            //如果是class文件我们就放入我们的集合中。
            classPaths.add(file.getPath());
        }
    }
}

}

然后是三个简单的注解创建。

1234567891011121314151617181920212223242526272829303132333435
/** * @Author:linyh * @Date: 2018/11/2 10:38 * @Modified By: */@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Method {    String value();} /** * @Author:linyh * @Date: 2018/11/1 17:40 * @Modified By: */@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Pojo {    Class? value();} /** * @Author:linyh * @Date: 2018/11/1 17:37 * @Modified By: */@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Select {    String value();}

/**

  • @Author:linyh
  • @Date: 2018/11/2 10:38
  • @Modified By:

*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Method {
String value();
}

/**

  • @Author:linyh
  • @Date: 2018/11/1 17:40
  • @Modified By:

*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Pojo {
Class? value();
}

/**

  • @Author:linyh
  • @Date: 2018/11/1 17:37
  • @Modified By:

*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
String value();
}

在mapper中这样使用,为了解析到SQL语句与mapper对应的实体类。

1234567891011
/** * @Author:linyh * @Date: 2018/10/31 16:56 * @Modified By: */@Pojo(Test.class)public interface TestCustomMapper {     @Select("select * from test where id = %d")    Test selectByPrimaryKey(int id);}

/**

  • @Author:linyh
  • @Date: 2018/10/31 16:56
  • @Modified By:

*/
@Pojo(Test.class)
public interface TestCustomMapper {


@Select("select * from test where id = %d")
Test selectByPrimaryKey(int id);

}

然后是之前的默认executor,细化了职责。

123456789101112131415
/** * @description: 自定义Executor * @author: linyh * @create: 2018-10-31 17:46 **/public class SimpleExecutor implements CustomExecutor {    /**     * 这里为默认Executor,细化了责任,查询交给StatementHandler处理     */    @Override    public T T query(String statement, String parameter, Class pojo) throws IllegalAccessException, SQLException, InstantiationException, InvocationTargetException, NoSuchMethodException {        StatementHandler statementHandler = new StatementHandler();        return statementHandler.query(statement, parameter, pojo);    }}

/**

  • @description: 自定义Executor
  • @author: linyh
  • @create: 2018-10-31 17:46

/
public class SimpleExecutor implements CustomExecutor {
/

* 这里为默认Executor,细化了责任,查询交给StatementHandler处理
*/
@Override
public T query(String statement, String parameter, Class pojo) throws IllegalAccessException, SQLException, InstantiationException, InvocationTargetException, NoSuchMethodException {
StatementHandler statementHandler = new StatementHandler();
return statementHandler.query(statement, parameter, pojo);
}
}

然后是使用了装饰器模式的在默认executor的功能之上新增缓存功能的executor

123456789101112131415161718192021222324252627282930313233343536373839404142434445
/** * @description: 缓存的Executor,用了装饰器模式 * @author: linyh * @create: 2018-11-01 18:02 **/public class CacheExecutor implements CustomExecutor{     //这里的delegate为被装饰的executor    private CustomExecutor delegate;    private static final MapInteger, Object cache = new HashMap();     /**     * 装饰executor,为了在原有的executor之上增加新的功能,扩展为意图     */    public CacheExecutor(CustomExecutor delegate) {        this.delegate = delegate;    }     /**     * 修改了查询方法,扩展了缓存功能。     * 由于只实现了查询功能,所以这里的缓存处理没有清空的处理。     * 后续升级版本时需要新增缓存更新方法。     */    @Override    public T T query(String statement, String parameter, Class pojo) throws IllegalAccessException, SQLException, InstantiationException, InvocationTargetException, NoSuchMethodException {        //这里的CacheKey为缓存的关键        CacheKey cacheKey = new CacheKey();        //根据SQL语句与参数两个维度来判断每次查询是否相同        //底层原理为hashCode的计算,同一个SQL语句与同一个参数将视为同一个查询,cacheKey中的code也会一样        //在Mybatis中的cacheKey里的code维度有更多,这里简化体现思想即可        cacheKey.update(statement);        cacheKey.update(parameter);         //判断Map中是否含有根据SQL语句与参数算出来的code        if (!cache.containsKey(cacheKey.getCode())) {            //如没有就用被装饰者的查询方法,然后新增缓存            Object obj = delegate.query(statement, parameter, pojo);            cache.put(cacheKey.getCode(), obj);            return (T)obj;        }        //缓存命中        System.out.println("从缓存拿数据" + cache.get(cacheKey.getCode()));        return (T)cache.get(cacheKey.getCode());    }}

/**

  • @description: 缓存的Executor,用了装饰器模式
  • @author: linyh
  • @create: 2018-11-01 18:02

**/
public class CacheExecutor implements CustomExecutor{


//这里的delegate为被装饰的executor
private CustomExecutor delegate;
private static final MapInteger, Object cache = new HashMap();

/**
 * 装饰executor,为了在原有的executor之上增加新的功能,扩展为意图
 */
public CacheExecutor(CustomExecutor delegate) {
    this.delegate = delegate;
}

/**
 * 修改了查询方法,扩展了缓存功能。
 * 由于只实现了查询功能,所以这里的缓存处理没有清空的处理。
 * 后续升级版本时需要新增缓存更新方法。
 */
@Override
public  T query(String statement, String parameter, Class pojo) throws IllegalAccessException, SQLException, InstantiationException, InvocationTargetException, NoSuchMethodException {
    //这里的CacheKey为缓存的关键
    CacheKey cacheKey = new CacheKey();
    //根据SQL语句与参数两个维度来判断每次查询是否相同
    //底层原理为hashCode的计算,同一个SQL语句与同一个参数将视为同一个查询,cacheKey中的code也会一样
    //在Mybatis中的cacheKey里的code维度有更多,这里简化体现思想即可
    cacheKey.update(statement);
    cacheKey.update(parameter);

    //判断Map中是否含有根据SQL语句与参数算出来的code
    if (!cache.containsKey(cacheKey.getCode())) {
        //如没有就用被装饰者的查询方法,然后新增缓存
        Object obj = delegate.query(statement, parameter, pojo);
        cache.put(cacheKey.getCode(), obj);
        return (T)obj;
    }
    //缓存命中
    System.out.println("从缓存拿数据" + cache.get(cacheKey.getCode()));
    return (T)cache.get(cacheKey.getCode());
}

}

顺便贴上缓存核心实现类(核心思想是把一次查询分为几个维度去计算hashCode,相同SQL语句与相同参数的每次查询算出来的Code将是相同的,即可视为同一查询)

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
/** * @description: 缓存key值 * @author: linyh * @create: 2018-11-02 11:22 **/public class CacheKey {    //沿用MyBatis设计的默认值    private static final int DEFAULT_HASHCODE = 17;    private static final int DEFAULT_MULTIPLYER = 37;     private int hashCode;    private int count;    private int multiplyer;     public CacheKey() {        this.hashCode = DEFAULT_HASHCODE;        this.count = 0;        this.multiplyer = DEFAULT_MULTIPLYER;    }     /**     * 计算每一个传进来的Object的独有的一个code     */    public void update(Object object){        if (object != null && object.getClass().isArray()) {            int length = Array.getLength(object);            for (int i = 0; i  length; i++) {                Object element = Array.get(object, i);                doUpdate(element);            }        }else {            doUpdate(object);        }    }     public int getCode() {        return hashCode;    }     private void doUpdate(Object o) {        int baseHashCode = o == null ? 1 : o.hashCode();        count++;        baseHashCode *= count;         hashCode = multiplyer * hashCode + baseHashCode;    }}

/**

  • @description: 缓存key值
  • @author: linyh
  • @create: 2018-11-02 11:22

**/
public class CacheKey {
//沿用MyBatis设计的默认值
private static final int DEFAULT_HASHCODE = 17;
private static final int DEFAULT_MULTIPLYER = 37;


private int hashCode;
private int count;
private int multiplyer;

public CacheKey() {
    this.hashCode = DEFAULT_HASHCODE;
    this.count = 0;
    this.multiplyer = DEFAULT_MULTIPLYER;
}

/**
 * 计算每一个传进来的Object的独有的一个code
 */
public void update(Object object){
    if (object != null && object.getClass().isArray()) {
        int length = Array.getLength(object);
        for (int i = 0; i  length; i++) {
            Object element = Array.get(object, i);
            doUpdate(element);
        }
    }else {
        doUpdate(object);
    }
}

public int getCode() {
    return hashCode;
}

private void doUpdate(Object o) {
    int baseHashCode = o == null ? 1 : o.hashCode();
    count++;
    baseHashCode *= count;

    hashCode = multiplyer * hashCode + baseHashCode;
}

}

然后是负责查询工作的类

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
/** * @description: 负责查询工作 * @author: linyh * @create: 2018-11-02 10:10 **/public class StatementHandler {     private ResultSetHandler resultSetHandler = new ResultSetHandler();     /**     * 主要执行查询工作的地方     */    public T T query(String statement, String parameter, Class pojo) throws SQLException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {        Connection conn = null;         //TODO 这里需要一个ParameterHandler,然后用TypeHandler设置参数,偷懒没写TypeHandler...        statement = String.format(statement, Integer.parseInt(parameter));         try {            conn = getConnection();            PreparedStatement preparedStatement = conn.prepareStatement(statement);            preparedStatement.execute();            //查询到这里结束,映射结果的工作委派给ResultSetHandler去做            return resultSetHandler.handle(preparedStatement.getResultSet(), pojo);        } finally {            if (conn != null) {                conn.close();                conn = null;            }        }    }     private Connection getConnection() throws SQLException {        String driver = "com.mysql.jdbc.Driver";        String url = "jdbc:mysql://127.0.0.1:3306/gp?serverTimezone=UTC";        String username = "root";        String password = "admin";        Connection conn = null;        try {            Class.forName(driver); //classLoader,加载对应驱动            conn = DriverManager.getConnection(url, username, password);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException e) {            e.printStackTrace();        }        return conn;    }}

/**

  • @description: 负责查询工作
  • @author: linyh
  • @create: 2018-11-02 10:10

**/
public class StatementHandler {


private ResultSetHandler resultSetHandler = new ResultSetHandler();

/**
 * 主要执行查询工作的地方
 */
public  T query(String statement, String parameter, Class pojo) throws SQLException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    Connection conn = null;

    //TODO 这里需要一个ParameterHandler,然后用TypeHandler设置参数,偷懒没写TypeHandler...
    statement = String.format(statement, Integer.parseInt(parameter));

    try {
        conn = getConnection();
        PreparedStatement preparedStatement = conn.prepareStatement(statement);
        preparedStatement.execute();
        //查询到这里结束,映射结果的工作委派给ResultSetHandler去做
        return resultSetHandler.handle(preparedStatement.getResultSet(), pojo);
    } finally {
        if (conn != null) {
            conn.close();
            conn = null;
        }
    }
}

private Connection getConnection() throws SQLException {
    String driver = "com.mysql.jdbc.Driver";
    String url = "jdbc:mysql://127.0.0.1:3306/gp?serverTimezone=UTC";
    String username = "root";
    String password = "admin";
    Connection conn = null;
    try {
        Class.forName(driver); //classLoader,加载对应驱动
        conn = DriverManager.getConnection(url, username, password);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return conn;
}

}

负责映射结果集的类

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
/** * @description: 负责结果的映射 * @author: linyh * @create: 2018-11-02 10:10 **/public class ResultSetHandler {     /**     * 细化了executor的职责,专门负责结果映射     */    public T T handle(ResultSet resultSet, Class pojo) throws IllegalAccessException, InstantiationException, NoSuchMethodException, SQLException, InvocationTargetException {        //代替了ObjectFactory        Object pojoObj = pojo.newInstance();         //遍历pojo中每一个属性域去设置参数        if (resultSet.next()) {            for (Field field : pojoObj.getClass().getDeclaredFields()) {                setValue(pojoObj, field, resultSet);            }        }        return (T)pojoObj;    }     /**     * 利用反射给每一个属性设置参数     */    private void setValue(Object pojo, Field field, ResultSet rs) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, SQLException {        Method setMethod = pojo.getClass().getMethod("set" + firstWordCapital(field.getName()), field.getType());        setMethod.invoke(pojo, getResult(rs, field));    }     /**     * 根据反射判断类型,从ResultSet中取对应类型参数     */    private Object getResult(ResultSet rs, Field field) throws SQLException {        //TODO 这里需要用TypeHandle处理,偷懒简化了,后续升级点        Class type = field.getType();        if (Integer.class == type) {            return rs.getInt(field.getName());        }        if (String.class == type) {            return rs.getString(field.getName());        }        return rs.getString(field.getName());    }     /**     * 将一个单词首字母大写     */    private String firstWordCapital(String word){        String first = word.substring(0, 1);        String tail = word.substring(1);        return first.toUpperCase() + tail;    }}

/**

  • @description: 负责结果的映射
  • @author: linyh
  • @create: 2018-11-02 10:10

**/
public class ResultSetHandler {


/**
 * 细化了executor的职责,专门负责结果映射
 */
public  T handle(ResultSet resultSet, Class pojo) throws IllegalAccessException, InstantiationException, NoSuchMethodException, SQLException, InvocationTargetException {
    //代替了ObjectFactory
    Object pojoObj = pojo.newInstance();

    //遍历pojo中每一个属性域去设置参数
    if (resultSet.next()) {
        for (Field field : pojoObj.getClass().getDeclaredFields()) {
            setValue(pojoObj, field, resultSet);
        }
    }
    return (T)pojoObj;
}

/**
 * 利用反射给每一个属性设置参数
 */
private void setValue(Object pojo, Field field, ResultSet rs) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, SQLException {
    Method setMethod = pojo.getClass().getMethod("set" + firstWordCapital(field.getName()), field.getType());
    setMethod.invoke(pojo, getResult(rs, field));
}

/**
 * 根据反射判断类型,从ResultSet中取对应类型参数
 */
private Object getResult(ResultSet rs, Field field) throws SQLException {
    //TODO 这里需要用TypeHandle处理,偷懒简化了,后续升级点
    Class type = field.getType();
    if (Integer.class == type) {
        return rs.getInt(field.getName());
    }
    if (String.class == type) {
        return rs.getString(field.getName());
    }
    return rs.getString(field.getName());
}

/**
 * 将一个单词首字母大写
 */
private String firstWordCapital(String word){
    String first = word.substring(0, 1);
    String tail = word.substring(1);
    return first.toUpperCase() + tail;
}

}

其他的MapperProxy之类的没有什么改动,只是新增了一个实体类的属性,为了传递给结果集映射时使用。

最后介绍Plugin的实现~首先是保存所有plugin的类(此类将作为Configuration属性存放)。

12345678910111213141516171819202122232425262728293031
/** * @description: 插件链存放类 * @author: linyh * @create: 2018-11-01 17:59 **/public class CustomInterceptorChain {     private final ListInterceptor interceptors = new ArrayList();     public void addInterceptor(Interceptor interceptor){        interceptors.add(interceptor);    }     /**     * 将目标executor依此按照插件链动态代理target     * 如有多个plugin,多次代理(如b插件代理了a执行器,c插件又代理a执行器,此时执行顺序为c - b - a)     */    public Object pluginAll(Object target){        for (Interceptor interceptor : interceptors) {            target = interceptor.plugin(target);        }        return target;    }     public boolean hasPlugin(){        if (interceptors.size() == 0) {            return false;        }        return true;    }}

/**

  • @description: 插件链存放类
  • @author: linyh
  • @create: 2018-11-01 17:59

**/
public class CustomInterceptorChain {


private final ListInterceptor interceptors = new ArrayList();

public void addInterceptor(Interceptor interceptor){
    interceptors.add(interceptor);
}

/**
 * 将目标executor依此按照插件链动态代理target
 * 如有多个plugin,多次代理(如b插件代理了a执行器,c插件又代理a执行器,此时执行顺序为c - b - a)
 */
public Object pluginAll(Object target){
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

public boolean hasPlugin(){
    if (interceptors.size() == 0) {
        return false;
    }
    return true;
}

}

插件的接口

12345678910
/** * @description: * @author: linyh * @create: 2018-11-02 10:12 **/public interface Interceptor {    Object intercept(Invocation invocation) throws Throwable;     Object plugin(Object target);}

/**

  • @description:
  • @author: linyh
  • @create: 2018-11-02 10:12

**/
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;


Object plugin(Object target);

}

自定义插件实现上面的接口

12345678910111213141516171819202122232425262728
/** * @description: 测试用插件 * @author: linyh * @create: 2018-11-02 10:29 **/@Method("query")public class testPlugin implements Interceptor{    @Override    public Object intercept(Invocation invocation) throws Throwable {        String statement = (String) invocation.getArgs()[0];        String parameter = (String) invocation.getArgs()[1];        Class pojo = (Class) invocation.getArgs()[2];        System.out.println("----------plugin生效----------");        System.out.println("executor执行query方法前拦截,拦截到的Sql语句为: " + statement);        System.out.println("参数为: " + parameter + " 实体类为: " + pojo.getName());        System.out.println("-----------拦截结束-----------");        //在这里执行原executor的方法        return invocation.proceed();    }     /**     * 实际上此方法就是将此插件给target做一个动态代理     */    @Override    public Object plugin(Object target) {        return Plugin.wrap(target, this);    }}

/**

  • @description: 测试用插件
  • @author: linyh
  • @create: 2018-11-02 10:29

**/
@Method(“query”)
public class testPlugin implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
String statement = (String) invocation.getArgs()[0];
String parameter = (String) invocation.getArgs()[1];
Class pojo = (Class) invocation.getArgs()[2];
System.out.println(“———-plugin生效———-“);
System.out.println(“executor执行query方法前拦截,拦截到的Sql语句为: “ + statement);
System.out.println(“参数为: “ + parameter + “ 实体类为: “ + pojo.getName());
System.out.println(“———–拦截结束———–”);
//在这里执行原executor的方法
return invocation.proceed();
}


/**
 * 实际上此方法就是将此插件给target做一个动态代理
 */
@Override
public Object plugin(Object target) {
    return Plugin.wrap(target, this);
}

}

一个执行原方法的类。

123456789101112131415161718192021222324252627282930313233343536
/** * @description: 此类为辅助plugin执行原方法的类,存放原executor、原方法、原参数 * @author: linyh * @create: 2018-11-02 10:13 **/public class Invocation {     private Object target;    private Method method;    private Object[] args;     public Invocation(Object target, Method method, Object[] args) {        this.target = target;        this.method = method;        this.args = args;    }     public Object getTarget() {        return target;    }     public Method getMethod() {        return method;    }     public Object[] getArgs() {        return args;    }     /**     * 在这里执行原executor的方法     */    public Object proceed() throws InvocationTargetException, IllegalAccessException {        return method.invoke(target, args);    }}

/**

  • @description: 此类为辅助plugin执行原方法的类,存放原executor、原方法、原参数
  • @author: linyh
  • @create: 2018-11-02 10:13

**/
public class Invocation {


private Object target;
private Method method;
private Object[] args;

public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
}

public Object getTarget() {
    return target;
}

public Method getMethod() {
    return method;
}

public Object[] getArgs() {
    return args;
}

/**
 * 在这里执行原executor的方法
 */
public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
}

}

然后是一个核心实现类,插件实际的代理者。

1234567891011121314151617181920212223242526272829303132333435363738394041424344
/** * @description: 插件代理者 * @author: linyh * @create: 2018-11-02 10:30 **/public class Plugin implements InvocationHandler {    private Object target;    private Interceptor interceptor;     /**     * @param target 被代理的Executor类     * @param interceptor plugin插件     */    public Plugin(Object target, Interceptor interceptor) {        this.target = target;        this.interceptor = interceptor;    }     /**     * 包装executor的方法     * @param obj 被代理的Executor类     * @param interceptor plugin插件     * @return 代理类     */    public static Object wrap(Object obj, Interceptor interceptor) {        Class clazz = obj.getClass();        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new Plugin(obj, interceptor));    }     /**     * 代理方法核心     */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //判断插件上的注解是否与method的方法名匹配(需拦截executor的哪个一个方法)        if (interceptor.getClass().isAnnotationPresent(com.test.mybatis.v2.annotation.Method.class)) {            if (method.getName().equals(interceptor.getClass().getAnnotation(com.test.mybatis.v2.annotation.Method.class).value())) {                //不执行原方法,直接执行插件方法                return interceptor.intercept(new Invocation(target, method, args));            }        }        return method.invoke(target, method, args);    }}

/**

  • @description: 插件代理者
  • @author: linyh
  • @create: 2018-11-02 10:30

**/
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;


/**
 * @param target 被代理的Executor类
 * @param interceptor plugin插件
 */
public Plugin(Object target, Interceptor interceptor) {
    this.target = target;
    this.interceptor = interceptor;
}

/**
 * 包装executor的方法
 * @param obj 被代理的Executor类
 * @param interceptor plugin插件
 * @return 代理类
 */
public static Object wrap(Object obj, Interceptor interceptor) {
    Class clazz = obj.getClass();
    return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new Plugin(obj, interceptor));
}

/**
 * 代理方法核心
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //判断插件上的注解是否与method的方法名匹配(需拦截executor的哪个一个方法)
    if (interceptor.getClass().isAnnotationPresent(com.test.mybatis.v2.annotation.Method.class)) {
        if (method.getName().equals(interceptor.getClass().getAnnotation(com.test.mybatis.v2.annotation.Method.class).value())) {
            //不执行原方法,直接执行插件方法
            return interceptor.intercept(new Invocation(target, method, args));
        }
    }
    return method.invoke(target, method, args);
}

}

接下来编写测试类进行测试吧

12345678
        SqlSessionFactory factory = new SqlSessionFactory();        //没有插件且没有开启缓存        CustomSqlSession sqlSession1 = factory.build("com.test.mybatis.v2.mapper").openSqlSession();        TestCustomMapper mapper1 = sqlSession1.getMapper(TestCustomMapper.class);        Test test1 = mapper1.selectByPrimaryKey(1);        System.out.println("第一次查询结果为: " + test1);        test1 = mapper1.selectByPrimaryKey(1);        System.out.println("第二次查询结果为: " + test1);

    SqlSessionFactory factory = new SqlSessionFactory();
    //没有插件且没有开启缓存
    CustomSqlSession sqlSession1 = factory.build("com.test.mybatis.v2.mapper").openSqlSession();
    TestCustomMapper mapper1 = sqlSession1.getMapper(TestCustomMapper.class);
    Test test1 = mapper1.selectByPrimaryKey(1);
    System.out.println("第一次查询结果为: " + test1);
    test1 = mapper1.selectByPrimaryKey(1);
    System.out.println("第二次查询结果为: " + test1);

控制台打印

手写MyBatis2.0附带Plugin功能(增强版本)

第二个测试

123456789
        SqlSessionFactory factory = new SqlSessionFactory();        //有插件但没有开启缓存        CustomSqlSession sqlSession2 = factory.build("com.test.mybatis.v2.mapper",                new String[] {"com.test.mybatis.v2.plugin.customPlugin.testPlugin"}).openSqlSession();        TestCustomMapper mapper2 = sqlSession2.getMapper(TestCustomMapper.class);        Test test2 = mapper2.selectByPrimaryKey(2);        System.out.println("第一次查询结果为: " + test2);        test2 = mapper2.selectByPrimaryKey(2);        System.out.println("第二次查询结果为: " + test2);

    SqlSessionFactory factory = new SqlSessionFactory();
    //有插件但没有开启缓存
    CustomSqlSession sqlSession2 = factory.build("com.test.mybatis.v2.mapper",
            new String[] {"com.test.mybatis.v2.plugin.customPlugin.testPlugin"}).openSqlSession();
    TestCustomMapper mapper2 = sqlSession2.getMapper(TestCustomMapper.class);
    Test test2 = mapper2.selectByPrimaryKey(2);
    System.out.println("第一次查询结果为: " + test2);
    test2 = mapper2.selectByPrimaryKey(2);
    System.out.println("第二次查询结果为: " + test2);

控制台打印

手写MyBatis2.0附带Plugin功能(增强版本)

第三个测试

123456789
        SqlSessionFactory factory = new SqlSessionFactory();        //有插件且有缓存        CustomSqlSession sqlSession3 = factory.build("com.test.mybatis.v2.mapper",                new String[] {"com.test.mybatis.v2.plugin.customPlugin.testPlugin"}, true).openSqlSession();        TestCustomMapper mapper3 = sqlSession3.getMapper(TestCustomMapper.class);        Test test3 = mapper3.selectByPrimaryKey(3);        System.out.println("第一次查询结果为: " + test3);        test3 = mapper3.selectByPrimaryKey(3);        System.out.println("第二次查询结果为: " + test3);

    SqlSessionFactory factory = new SqlSessionFactory();
    //有插件且有缓存
    CustomSqlSession sqlSession3 = factory.build("com.test.mybatis.v2.mapper",
            new String[] {"com.test.mybatis.v2.plugin.customPlugin.testPlugin"}, true).openSqlSession();
    TestCustomMapper mapper3 = sqlSession3.getMapper(TestCustomMapper.class);
    Test test3 = mapper3.selectByPrimaryKey(3);
    System.out.println("第一次查询结果为: " + test3);
    test3 = mapper3.selectByPrimaryKey(3);
    System.out.println("第二次查询结果为: " + test3);

控制台打印

手写MyBatis2.0附带Plugin功能(增强版本)

其中plugin作用在executor的query方法之前,Mybatis不仅可以配置插件到executor的query中,还能配置插件到例如ResultSetHandler中。

手写MyBatis2.0附带Plugin功能(增强版本)

以上图片为Mybatis官网中截取,可以看到拦截的方法可以有很多种,这里简化了一下,只拦截了executor的query方法,体会到Mybatis的思想即可。

到这里就大致完成了MyBatis2.0版本的编写了,依然存在很多不足之处,可见编写一个强大的框架是十分不易的,不可能一步登天就完成所有的功能,都是一点一点累加上去,正因为如此,一个好的设计理念,好的抽象思维就显得格外重要,需要架构者具有能设计出一个易扩展、符合面向对象设计原则的代码的能力,而这个能力实属不易,或许这就是架构师稀少的原因吧。

总结

不足之处与需要改进的地方

  1. 设置参数与结果映射都需要一个TypeHandler,这里偷懒简化了,可以作为下一个版本的加强点。
  2. SQL语句局限于查询方法,新增所有的增删改方法也是下个版本的加强点。
  3. 实现一个清空缓存的方法,在使用了增删改方法时有清空缓存的必要。
  4. Plugin插件功能扩展,使其可以作用到Executor以外的三个类,扩展可以拦截的方法。

以上提到的功能除了TypeHandler以外,都是在做重复工作,意义不大,所以我在2.0版本中没有去实现那些功能,例如增删改查方法我只实现一个查的方法, 作为一个例子,其他的增删改都是可以一样画葫芦的,Plugin功能扩展也是如此。

写到这里,越发觉得开发一个框架例如mybatis是多么的不易,感觉到mybatis底下组件的强大,2.0中一定还有很多没有提及的缺点,可以体会开发一个健壮的框架的难度了。经过这次自己手写MyBatis,更深刻的认识了MyBatis底层各种组件的实现原理,也巩固了一些设计模式、面向对象的一些原则。

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 手写MyBatis2.0附带Plugin功能(增强版本)


 上一篇
自己动手实现一个简单的Mybatis(初级版本1.0) 自己动手实现一个简单的Mybatis(初级版本1.0)
手写Mybatis-v1.0**源码链接(包括v1.0与v2.0): ** 从上一个文章 —Mybatis概述中了解到了Mybatis的主要架构与底层原理流程,结尾给出了一个宏观流程图,可以知道,大致我们可以从三个模块入手: SqlSes
2021-04-05
下一篇 
MyBatis动态SQL(认真看看, 以后写SQL就爽多了) MyBatis动态SQL(认真看看, 以后写SQL就爽多了)
作者:阿进的写字台cnblogs.com/homejim/p/9909657.html 温馨提示:文中代码看不全可左右滑动 MyBatis 令人喜欢的一大特性就是动态 SQL。 在使用 JDBC 的过程中,
2021-04-05