手写简易的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
必要知识储备:
- 基于Java的Mysql应用 -> JDBC
- JDK 动态代理技术
- Java 自定义注解的使用
- Java 正则表达式
- Java 反射相关知识
- 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的更多相关文章
- mybatis(八)手写简易版mybatis
一.画出流程图 二.设计核心类 二.V1.0 的实现 创建一个全新的 maven 工程,命名为 mebatis,引入 mysql 的依赖. <dependency> <groupId ...
- JDK动态代理深入理解分析并手写简易JDK动态代理(下)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...
- JDK动态代理深入理解分析并手写简易JDK动态代理(上)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...
- 【教程】手写简易web服务器
package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...
- 手写简易SpringMVC
手写简易SpringMVC 手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器 必备知识: Servlet相关理解和使用,Maven,Java ...
- Java多线程之Executor框架和手写简易的线程池
目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...
- 手写简易WEB服务器
今天我们来写一个类似于Tomcat的简易服务器.可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!首先我们要准备的知识是: Socket编程 HTML HTTP协议 服务器 ...
- 手写简易版RPC框架基于Socket
什么是RPC框架? RPC就是远程调用过程,实现各个服务间的通信,像调用本地服务一样. RPC有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...
- JavaScript之Promise实现原理(手写简易版本 MPromise)
手写 Promise 实现 Promise的基本使用 Promise定义及用法详情文档:Promise MAD文档 function testPromise(param) { return new P ...
随机推荐
- CentOS Linux release 7.7.1908 (Core)--rabbitmq安装
1.连接服务器,输入账号和密码,密码输入的时候是看不见的,只管输就行,然后回车. 2.安装Erlang,RabbitMQ是用这门语言写的,所以要安装他. 3.安装filezilia就是win和linu ...
- pip未找到
命令终端运行 sudo easy_install pip 安装成功后最后会显示 Installed /Library/Python/2.7/site-packages/pip-9.0.1-py2.7. ...
- 004.OpenShift命令及故障排查
一 CLI访问OpenShift资源 1.1 资源操作 OCP将OpenShift集群中的为由主节点管理的对象统称为资源,如:node.service.pod.project.deployment.u ...
- WeChair项目Beta冲刺(4/10)
团队项目进行情况 1.昨日进展 Beta冲刺第四天 昨日进展: 前后端并行开发,项目按照计划有条不絮进行 2.今日安排 前端:扫码占座功能和预约功能并行开发 后端:扫码占座后端逻辑和预约功能逻辑 ...
- C语言副本机制
1.除了数组外,其他都有副本机制(包括结构体数组) 2.结构体作为参数具有副本机制,结构体返回值也有副本机制 . 3.函数的参数和返回值都有他的副本机制. #include<stdio.h> ...
- skywalking面板功能介绍2
场景: spring-user调用spring-order 1.spring-user部署了两个应用实例 2.spring-order部署了一个实例 应用详情信息在表 从上面表中可以看出spring- ...
- 并发编程,python的进程,与线程
并发编程 操作系统发展史 基于单核研究 多道技术 1.空间上的复用 多个程序公用一套计算机硬件 2.时间上的复用 切换+保存状态 例子:洗衣 烧水 做饭 切换 1.程序遇到IO操作系统会立刻剥夺走CP ...
- vs2010调试运行时弹出对话框:系统找不到指定文件
很多时候,我们会将一些低版本IDE编译过的项目,搬迁到VS2010 ,那么会存在很多编译,调试问题.[1] 编译成功了.可是无法调试 . . 显示 无法启动程序“...........\t ...
- Mybatis 动态insert语句
mybatis的一个比较先进的思想是把Sql语句写在了配置xml文件(也支持注解),通过配置文件的方式,免去了一般软件开发的硬编码,当业务需求改变的时候,只需要更改sql语句即可! 下面是个人在学习m ...
- SQL循环遍历,删除表里某一列是重复的数据,只保留一条。
DECLARE @tempId NVARCHAR(Max), @tempIDD uniqueidentifier WHILE EXISTS ( SELECT UserId FROM Users Gro ...