手写简易的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 ...
随机推荐
- Linux服务器安装python3.6
CentOS 7上默认安装的python版本是2.7.5,系统自带的旧版本python被系统很多其他软件环境依赖,因此不能卸载原Python,直接选择Python3.6.5进行全新安装. 1 安装Py ...
- 看到这些常见的android面试题,你慌了吗?
最近参加了一些Android工程师岗位的面试,总结了一些常见的考点,希望能帮到正在面试的你(答案还在整理中)! 1.Java调用函数传入实际参数时,是值传递还是引用传递? 2.单例模式的DCL方式,为 ...
- ca73a_c++_流的条件状态
/*ca73a_c++_流的条件状态strm::iostate strm::badbit //流的状态strm::failbit //输入的状态,应该输入数字,结果输入为字符,strm::eofbit ...
- visual studio 2005/2010/2013/2015/2017 vc++ c#代码编辑常用快捷键-代码编辑器的展开和折叠
visual studio 2005/2010/2013/2015/2017 vc++ c#代码编辑快捷键-代码编辑器的展开和折叠 VS2015代码编辑器的展开和折叠代码确实很方便和实用.以下是展开代 ...
- python-判断、循环、列表、字典
一.如何将两个列表合并成一个字典 运用dict(zip()) 例如: usernames = ['xiaohei', 'xiaobai', 'xiaoming'] passwords = ['1234 ...
- uni-app之实现分页
一.下载库 官方文档地址为:https://ext.dcloud.net.cn/plugin?id=32 点击下载zip压缩包即可,下载完毕后解压到放置前端相关组件目录,即components目录. ...
- JavaWeb网上图书商城完整项目-数据库操作工具类2-MapHandle的高级用法
1.现在在上面一章的基础上,我们引入一个address表,该表记录person类的地址,address表的格式如下所示 现在person类要和address表想关联,得到当前联系人的住宅地址,我们应该 ...
- 音视频前沿:新一代 AV1 视频标准究竟是怎样一种存在?
AV1是开放媒体联盟Alliance for Open Media (AOM) 开发的第一代视频编码标准,自推出以来获得了产业界巨大关注和支持.腾讯多媒体实验室也加入进来和其他公司团队一同积极推动AV ...
- VS2017 快捷键
VS2017注释:先CTRL+K 然后CTRL+C (ctrl按住不松,松开k按c) 取消注释:先CTRL+K,然后CTRL+U (ctrl按住不松,松开k按c)
- GitHub 热点速览 Vol.26:手把手带你做数据库
作者:HelloGitHub-小鱼干 摘要:手把手带你学知识,应该是学习新知识最友好的姿势了.toyDB 虽然作为一个"玩具"项目不能应用在实际开发中,但通过它你可以了解到如何制作 ...