手写简易的Mybatis

手写简易的Mybatis


此篇文章用来记录今天花个五个小时写出来的简易版mybatis,主要实现了基于注解方式的增删查改,目前支持List,Object类型的查找,参数都是基于Map集合的,可以先看一下接口:

public interface StudentInterface {

    @Select(value = " select * from student where age > #{age}")
    List<StudentMeBatis> getList(Map<String, Object> map);

    @Select(value = " select * from student where age = #{age}")
    StudentMeBatis get(Map<String, Object> map);

    @Insert(value = " insert into student values (#{age}, #{name}, #{year})")
    boolean insert (Map<String, Object> map);

    @Delete(value = " delete from student where name = #{name} ")
    boolean delete (Map<String, Object> map);

    @Update(value = " update student set age = #{age}, year = #{year} where name = #{name} ")
    boolean update (Map<String, Object> map);
}

测试效果:

public static void main(String[] args){

    StudentInterface studentInterface = MapperProxyUtil.newProxyInstance(StudentInterface.class);
    Map<String, Object> params = new HashMap<>();
    params.put("age",  15);
    params.put("name", 15);
    params.put("year", 1996);
    System.out.println(studentInterface.getList(params));
    System.out.println(studentInterface.get(params));
    System.out.println(studentInterface.insert(params));
}


响应数据:
2019-08-29 00:24:57.311 DEBUG [main] mebatis.sqlsession.DBUtil (55) getConnection - Connection is ready. consume time is: 132ms
2019-08-29 00:24:57.335 INFO  [main] mebatis.sqlsession.SqlRunner (102) select - SQL Execute successful, sql is:  select * from student where age > ?, params is: [15]
[Student{age='22', name='张三', year='1996'}, Student{age='19', name='李四', year='1998'}]
2019-08-29 00:24:57.387 INFO  [main] mebatis.sqlsession.SqlRunner (102) select - SQL Execute successful, sql is:  select * from student where age = ?, params is: [15]
Student{age='15', name='15', year='1996'}
2019-08-29 00:24:57.391 INFO  [main] mebatis.sqlsession.SqlRunner (119) baseMethod - SQL Execute successful, sql is:  insert into student values (?, ?, ?), params is: [15, 15, 1996], result is: true
true

必要知识储备:

  1. 基于Java的Mysql应用 -> JDBC
  2. JDK 动态代理技术
  3. Java 自定义注解的使用
  4. Java 正则表达式
  5. Java 反射相关知识
  6. FastJson

先来梳理结构,如果我们想做一个类似mybatis,基于注解实现的增删查改,需要有哪些步骤呢?

第一步:肯定需要增删查改四个注解

第二步:接口是没有方法体,无法执行的,我们怎么让它可以执行呢?动态代理技术,即第二步的核心就是让接口可以被代理

第三步:接口代理之后,可以拿到一个个方法的注解,拿到里面的value 和 方法参数,但是如何把参数和sql合起来呢,毕竟什么#{name} 什么的,sql是不认的,因此我们需要根据sql字符串(包含特殊字符),和方法的参数进行解析构建最终的执行语句

第四步:语句拿到了,参数拿到了,也可以执行了,怎么确定返回结果呢?这时候需要对方法的返回值做一个处理,拿到方法的返回结果,根据其返回值动态的构建到底是什么类型的返回结果,通过JDBC和FastJson去处理类型

第五步:万事俱备,只欠东风,构建最终的执行单元,去执行即可,到了这一步基本就是水到渠成(其实到第三步的时候所有思路就已经很清晰了)

第一步:构建注解

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

第二步:代理接口

/**
 * ******************************
 * author:      柯贤铭
 * createTime:   2019/8/28 10:31
 * description:  MapperProxyUtil 核心代理类
 * version:      V1.0
 * ******************************
 */
public class MapperProxyUtil implements InvocationHandler {

    /**
     * 代理指定的接口
     * @param tClass 接口class
     * @param <T>    接口类型
     */
    public static  <T> T newProxyInstance(Class<T> tClass) {
        return ProxyUtil.newProxyInstance(new MapperProxyUtil(), tClass);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 获取CRUD方法对应的基本SQL及参数,返回结果           (初始化版本)
        AnotationMsg detail = AnotationUtil.getAnotationDetail(method, args, method.getGenericReturnType());

        // sql 解析,参数替换等 -> SQL | PARAMS | RESULT_TYPE (可运行版本)
        SqlFactoryUtil.Runner sqlRunner = SqlFactoryUtil.executeRunner(detail);

        // 返回执行结果
        return SqlRunner.execute(sqlRunner);
    }
}

// 调用
StudentInterface studentInterface = MapperProxyUtil.newProxyInstance(StudentInterface.class);

第三步:解析原始SQL及参数

public class SqlFactoryUtil {

    // 组装最终执行单元 -> SQL | PARAMS | RESULT_TYPE
    //******************************************************
    // SQL         ->  "SQL";
    // PARAMS      ->  "PARAMS";
    // RESULT_TYPE ->  "RESULT_TYPE";
    //******************************************************

    /***
     * 生成执行单元
     */
    public static Runner executeRunner (AnotationMsg anotationMsg) throws MapperExeception {
        if (anotationMsg == null || StringUtils.isBlank(anotationMsg.getSql()) || anotationMsg.getMap() == null) {
            throw MapperExeception.errorSql(" -> " + anotationMsg);
        }

        // 参数集合
        List<Object> params = new ArrayList<>();

        // 转换参数 - 获取值
        Pattern p = Pattern.compile("#\\{.+?}");
        Matcher m = p.matcher(anotationMsg.getSql());
        while(m.find()){
            String key = m.group();
            key = key.replaceAll("#\\{", "");
            key = key.replaceAll("}", "");

            if (!anotationMsg.getMap().containsKey(key)) {
                throw MapperExeception.errorParams("do not have key -> " + key);
            }

            // 添加参数
            params.add(anotationMsg.getMap().get(key));
        }

        // 执行sql
        String finalSQL = m.replaceAll("?");

        // 返回 runner
        Runner result = new Runner();
        result.setSql(finalSQL);
        result.setParams(params);
        result.setResultType(anotationMsg.getResultType());
        return result;
    }

    public static class Runner {
        // 执行sql
        private String       sql;

        // 参数
        private List<Object> params;

        // 返回参数
        private Type         resultType;

        public String getSql() {
            return sql;
        }

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

        public List<Object> getParams() {
            return params;
        }

        public void setParams(List<Object> params) {
            this.params = params;
        }

        public Type getResultType() {
            return resultType;
        }

        public void setResultType(Type resultType) {
            this.resultType = resultType;
        }

        @Override
        public String toString() {
            return "Runner{" +
                    "sql='" + sql + '\'' +
                    ", params=" + params +
                    ", resultType=" + resultType +
                    '}';
        }
    }
}

第四步:执行JDBC

public static Object execute(SqlFactoryUtil.Runner runner) throws Exception {
    // 返回值类型
    Type resultType = runner.getResultType();

    // 返回值类型为 boolean
    if (resultType.getTypeName().toLowerCase().equals(BOOLEAN)) {
        return baseMethod(runner.getSql(), runner.getParams());
    }

    // 查询类型需要特殊处理
    ResultSet resultSet = select(runner.getSql(), runner.getParams());

    // 返回值
    Class type = getResultType(resultType);

    if (resultType.getTypeName().contains("java.util.List")) {
        return listHandle(resultSet, type);
    }

    return beanHandle(resultSet, type);
}

第五步:根据返回值类型构建最终结果

/***
 * List 类型handle
 * @param resultSet     resultSet
 * @param resultType    返回值类型
 * @return              List
 * @throws SQLException SQLException
 */
private static List listHandle (ResultSet resultSet, Class resultType) throws SQLException {

    // 返回结果
    List result = new ArrayList();

    ResultSetMetaData metaData = resultSet.getMetaData();
    int columnCount = metaData.getColumnCount();
    String[] columnNames = new String[columnCount];
    for (int i = 0; i < columnCount; i++) {
        columnNames[i] = metaData.getColumnName(i + 1);
    }
    while (resultSet.next()) {
        JSONObject object = new JSONObject();
        for (String columnName : columnNames) {
            Object columnValue = resultSet.getObject(columnName);
            object.put(columnName, columnValue);
        }

        result.add(object.toJavaObject(resultType));
    }
    return result;
}

总结:以上代码都是按照此思路截取的一部分,其实按照思路来,自己攻略到第二步,第三步,慢慢的就都摸清了,关键就是如何让接口被代理,剩下的都是水到渠成,遇到一个问题解决一个问题即可.

全部代码可见GitHub:https://github.com/kkzhilu/KerwinTools/tree/master/src/main/java/mebatis

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值