手写简易的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 ...
随机推荐
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- a标签伪类link,hover,active,visited,focus区别
<div id="content"> <h3><a class="a1" href="#">a标签伪类l ...
- Android学习笔记ActionView
概念 案例 1.布局文件 activity_main.xml <?xml version="1.0" encoding="utf-8"?> < ...
- Laravel:No application encryption key has been specified.
其实吧,这个就是你没有生成密钥 你首先去看看,如果是刚刚下载的lavavel应该会有一个.env.example文件在根目录下,然后修改这个文件名,改成.env 然后用命令行去执行php artisa ...
- Andrew Ng - 深度学习工程师 - Part 2. 改善深层神经网络:超参数调试、正则化以及优化(Week 2. 优化算法)
===========第2周 优化算法================ ===2.1 Mini-batch 梯度下降=== epoch: 完整地遍历了一遍整个训练集 ===2.2 理解Mini-bat ...
- docker配置国内镜像地址,解决无法pull镜像问题docker: Error response from daemon
问题: 执行命令 $ docker run -it --rm -p 8888:8080 tomcat:8.5.32 报错 Unable to find image 'tomcat:8.5.32' lo ...
- redis基础二----操作hash
上面usr就是hash的名字,usr这个hash中存储了key 为id.name和age的值 一个hash相当于一个数据对象,里面可以存储key为id name age的值 2.批量插入一个hash数 ...
- 前后端分层架构MVC&MVVM
早期 特点 页面由 JSP.PHP 等工程师在服务端生成 JSP 里揉杂大量业务代码 浏览器负责展现,服务端给什么就展现什么,展现的控制在 Web Server 层 优点 简单明快,本地起一个 Tom ...
- 如何下载 Ubuntu 镜像文件?
Ubuntu,是一款基于 Debian Linux 的以桌面应用为主的操作系统,内容涵盖文字处理.电子邮件.软件开发工具和 Web 服务等,可供用户免费下载.使用和分享. 但是对于国内的用户来说如果直 ...
- python fabric安装
1 安装epel wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo 2 安装pip yum i ...