手写简易的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

手写简易的Mybatis的更多相关文章

  1. mybatis(八)手写简易版mybatis

    一.画出流程图 二.设计核心类 二.V1.0 的实现 创建一个全新的 maven 工程,命名为 mebatis,引入 mysql 的依赖. <dependency> <groupId ...

  2. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...

  3. JDK动态代理深入理解分析并手写简易JDK动态代理(上)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...

  4. 【教程】手写简易web服务器

    package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...

  5. 手写简易SpringMVC

    手写简易SpringMVC 手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器 必备知识: Servlet相关理解和使用,Maven,Java ...

  6. Java多线程之Executor框架和手写简易的线程池

    目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...

  7. 手写简易WEB服务器

    今天我们来写一个类似于Tomcat的简易服务器.可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!首先我们要准备的知识是: Socket编程 HTML HTTP协议 服务器 ...

  8. 手写简易版RPC框架基于Socket

    什么是RPC框架? RPC就是远程调用过程,实现各个服务间的通信,像调用本地服务一样. RPC有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...

  9. JavaScript之Promise实现原理(手写简易版本 MPromise)

    手写 Promise 实现 Promise的基本使用 Promise定义及用法详情文档:Promise MAD文档 function testPromise(param) { return new P ...

随机推荐

  1. numpy.stack和numpy.concatenate的区别

    在使用numpy进行矩阵运算的时候踩到的坑,原因是不能正确区分numpy.concatenate和numpy.stack在功能上的差异. 先说numpy.concatenate,直接看文档: nump ...

  2. 用VC++6.0,双击主对话框中的按钮时,不能跳转到代码处

    1. 首先在项目中--[生成]build--[清除解决方案]clean 2. 关闭项目 3. 删除项目中的[Debug]下所有文件 4. 把*.aps,*.clw,*.ncb,*.opt删掉----- ...

  3. Python编程快速上手 让繁琐工作自动化 - 专业程序员的养成完整版PDF免费下载_百度云盘

    Python编程快速上手  让繁琐工作自动化 - 专业程序员的养成完整版PDF免费下载_百度云盘 提取码:7qm4 有关本书 累计销售超过10万册 零基础编程者的不二之选 基于Python3编写 通用 ...

  4. 本地代码提交到远程仓库(git)

    [准备环境] 我没有在Linux搭建gitlab私有云服务器,用的是开源的 gitee托管平台 1.在gitee注册账号 2.本地下载git客户端 [步骤] 1  本地新建1个文件夹  进入文件夹后 ...

  5. vue 入门, todoList

    #  实现逻辑: > * 技术栈> 1. 生命周期,creatd( 创建后执行)> 2. methods': 调用事件方法,结果不会缓存> 3. Watch : 监听器,监听输 ...

  6. 吃货联盟订餐系统 源代码 Java初级小项目

    咳咳,今天博主给大家写一个小的项目:吃货联盟订餐系统.博主不是大神(互联网架构师的路上ing),也是小白一个,不过是刚入门的小白^_^.项目功能也很简单:只是模拟日常的订餐流程呦,所以有错误以及功能不 ...

  7. 05.DBUnit的使用

    相信做过单元测试的人都会对JUnit 非常的熟悉了,今天要介绍的DbUnit(http://dbunit.sourceforge.net/ ) 则是专门针对数据库测试的对JUnit 的一个扩展,它可以 ...

  8. MySql数据库GROUP BY使用过程中的那些坑

    MySql数据库GROUP BY使用过程中的那些坑 GROUP BY 语句用于结合合计函数,根据一个或多个列对结果集进行分组. 特别注意: group by 有一个原则,就是 select 后面的所有 ...

  9. 手把手教你使用Python抓取QQ音乐数据(第二弹)

    [一.项目目标] 通过Python爬取QQ音乐数据(一)我们实现了获取 QQ 音乐指定歌手单曲排行指定页数的歌曲的歌名.专辑名.播放链接. 此次我们在之前的基础上获取QQ音乐指定歌曲的歌词及前15个精 ...

  10. LINUX 下 一些常用的信息显示命令:

    tcsh——shell程序,它可以在登录shell和shell 脚本命令处理器之间做命令语言解释器.stat——显示指定文件的相关信息who.w——显示在线登陆用户whoami——显示用户自己的身份h ...