手写实现mybatis迷你版

 

实现功能

第一次用markdown写文章,也比较懒,大部分但是代码的复制粘贴。

实现注解Entity用于映射实体类,Intercepts用于拦截器,插件拓展,Select配置SQL语句。

实体类驼峰命名的字段自动转换为数据库字段时变成下划线分割字段。

实现mybatis缓存功能,缓存有3种机制,默认无限缓存由HashMap实现,LRU缓存(淘汰最近最少使用缓存),LFU缓存(淘汰使用频率最少缓存)。

实现mybatis插件拓展功能。

新建项目

新建maven项目,pom文件导入mysql就行



    4.0.0

    com.msl
    minibatis
    1.0-SNAPSHOT

    
        
            mysql
            mysql-connector-java
            5.1.49
        
    


在resources下创建minibatis.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
jdbc.username=root
jdbc.password=123456
cache.enabled=true
plugin.path=com.msl.minibatis.interceptor.MyPlugin
mapper.path=com.msl.minibatis.mapper

手写实现mybatis迷你版_第1张图片

定义注解

 

Entity注解

package com.msl.minibatis.annotation;

import java.lang.annotation.*;

/**
 * 映射返回的实体类
 * @Author msl
 * @Date 2021-01-17 19:54
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {
    Class value();
}

Intercepts注解

package com.msl.minibatis.annotation;

import java.lang.annotation.*;

/**
 * 拦截器,用于插件功能的实现
 * @Author msl
 * @Date 2021-01-17 19:54
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
}

Select注解

package com.msl.minibatis.annotation;

import java.lang.annotation.*;

/**
 * 配置sql查询语句
 * @Author msl
 * @Date 2021-01-17 19:55
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    String value();
}

session模块

Configuration

全局配置类

解析minibatis.properties文件,获取数据库配置,是否开启缓存,mapper接口类路径,插件类路径

主要功能,解析sql.properties,获取接口方法和sql语句的映射,使用一个Map mappedStatements来存放。

MapperRegistry MAPPER_REGISTRYmapper接口工厂类,在后续的binding里实现

List> mapperList所有的Mapper接口类

InterceptorChain interceptorChain插件类

newExecutor()会读取minibatis.properties配置中缓存机制,选择是否开启缓存,配置插件时,使用插件代理。

package com.msl.minibatis.session;

import com.msl.minibatis.TestMiniBatis;
import com.msl.minibatis.annotation.Entity;
import com.msl.minibatis.annotation.Select;
import com.msl.minibatis.binding.MapperRegistry;
import com.msl.minibatis.cache.*;
import com.msl.minibatis.executor.CachingExecutor;
import com.msl.minibatis.executor.Executor;
import com.msl.minibatis.executor.SimpleExecutor;
import com.msl.minibatis.plugin.Interceptor;
import com.msl.minibatis.plugin.InterceptorChain;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 读取全局配置
 *
 * @Author msl
 * @Date 2021-01-17 19:59
 */
@Slf4j
public class Configuration {

    /**
     * sql映射关系配置,使用注解时不用配置
     */
    public static final ResourceBundle sqlMappings;

    /**
     * 全局配置
     */
    public static final ResourceBundle properties;
    /**
     * 维护接口与工厂类关系
     */
    public static final MapperRegistry MAPPER_REGISTRY = new MapperRegistry();
    /**
     * 维护接口方法与SQL关系
     */
    public static final Map mappedStatements = new HashMap<>();

    /**
     * 插件
     */
    private InterceptorChain interceptorChain = new InterceptorChain();
    /**
     * 所有Mapper接口
     */
    private List> mapperList = new ArrayList<>();
    /**
     * 类所有文件
     */
    private List classPaths = new ArrayList<>();

    /**
     * 读取resources路径下的properties文件
     */
    static {
        sqlMappings = ResourceBundle.getBundle("sql");
        properties = ResourceBundle.getBundle("minibatis");
    }

    /**
     * 初始化时解析全局配置文件
     * 1.解析sql.properties
     * 2.解析minibatis.properties
     */
    public Configuration() {
        // 1.解析sql.properties 在properties和注解中重复配置SQL会覆盖
        // 获取sql中的键值对
        for (String key : sqlMappings.keySet()) {
            Class mapper = null;
            // sql语句
            String statement = null;
            // pojo对象string值
            String pojoStr = null;
            // pojo对象类
            Class pojo = null;

            // properties中的value用--隔开,第一个是SQL语句
            statement = sqlMappings.getString(key).split("--")[0];
            // properties中的value用--隔开,第二个是需要转换的POJO类型
            pojoStr = sqlMappings.getString(key).split("--")[1];
            try {
                // properties中的key是接口类型+方法
                // 从接口类型+方法中截取接口类型
                mapper = Class.forName(key.substring(0, key.lastIndexOf(".")));
                pojo = Class.forName(pojoStr);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            // 接口与返回的实体类关系
            MAPPER_REGISTRY.addMapper(mapper, pojo);
            // 接口方法与SQL关系
            mappedStatements.put(key, statement);
        }
        // 2.解析接口上的注解(会覆盖XML中的接口与实体类的关系)
        String mapperPath = properties.getString("mapper.path");
        scanPackage(mapperPath);
        for (Class mapper : mapperList) {
            parsingClass(mapper);
        }
        // 3.解析插件,可配置多个插件
        String pluginPathValue = properties.getString("plugin.path");
        String[] pluginPaths = pluginPathValue.split(",");
        if (pluginPaths != null) {
            // 将插件添加到interceptorChain中
            for (String plugin : pluginPaths) {
                Interceptor interceptor = null;
                try {
                    interceptor = (Interceptor) Class.forName(plugin).newInstance();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                interceptorChain.addInterceptor(interceptor);
            }
        }
    }

    /**
     * 根据statement判断是否存在映射的SQL
     *
     * @param statementName
     * @return
     */
    public boolean hasStatement(String statementName) {
        return mappedStatements.containsKey(statementName);
    }

    /**
     * 根据statement ID获取SQL
     *
     * @param id
     * @return
     */
    public String getMappedStatement(String id) {
        return mappedStatements.get(id);
    }

    public  T getMapper(Class clazz, DefaultSqlSession sqlSession) {
        return MAPPER_REGISTRY.getMapper(clazz, sqlSession);
    }

    /**
     * 创建执行器,当开启缓存时使用缓存装饰
     * 当配置插件时,使用插件代理
     *
     * @return
     */
    public Executor newExecutor() {
        Executor executor = null;
        // 读取minibatis.properties中的cache.enabled值
        // Boolean.valueOf() 空值返回false 即默认不开启缓存
        if (Boolean.valueOf(properties.getString(CacheKey.CACHE_ENABLED))) {
            System.out.println("选择器开启缓存");
            String cacheType = properties.getString("cache.type");
            Integer capacity = Integer.valueOf(properties.getString("cache.capacity"));
            CacheFactory cacheFactory;
            if (cacheType == null) {
                System.out.println("默认缓存,用HashMap实现,人有多大胆,缓存多大产");
                cacheFactory = new SimpleCache();
            } else if (cacheType.equals("LRU")) {
                System.out.println("使用LRU缓存,capacity=" + capacity);
                cacheFactory = new LRUCache(capacity);
            } else {
                System.out.println("使用LFU缓存,capacity=" + capacity);
                cacheFactory = new LFUCache(capacity);
            }

            executor = new CachingExecutor(new SimpleExecutor(), cacheFactory);
        } else {
            System.out.println("执行器不开启缓存");
            executor = new SimpleExecutor();
        }

        // 目前只拦截了Executor,所有的插件都对Executor进行代理,没有对拦截类和方法签名进行判断
        if (interceptorChain.hasPlugin()) {
            return (Executor) interceptorChain.pluginAll(executor);
        }
        return executor;
    }

    /**
     * 解析Mapper接口上配置的注解(SQL语句)
     */
    private void parsingClass(Class mapper) {
        // 1.解析类上的注解
        // 如果有Entity注解,说明是查询数据库的接口
        if (mapper.isAnnotationPresent(Entity.class)) {
            for (Annotation annotation : mapper.getAnnotations()) {
                if (annotation.annotationType().equals(Entity.class)) {
                    // 注册接口与实体类的映射关系
                    MAPPER_REGISTRY.addMapper(mapper, ((Entity) annotation).value());
                }
            }
        }

        // 2.解析方法上的注解
        Method[] methods = mapper.getMethods();
        for (Method method : methods) {
            //TODO 其他操作
            // 解析@Select注解的SQL语句
            if (method.isAnnotationPresent(Select.class)) {
                for (Annotation annotation : method.getDeclaredAnnotations()) {
                    if (annotation.annotationType().equals(Select.class)) {
                        // 注册接口类型+方法名和SQL语句的映射关系
                        String statement = method.getDeclaringClass().getName() + "." + method.getName();
                        mappedStatements.put(statement, ((Select) annotation).value());
                    }
                }
            }
        }
    }

    /**
     * 根据全局配置文件的Mapper接口路径,扫描所有接口
     */
    private void scanPackage(String mapperPath) {
        String classPath = TestMiniBatis.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 = null;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if (clazz.isInterface()) {
                mapperList.add(clazz);
            }

        }
    }

    /**
     * 获取文件或文件夹下所有的类.class
     */
    private void doPath(File file) {
        // 文件夹,遍历
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f1 : files) {
                // 递归子文件夹
                doPath(f1);
            }
        } else {
            // 文件,直接添加
            if (file.getName().endsWith(".class")) {
                classPaths.add(file.getPath());
            }
        }
    }
}

DefauleSqlSession

package com.msl.minibatis.session;

import com.msl.minibatis.executor.Executor;

/**
 * sqlSession  api
 * 在实际mybatis中DefaultSqlSession是线程不安全的,底层是HashMap
 * Mybatis-Spring整合后通过SqlSessionTemplate类实现线程安全,该类对DefaultSqlSession进行jdk动态代理,在所有DAO层共享一个实例(默认单例)
 * SqlSessionTemplate是通过代理拦截和SqlSessionHolder实现的sqlsession线程安全和自动新建和释放连接的。
 * 看构造函数函数中构建代理类,该代理类实现SqlSession接口,定义了方法拦截器.
 * 如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法,这个方法中自动进行了SqlSession的自动请求和释放
 * 如果不被spring托管则自己新建和释放sqlsession,如果被spring管理则使用SqlSessionHolder进行request和relase操作
 * 

* 不使用Spring框架时要获取线程安全的SqlSession,mybatis也实现了SqlSessionManager类,该类实现了SqlSessionFactory * SqlSession并且在其中定义了一个TreadLocal的SqlSession对象,同时使用了代理拦截进行了SqlSession的自动管理 * * @Author msl * @Date 2021-01-17 20:15 */ public class DefaultSqlSession { private Configuration configuration; private Executor executor; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; // 根据全局配置决定是否使用缓存装饰 this.executor = configuration.newExecutor(); } public Configuration getConfiguration() { return configuration; } /** * 获取mapper * * @param clazz * @param * @return */ public T getMapper(Class clazz) { return configuration.getMapper(clazz, this); } /** * 1.根据statement获取配置类中的具体sql语句 * 2.调用executor.query方法,executor.query会处理入参,查询语句,并且将ResultSet封装成pojo类返回 * * @param statement * @param parameter * @param pojo * @param * @return */ public T selectOne(String statement, Object[] parameter, Class pojo) { String sql = getConfiguration().getMappedStatement(statement); return executor.query(sql, parameter, pojo); } }

SqlSessionFactory

会话工厂类,该类负责创建Configuration类(java设计模式为建造者模式)

package com.msl.minibatis.session;

/**
 * 会话工厂类,用于解析配置文件,产生SqlSession
 *
 * @Author msl
 * @Date 2021-01-17 20:25
 */
public class SqlSessionFactory {

    private Configuration configuration;

    /**
     * build方法用于初始化Configuration,解析配置文件的工作在Configuration的构造函数中
     *
     * @return
     */
    public SqlSessionFactory build() {
        configuration = new Configuration();
        return this;
    }

    /**
     * 获取DefaultSqlSession
     *
     * @return
     */
    public DefaultSqlSession openSqlSession() {
        return new DefaultSqlSession(configuration);
    }
}

binding接口类绑定sql语句

绑定,主要实现对mapper接口类的动态代理,例如创建了BlogMapper 接口类,在接口类里定义一个方法,并且使用@Select注解配置sql语句,或者在sql.properties里配置相应sql语句。MapperProxy动态代理mapper接口类,通过 接口类名+“."+方法名拼接找到映射的sql语句,后续的执行交给Executor接口实现类实现。

MapperProxy

package com.msl.minibatis.binding;

import com.msl.minibatis.session.DefaultSqlSession;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * mapper代理类 jdk动态代理
 * @Author msl
 * @Date 2021-01-17 21:22
 */
public class MapperProxy implements InvocationHandler {

    /**
     * sqlSession包含了配置类,配置类存放了相应的statementId
     */
    private DefaultSqlSession sqlSession;

    /**
     * pojo对象
     */
    private Class object;

    public MapperProxy(DefaultSqlSession sqlSession, Class object) {
        this.sqlSession = sqlSession;
        this.object = object;
    }

    /**
     * 所有Mapper接口的方法调用都会走到这里
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取mapper接口类名
       String mapperInterfaceName = method.getDeclaringClass().getName();
       String methodName = method.getName();
       // 根据接口类名和方法名拼接出statementId
       String statementId = mapperInterfaceName + "." + methodName;
        // 如果根据接口类型+方法名能找到映射的SQL,则执行SQL
        if (sqlSession.getConfiguration().hasStatement(statementId)) {
            return sqlSession.selectOne(statementId, args, object);
        }
        // 否则直接执行被代理对象的原方法
        return method.invoke(proxy, args);
    }
}

 

MapperProxyFactory

MapperProxy工厂类,泛型T为mapper接口类,object为pojo对象

package com.msl.minibatis.binding;

import com.msl.minibatis.session.DefaultSqlSession;

import java.lang.reflect.Proxy;

/**
 * MapperProxy工厂类
 *
 * @Author msl
 * @Date 2021-01-17 20:14
 */
public class MapperProxyFactory {

    /**
     * mapper接口类
     */
    private Class mapperInterface;

    /**
     * pojo对象
     */
    private Class object;

    public MapperProxyFactory(Class mapperInterface, Class object) {
        this.mapperInterface = mapperInterface;
        this.object = object;
    }

    /**
     * 调用Proxy.newProxyInstance方法实现实例化 MapperProxy對象
     * @param sqlSession sqlSession包含了配置类,配置类存放了相应的statementId
     * @return 返回对象强制转换成 mapperInterface mapper接口类
     */
    public T newInstance(DefaultSqlSession sqlSession) {
        // 获取接口类
        Class[] interfaces = new Class[]{mapperInterface};
        // jdk代理对象
        // sqlSession包含了配置类,配置类存放了相应的statementId
        MapperProxy mapperProxy = new MapperProxy(sqlSession, object);
        // jdk动态代理3个参数
        // 1类加载器 2 被代理类实现的接口(这里没有被代理类) 3 InvocationHandler实现类
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), interfaces, mapperProxy);
    }
}

MapperRegistry

mapper注册类,负责维护mapper接口类和mapperProxyFactory工厂类映射关系。

package com.msl.minibatis.binding;

import com.msl.minibatis.session.DefaultSqlSession;

import java.util.HashMap;
import java.util.Map;

/**
 * mapper注册,获取MapperProxy代理对象
 *
 * @Author msl
 * @Date 2021-01-17 20:03
 */
public class MapperRegistry {

    /**
     * 接口和工厂类映射关系
     */
    private final Map, MapperProxyFactory> knownMappers = new HashMap<>();

    /**
     * 在Configuration中解析接口上的注解时,存入接口和工厂类的映射关系
     * 此处传入pojo类型,是为了最终处理结果集的时候将结果转换为POJO类型
     *
     * @param clazz mapper接口类
     * @param pojo
     * @param 
     */
    public  void addMapper(Class clazz, Class pojo) {
        // 初始化mapper代理工厂类
        MapperProxyFactory mapperProxyFactory = new MapperProxyFactory(clazz, pojo);
        // 放入knownMappers中 享元模式
        knownMappers.put(clazz, mapperProxyFactory);
    }

    /**
     * 创建一个代理对象
     * clazz mapper接口类
     * sqlSession包含了配置类,配置类存放了相应的statementId
     */
    public  T getMapper(Class clazz, DefaultSqlSession sqlSession) {

        // 获取该clazz的代理工厂类
        // proxyFactory.mapperInterface 等同于 clazz
        MapperProxyFactory proxyFactory = knownMappers.get(clazz);

        if (proxyFactory == null) {
            throw new RuntimeException("Type: " + clazz + " can not find");
        }
        // 返回mapper代理类
        //
        return (T) proxyFactory.newInstance(sqlSession);
    }
}

executor模块执行sql语句

mapperProxy代理类获取到sql语句以及要封装的pojo对象,executor模块负责执行sql,并且使用ParameterHandler类解析mapper接口类的入参,StatementHandler封装jdbc,执行sql语句,返回的结果ResultSet通过ResultSetHandler类进行解析成pojo类。

Executor

Executor接口类,CachingExecutor,SimpleExecutor都会实现接口类。其中CachingExecutor具有缓存功能,通过在minibatis.properties文件中配置cache.enabled=true开启缓存,该缓存功能模拟mybatis的一级缓存功能。

package com.msl.minibatis.executor;

/**
 * 执行器
 * @Author msl
 * @Date 2021-01-17 21:13
 */
public interface Executor {
     T query(String statement, Object[] parameter, Class pojo);
}

SimpleExecutor

默认的执行类,直接调用statementHandler去查询数据

package com.msl.minibatis.executor;

/**
 * @Author msl
 * @Date 2021-01-17 21:21
 */
public class SimpleExecutor implements Executor {

    /**
     * 调用statementHandler去执行query
     *
     * @param statement
     * @param parameter
     * @param pojo
     * @param 
     * @return
     */
    @Override
    public  T query(String statement, Object[] parameter, Class pojo) {
        StatementHandler statementHandler = new StatementHandler();
        return statementHandler.query(statement, parameter, pojo);
    }
}

CachingExecutor

带缓存功能的执行类,模拟实现mybatis一级缓存的功能, 装饰器模式。缓存CacheFacotry有3种策略,默认SimpleCache缓存底层就一个HashMap,然后还有LRU缓存,LFU缓存。

package com.msl.minibatis.executor;

import com.msl.minibatis.cache.CacheFactory;
import com.msl.minibatis.cache.CacheKey;
import com.msl.minibatis.cache.LRUCache;
import lombok.extern.slf4j.Slf4j;

/**
 * @Author msl
 * @Date 2021-01-17 21:17
 */
@Slf4j
public class CachingExecutor implements Executor {

    private Executor delegate;

    /**
     * 一级缓存
     */
    CacheFactory cache = new LRUCache(16);

    public CachingExecutor(Executor delegate, CacheFactory cache) {
        this.cache = cache;
        this.delegate = delegate;
    }

    @Override
    public  T query(String statement, Object[] parameter, Class pojo) {
        // 计算CacheKey
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(statement);
        cacheKey.update(joinStr(parameter));

        LRUCache lruCache = new LRUCache(16);

        if (cache.containsKey(cacheKey.getCode())) {
            // 命中缓存
            System.out.println("命中缓存");
            return (T) cache.get(cacheKey.getCode());
        } else {
            System.out.println("去数据库查询数据");
            Object obj = delegate.query(statement, parameter, pojo);
            cache.put(cacheKey.getCode(), obj);
            System.out.println("查询结果放入缓存中");
            return (T) obj;
        }

    }

    /**
     * 拼接入参字符串 逗号分割
     *
     * @param objs
     * @return
     */
    private String joinStr(Object[] objs) {
        StringBuffer sb = new StringBuffer();
        if (objs != null && objs.length > 0) {
            for (Object objStr : objs) {
                sb.append(objStr.toString() + ",");
            }
        }
        int len = sb.length();
        if (len > 0) {
            sb.deleteCharAt(len - 1);
        }
        return sb.toString();
    }
}

StatementHandler

处理sql语句

package com.msl.minibatis.executor;

import com.msl.minibatis.session.Configuration;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * 操封装了JDBC Statement 作数据库
 *
 * @Author msl
 * @Date 2021-01-18 21:21
 */
public class StatementHandler {

    /**
     * 结果处理封装成pojo类
     */
    private ResultSetHandler resultSetHandler = new ResultSetHandler();

    /**
     * 查询接口
     *
     * @param statement
     * @param parameter
     * @param pojo
     * @param 
     * @return
     */
    public  T query(String statement, Object[] parameter, Class pojo) {
        Connection conn = null;
        PreparedStatement preparedStatement = null;
        Object result = null;

        try {
            conn = getConnection();
            preparedStatement = conn.prepareStatement(statement);
            ParameterHandler parameterHandler = new ParameterHandler(preparedStatement);
            parameterHandler.setParameters(parameter);
            preparedStatement.execute();
            try {
                result = resultSetHandler.handle(preparedStatement.getResultSet(), pojo);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return (T)result;
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                conn = null;
            }
        }
        // 只在try里面return会报错
        return null;
    }

    /**
     * 获取连接
     * @return
     * @throws SQLException
     */
    private Connection getConnection() {
        String driver = Configuration.properties.getString("jdbc.driver");
        String url =  Configuration.properties.getString("jdbc.url");
        String username = Configuration.properties.getString("jdbc.username");
        String password = Configuration.properties.getString("jdbc.password");
        Connection conn = null;
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}

ParameterHandler

处理参数

package com.msl.minibatis.executor;

import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * 处理入参
 *
 * @Author msl
 * @Date 2021-01-17 21:24
 */
public class ParameterHandler {

    private PreparedStatement preparedStatement;

    public ParameterHandler(PreparedStatement statement) {
        this.preparedStatement = statement;
    }

    /**
     * 从方法中获取参数,遍历设置SQL中的?占位符
     *
     * @param parameters
     */
    public void setParameters(Object[] parameters) {
        try {
            // PreparedStatement的序号是从1开始的
            for (int i = 0; i < parameters.length; i++) {
                int k = i + 1;
                if (parameters[i] instanceof Integer) {
                    preparedStatement.setInt(k, (Integer) parameters[i]);
                } else if (parameters[i] instanceof Long) {
                    preparedStatement.setLong(k, (Long) parameters[i]);
                } else if (parameters[i] instanceof String) {
                    preparedStatement.setString(k, String.valueOf(parameters[i]));
                } else if (parameters[i] instanceof Boolean) {
                    preparedStatement.setBoolean(k, (Boolean) parameters[i]);
                } else {
                    preparedStatement.setString(k, String.valueOf(parameters[i]));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

ResultSetHandler

处理结果

package com.msl.minibatis.executor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @Author msl
 * @Date 2021-01-17 21:21
 */
public class ResultSetHandler {

    /**
     * 返回的数据类型封装成pojo类
     *
     * @param resultSet
     * @param pojo
     * @param 
     * @return
     */
    public  T handle(ResultSet resultSet, Class pojo) {
        Object o = null;
        try {
            o = pojo.newInstance();
            if (resultSet.next()) {
                // 循环赋值
                for (Field field : o.getClass().getDeclaredFields()) {
                    setValue(o, field, resultSet);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) o;
    }


    /**
     * 通过反射给属性赋值
     *
     * @param pojo
     * @param field
     * @param rs
     */
    private void setValue(Object pojo, Field field, ResultSet rs) {
        try {
            // 获取 pojo 的 set 方法
            Method setMethod = pojo.getClass().getMethod("set" + firstWordCapital(field.getName()), field.getType());
            // 调用 pojo 的set 方法,使用结果集给属性赋值
            // 赋值先从resultSet取出值
            setMethod.invoke(pojo, getResult(rs, field));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据反射判断类型,从ResultSet中取对应类型参数
     *
     * @param rs
     * @param field
     * @return
     * @throws SQLException
     */
    private Object getResult(ResultSet rs, Field field) throws SQLException {
        //TODO TypeHandler
        Class type = field.getType();
        // 驼峰转下划线
        String dataName = HumpToUnderline(field.getName());
        // TODO 类型判断不够全
        if (Integer.class == type) {
            return rs.getInt(dataName);
        } else if (String.class == type) {
            return rs.getString(dataName);
        } else if (Long.class == type) {
            return rs.getLong(dataName);
        } else if (Boolean.class == type) {
            return rs.getBoolean(dataName);
        } else if (Double.class == type) {
            return rs.getDouble(dataName);
        } else {
            return rs.getString(dataName);
        }
    }

    /**
     * 数据库下划线转Java驼峰命名
     *
     * @param para
     * @return
     */
    public static String HumpToUnderline(String para) {
        StringBuilder sb = new StringBuilder(para);
        int temp = 0;
        if (!para.contains("_")) {
            for (int i = 0; i < para.length(); i++) {
                if (Character.isUpperCase(para.charAt(i))) {
                    sb.insert(i + temp, "_");
                    temp += 1;
                }
            }
        }
        return sb.toString().toUpperCase();
    }

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

Cache缓存

缓存模块

CacheFactory

缓存工厂类

package com.msl.minibatis.cache;

/**
 * @Author msl
 * @Date 2021-01-18 22:21
 */
public interface CacheFactory {

    boolean containsKey(int key);

    Object get(int key);

    void put(int key, Object value);
}

SimpleFactory

普通缓存,由HashMap实现

package com.msl.minibatis.cache;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author msl
 * @Date 2021-01-18 22:25
 */
public class SimpleCache implements CacheFactory {

    Map cache = new HashMap<>();

    @Override
    public boolean containsKey(int key) {
        return cache.containsKey(key);
    }

    @Override
    public Object get(int key) {
        return cache.get(key);
    }

    @Override
    public void put(int key, Object value) {
        cache.put(key, value);
    }
}

LRUCache

最近最少使用缓存

leetcode上有原题https://leetcode-cn.com/problems/lru-cache/

package com.msl.minibatis.cache;

import java.util.HashMap;
import java.util.Map;

/**
 * LRU缓存 最近最少使用
 * 在leetcode上写过这题
 *
 * @Author msl
 * @Date 2021-01-18 22:12
 */
public class LRUCache implements CacheFactory {

    /**
     * 缓存上限
     */
    private int capacity;
    private Map map;
    private ListNode head;
    private ListNode tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new ListNode(-1, null);
        tail = head;
    }

    @Override
    public boolean containsKey(int key) {
        return map.containsKey(key);
    }

    @Override
    public Object get(int key) {
        if (!map.containsKey(key)) {
            return null;
        }
        // map中存放的是要找的节点的前驱
        ListNode pre = map.get(key);
        ListNode cur = pre.next;

        // 把当前节点删掉并移到尾部
        if (cur != tail) {
            pre.next = cur.next;
            // 更新它后面 node 的前驱
            map.put(cur.next.key, pre);
            map.put(cur.key, tail);
            moveToTail(cur);
        }
        return cur.val;
    }

    @Override
    public void put(int key, Object value) {
        if (get(key) != null) {
            map.get(key).next.val = value;
            return;
        }
        // 若不存在则 new 一个
        ListNode node = new ListNode(key, value);
        // 当前 node 的 pre 是 tail
        map.put(key, tail);
        moveToTail(node);

        if (map.size() > capacity) {
            map.remove(head.next.key);
            map.put(head.next.next.key, head);
            head.next = head.next.next;
        }
    }

    private void moveToTail(ListNode node) {
        node.next = null;
        tail.next = node;
        tail = tail.next;
    }


    private class ListNode {
        Integer key;
        Object val;
        ListNode next;

        public ListNode(Integer key, Object val) {
            this.key = key;
            this.val = val;
            this.next = null;
        }
    }

}

LFUCache

力扣上有原题https://leetcode-cn.com/problems/lfu-cache/

package com.msl.minibatis.cache;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;

/**
 * LFU缓存 最少频率
 * 本来也想自己实现的,后来感觉嫌麻烦,直接去leetcode上捞一份题解过来改改用了
 *
 * @Author msl
 * @Date 2021-01-18 22:13
 */
public class LFUCache implements CacheFactory {

    /**
     * 存储缓存的内容
     */
    Map cache;

    /**
     * 存储每个频次对应的双向链表
     */
    Map> freqMap;

    /**
     * 记录当前缓存数量
     */
    int size;

    /**
     * 缓存上限
     */
    int capacity;

    /**
     * 存储当前最小频次
     */
    int min;

    public LFUCache(int capacity) {
        cache = new HashMap<>(capacity);
        freqMap = new HashMap<>();
        this.capacity = capacity;
    }

    @Override
    public boolean containsKey(int key) {
        return cache.containsKey(key);
    }

    @Override
    public Object get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        freqInc(node);
        return node.value;
    }

    @Override
    public void put(int key, Object value) {
        if (capacity == 0) {
            return;
        }
        Node node = cache.get(key);
        if (node != null) {
            node.value = value;
            freqInc(node);
        } else {
            if (size == capacity) {
                Node deadNode = removeNode();
                cache.remove(deadNode.key);
                size--;
            }
            Node newNode = new Node(key, value);
            cache.put(key, newNode);
            addNode(newNode);
            size++;
        }
    }

    void freqInc(Node node) {
        // 从原freq对应的链表里移除, 并更新min
        int freq = node.freq;
        LinkedHashSet set = freqMap.get(freq);
        set.remove(node);
        if (freq == min && set.size() == 0) {
            min = freq + 1;
        }
        // 加入新freq对应的链表
        node.freq++;
        LinkedHashSet newSet = freqMap.get(freq + 1);
        if (newSet == null) {
            newSet = new LinkedHashSet<>();
            freqMap.put(freq + 1, newSet);
        }
        newSet.add(node);
    }

    void addNode(Node node) {
        LinkedHashSet set = freqMap.get(1);
        if (set == null) {
            set = new LinkedHashSet<>();
            freqMap.put(1, set);
        }
        set.add(node);
        min = 1;
    }

    Node removeNode() {
        LinkedHashSet set = freqMap.get(min);
        Node deadNode = set.iterator().next();
        set.remove(deadNode);
        return deadNode;
    }

    class Node {
        Integer key;
        Object value;
        int freq = 1;

        public Node() {
        }

        public Node(Integer key, Object value) {
            this.key = key;
            this.value = value;
        }
    }
}

CacheKey

为什么哈希值和倍数取17和37呢,我数学也不太好,只能去问高中体育老师,反正mybatis源码里好像就是取的这两个值。

package com.msl.minibatis.cache;
import lombok.Data;
/**
 * 缓存key
 * @Author msl
 * @Date 2021-01-17 21:23
 */
public class CacheKey {

    /** 在minibatis.properties中读取这个key值来确定是否开启缓存 */
    public static final String CACHE_ENABLED = "cache.enabled";

    /** 默认哈希值 */
    private static final int DEFAULT_HASHCODE = 17;

    /** 倍数 */
    private static final int DEFAULT_MULTIPLIER = 37;

    private int hashCode;
    private int count;
    private int multiplier;

    /**
     * 构造函数
     */
    public CacheKey() {
        this.hashCode = DEFAULT_HASHCODE;
        this.count = 0;
        this.multiplier = DEFAULT_MULTIPLIER;
    }

    /**
     * 返回CacheKey的值
     * @return
     */
    public int getCode() {
        return hashCode;
    }

    /**
     * 计算CacheKey中的HashCode
     * @param object
     */
    public void update(Object object) {
        int baseHashCode = object == null ? 1 : object.hashCode();
        count++;
        baseHashCode *= count;
        hashCode = multiplier * hashCode + baseHashCode;
    }
}

plugin支持插件集成

创建模块

Interceptor

package com.msl.minibatis.plugin;

/**
 * 拦截器接口,所有自定义拦截器必须实现此接口
 *
 * @Author msl
 * @Date 2021-01-18 21:23
 */
public interface Interceptor {
    /**
     * 插件的核心逻辑实现
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    Object intercept(Invocation invocation) throws Throwable;

    /**
     * 对被拦截对象进行代理
     *
     * @param target
     * @return
     */
    Object plugin(Object target);
}

InterceptorChain

package com.msl.minibatis.plugin;

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

/**
 * 拦截器链 存放所有拦截器,和对代理对象进行循环代理
 *
 * @Author msl
 * @Date 2021-01-18 20:04
 */
public class InterceptorChain {

    private final List interceptors = new ArrayList<>();

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

    /**
     * 对被拦截对象进行层层代理
     *
     * @param target
     * @return
     */
    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;
    }
}

Invocation

package com.msl.minibatis.plugin;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 包装类,对被代理对象进行包装
 *
 * @Author msl
 * @Date 2021-01-18 21:24
 */
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;
    }

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }
}

Plugin

package com.msl.minibatis.plugin;

import com.msl.minibatis.annotation.Intercepts;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author msl
 * @Date 2021-01-18 21:24
 */
public class Plugin implements InvocationHandler {

    private Object target;
    private Interceptor interceptor;

    /**
     *
     * @param target 被代理对象
     * @param interceptor 拦截器(插件)
     */
    public Plugin(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    /**
     * 对被代理对象进行代理,返回代理类
     * @param obj
     * @param interceptor
     * @return
     */
    public static Object wrap(Object obj, Interceptor interceptor) {
        Class clazz = obj.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(),
                new Plugin(obj, interceptor));
    }

    /**
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 自定义的插件上有@Intercepts注解,指定了拦截的方法
        if (interceptor.getClass().isAnnotationPresent(Intercepts.class)) {
            // 如果是被拦截的方法,则进入自定义拦截器的逻辑
            if (method.getName().equals(interceptor.getClass().getAnnotation(Intercepts.class).value())) {
                return interceptor.intercept(new Invocation(target, method, args));
            }
        }
        // 非被拦截方法,执行原逻辑
        return method.invoke(target, method, args);
    }
}

interceptor自定义插件

MyPlugin

本来想实现一个mini版本的PageHelper,但是已经写到凌晨了,太捞了,先鸽了,有空再去写。

package com.msl.minibatis.interceptor;

import com.msl.minibatis.annotation.Intercepts;
import com.msl.minibatis.plugin.Interceptor;
import com.msl.minibatis.plugin.Invocation;
import com.msl.minibatis.plugin.Plugin;

import java.util.Arrays;

/**
 * @Author msl
 * @Date 2021-01-17 21:25
 */
@Intercepts("query")
public class MyPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        String statement = (String) invocation.getArgs()[0];
        Object[] parameter = (Object[]) invocation.getArgs()[1];
        Class pojo = (Class) invocation.getArgs()[2];
        System.out.println("进入自定义插件:MyPlugin");
        System.out.println("SQL:["+statement+"]");
        System.out.println("Parameters:"+ Arrays.toString(parameter));
        // todo 想实现类似于 PageHelper分页功能,看了一下它的源码,好像也不是很难实现,先咕了。
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}

测试功能

entity创建实体类User

package com.msl.minibatis.entity;

import java.io.Serializable;

/**
 * @Author msl
 * @Date 2021-01-17 21:27
 */
public class User implements Serializable {

    private Integer id;

    private String name;

    private String birthDay;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBirthDay() {
        return birthDay;
    }

    public void setBirthDay(String birthDay) {
        this.birthDay = birthDay;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", birthDay='" + birthDay + '\'' +
                '}';
    }
}

mapper创建UserMapper接口类

package com.msl.minibatis.mapper;

import com.msl.minibatis.annotation.Entity;
import com.msl.minibatis.annotation.Select;
import com.msl.minibatis.entity.User;

/**
 * @Author msl
 * @Date 2021-01-17 21:26
 */
@Entity(User.class)
public interface UserMapper {

    @Select("select * from user where id = ?")
    public User selectUserById(Integer id);

    @Select("select * from user where birth_day = ?")
    public User selectUserByBirthDay(String birthDay);
}

TestMiniBatis测试功能

package com.msl.minibatis;

import com.msl.minibatis.entity.User;
import com.msl.minibatis.mapper.UserMapper;
import com.msl.minibatis.session.DefaultSqlSession;
import com.msl.minibatis.session.SqlSessionFactory;

/**
 * @Author msl
 * @Date 2021-01-18 23:36
 */
public class TestMiniBatis {
    public static void main(String[] args) {
        SqlSessionFactory factory = new SqlSessionFactory();
        DefaultSqlSession sqlSession = factory.build().openSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectUserById(1);
        System.out.println("第一次查询: " + user);

        System.out.println("第二次查询,测试缓存");
        User user2 = userMapper.selectUserById(1);
        System.out.println("第2次查询: " + user2);

    }
}

返回结果

手写实现mybatis迷你版_第2张图片

 

大功告成!

再切换LFU缓存试试看,在minibatis.properties里修改配置

cache.enabled=true
cache.type=LRU
cache.capacity=64

再试一次

手写实现mybatis迷你版_第3张图片

 

源码已上传git

https://github.com/tonighteatji/msl-minbatis/tree/master

你可能感兴趣的:(java,java,mybatis,反射,后端)