一个connection对象可以创建一个或一个以上的statement对象_从 0 开始手写一个 Mybatis 框架,三步搞定...
来自:开源中国,作者:我叫刘半仙
链接:https://my.oschina.net/liughDevelop/blog/1631006
MyBatis框架的核心功能其实不难,无非就是动态代理和jdbc的操作,难的是写出来可扩展,高内聚,低耦合的规范的代码。本文完成的Mybatis功能比较简单,代码还有许多需要改进的地方,大家可以结合Mybatis源码去动手完善。
一、Mybatis框架流程简介
在手写自己的Mybatis框架之前,我们先来了解一下Mybatis,它的源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,才能够更深入的理解源码(ref:Mybatis源码解读-设计模式总结http://www.crazyant.net/2022.html)。我们对上图进行分析总结:
1、mybatis的配置文件有2类
mybatisconfig.xml,配置文件的名称不是固定的,配置了全局的参数的配置,全局只能有一个配置文件。
Mapper.xml 配置多个statemement,也就是多个sql,整个mybatis框架中可以有多个Mappe.xml配置文件。
2、通过mybatis配置文件得到SqlSessionFactory
3、通过SqlSessionFactory得到SqlSession,用SqlSession就可以操作数据了。
4、SqlSession通过底层的Executor(执行器),执行器有2类实现:
基本实现
带有缓存功能的实现
5、MappedStatement是通过Mapper.xml中定义statement生成的对象。
6、参数输入执行并输出结果集,无需手动判断参数类型和参数下标位置,且自动将结果集映射为Java对象
HashMap,KV格式的数据类型
Java的基本数据类型
POJO,java的对象
二、梳理自己的Mybatis的设计思路
根据上文Mybatis流程,我简化了下,分为以下步骤:
1、读取xml文件,建立连接
从图中可以看出,MyConfiguration负责与人交互。待读取xml后,将属性和连接数据库的操作封装在MyConfiguration对象中供后面的组件调用。本文将使用dom4j来读取xml文件,它具有性能优异和非常方便使用的特点。
2、创建SqlSession,搭建Configuration和Executor之间的桥梁
我们经常在使用框架时看到Session,Session到底是什么呢?一个Session仅拥有一个对应的数据库连接。类似于一个前段请求Request,它可以直接调用exec(SQL)来执行SQL语句。从流程图中的箭头可以看出,MySqlSession的成员变量中必须得有MyExecutor和MyConfiguration去集中做调配,箭头就像是一种关联关系。我们自己的MySqlSession将有一个getMapper方法,然后使用动态代理生成对象后,就可以做数据库的操作了。
3、创建Executor,封装JDBC操作数据库
Executor是一个执行器,负责SQL语句的生成和查询缓存(缓存还没完成)的维护,也就是jdbc的代码将在这里完成,不过本文只实现了单表,有兴趣的同学可以尝试完成多表。
4、创建MapperProxy,使用动态代理生成Mapper对象
我们只是希望对指定的接口生成一个对象,使得执行它的时候能运行一句sql罢了,而接口无法直接调用方法,所以这里使用动态代理生成对象,在执行时还是回到MySqlSession中调用查询,最终由MyExecutor做JDBC查询。这样设计是为了单一职责,可扩展性更强。
三、实现自己的Mybatis
工程文件及目录:
首先,新建一个maven项目,在pom.xml中导入以下依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <groupId>com.liughgroupId> <artifactId>liugh-mybatisartifactId> <version>0.0.1-SNAPSHOTversion> <packaging>jarpackaging>
<properties> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <maven.compiler.source>1.8maven.compiler.source> <maven.compiler.target>1.8maven.compiler.target> <java.version>1.8java.version> properties>
<dependencies>
<dependency> <groupId>dom4jgroupId> <artifactId>dom4jartifactId> <version>1.6.1version> dependency>
<dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <version>5.1.29version> dependency> dependencies>project>
创建我们的数据库xml配置文件:
<?xml version="1.0" encoding="UTF-8"?><database> <property name="driverClassName">com.mysql.jdbc.Driverproperty> <property name="url">jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8property> <property name="username">rootproperty> <property name="password">123456property>database>
然后在数据库创建test库,执行如下SQL语句:
CREATE TABLE `user` ( `id` varchar(64) NOT NULL, `password` varchar(255) DEFAULT NULL, `username` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;INSERT INTO `test`.`user` (`id`, `password`, `username`) VALUES ('1', '123456', 'liugh');
创建User实体类,和UserMapper接口和对应的xml文件:
package com.liugh.bean;
public class User { private String id; private String username; private String password; //省略get set toString方法...}
package com.liugh.mapper;import com.liugh.bean.User;public interface UserMapper { public User getUserById(String id); }
<?xml version="1.0" encoding="UTF-8"?><mapper nameSpace="com.liugh.mapper.UserMapper"> <select id="getUserById" resultType ="com.liugh.bean.User"> select * from user where id = ? select>mapper>
基本操作配置完成,接下来我们开始实现MyConfiguration:
package com.liugh.sqlSession;
import java.io.InputStream;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.ArrayList;import java.util.Iterator;import java.util.List;import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.Element;import org.dom4j.io.SAXReader;import com.liugh.config.Function;import com.liugh.config.MapperBean;
/** * 读取与解析配置信息,并返回处理后的Environment */public class MyConfiguration { private static ClassLoader loader = ClassLoader.getSystemClassLoader();
/** * 读取xml信息并处理 */ public Connection build(String resource){ try { InputStream stream = loader.getResourceAsStream(resource); SAXReader reader = new SAXReader(); Document document = reader.read(stream); Element root = document.getRootElement(); return evalDataSource(root); } catch (Exception e) { throw new RuntimeException("error occured while evaling xml " + resource); } }
private Connection evalDataSource(Element node) throws ClassNotFoundException { if (!node.getName().equals("database")) { throw new RuntimeException("root should be "); } String driverClassName = null; String url = null; String username = null; String password = null; //获取属性节点 for (Object item : node.elements("property")) { Element i = (Element) item; String value = getValue(i); String name = i.attributeValue("name"); if (name == null || value == null) { throw new RuntimeException("[database]: should contain name and value"); } //赋值 switch (name) { case "url" : url = value; break; case "username" : username = value; break; case "password" : password = value; break; case "driverClassName" : driverClassName = value; break; default : throw new RuntimeException("[database]: unknown name"); } }
Class.forName(driverClassName); Connection connection = null; try { //建立数据库链接 connection = DriverManager.getConnection(url, username, password); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return connection; }
//获取property属性的值,如果有value值,则读取 没有设置value,则读取内容 private String getValue(Element node) { return node.hasContent() ? node.getText() : node.attributeValue("value"); }
@SuppressWarnings("rawtypes") public MapperBean readMapper(String path){ MapperBean mapper = new MapperBean(); try{ InputStream stream = loader.getResourceAsStream(path); SAXReader reader = new SAXReader(); Document document = reader.read(stream); Element root = document.getRootElement(); mapper.setInterfaceName(root.attributeValue("nameSpace").trim()); //把mapper节点的nameSpace值存为接口名 List<Function> list = new ArrayList<Function>(); //用来存储方法的List for(Iterator rootIter = root.elementIterator();rootIter.hasNext();) {//遍历根节点下所有子节点 Function fun = new Function(); //用来存储一条方法的信息 Element e = (Element) rootIter.next(); String sqltype = e.getName().trim(); String funcName = e.attributeValue("id").trim(); String sql = e.getText().trim(); String resultType = e.attributeValue("resultType").trim(); fun.setSqltype(sqltype); fun.setFuncName(funcName); Object newInstance=null; try { newInstance = Class.forName(resultType).newInstance(); } catch (InstantiationException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } fun.setResultType(newInstance); fun.setSql(sql); list.add(fun); } mapper.setList(list);
} catch (DocumentException e) { e.printStackTrace(); } return mapper; }}
用面向对象的思想设计读取xml配置后:
package com.liugh.config;
import java.util.List;public class MapperBean { private String interfaceName; //接口名 private List<Function> list; //接口下所有方法 //省略 get set方法...}
Function对象包括sql的类型、方法名、sql语句、返回类型和参数类型。
package com.liugh.config;
public class Function { private String sqltype; private String funcName; private String sql; private Object resultType; private String parameterType; //省略 get set方法}
接下来实现我们的MySqlSession,首先的成员变量里得有Excutor和MyConfiguration,代码的精髓就在getMapper的方法里。
package com.liugh.sqlSession;
import java.lang.reflect.Proxy;
public class MySqlsession {
private Excutor excutor= new MyExcutor();
private MyConfiguration myConfiguration = new MyConfiguration();
public T selectOne(String statement,Object parameter){ return excutor.query(statement, parameter); } @SuppressWarnings("unchecked")public T getMapper(Class clas){ //动态代理调用return (T)Proxy.newProxyInstance(clas.getClassLoader(),new Class[]{clas},new MyMapperProxy(myConfiguration,this)); } }
紧接着创建Excutor和实现类:
package com.liugh.sqlSession;
public interface Excutor { public T query(String statement,Object parameter); }
MyExcutor中封装了JDBC的操作:
package com.liugh.sqlSession;
import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import com.liugh.bean.User;
public class MyExcutor implements Excutor{
private MyConfiguration xmlConfiguration = new MyConfiguration();
@Override public T query(String sql, Object parameter) { Connection connection=getConnection(); ResultSet set =null; PreparedStatement pre =null;try { pre = connection.prepareStatement(sql); //设置参数 pre.setString(1, parameter.toString());set = pre.executeQuery(); User u=new User(); //遍历结果集while(set.next()){ u.setId(set.getString(1)); u.setUsername(set.getString(2)); u.setPassword(set.getString(3)); } return (T) u; } catch (SQLException e) { e.printStackTrace(); } finally{try{ if(set!=null){ set.close(); }if(pre!=null){ pre.close(); }if(connection!=null){ connection.close(); } }catch(Exception e2){ e2.printStackTrace(); } } return null; } private Connection getConnection() { try { Connection connection =xmlConfiguration.build("config.xml");return connection; } catch (Exception e) { e.printStackTrace(); } return null; } }
MyMapperProxy代理类完成xml方法和真实方法对应,执行查询:
package com.liugh.sqlSession;
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.List;import com.liugh.config.Function;import com.liugh.config.MapperBean;
public class MyMapperProxy implements InvocationHandler{
private MySqlsession mySqlsession;
private MyConfiguration myConfiguration;
public MyMapperProxy(MyConfiguration myConfiguration,MySqlsession mySqlsession) { this.myConfiguration=myConfiguration; this.mySqlsession=mySqlsession; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MapperBean readMapper = myConfiguration.readMapper("UserMapper.xml"); //是否是xml文件对应的接口 if(!method.getDeclaringClass().getName().equals(readMapper.getInterfaceName())){ return null; } List<Function> list = readMapper.getList(); if(null != list || 0 != list.size()){ for (Function function : list) { //id是否和接口方法名一样 if(method.getName().equals(function.getFuncName())){ return mySqlsession.selectOne(function.getSql(), String.valueOf(args[0])); } } } return null; }}
到这里,就完成了自己的Mybatis框架,我们测试一下:
package com.liugh;
import com.liugh.bean.User;import com.liugh.mapper.UserMapper;import com.liugh.sqlSession.MySqlsession;
public class TestMybatis {
public static void main(String[] args) { MySqlsession sqlsession=new MySqlsession(); UserMapper mapper = sqlsession.getMapper(UserMapper.class); User user = mapper.getUserById("1"); System.out.println(user); } }
执行结果:
查询一个不存在的用户试试:
到这里我们就大功告成了!
我是个普通的程序猿,水平有限,文章难免有错误,欢迎牺牲自己宝贵时间的读者,就本文内容直抒己见,我的目的仅仅是希望对读者有所帮助。源码地址:https://github.com/qq53182347/liugh-mybatis
一个connection对象可以创建一个或一个以上的statement对象_从 0 开始手写一个 Mybatis 框架,三步搞定...相关推荐
- 从 0 开始手写一个 Spring MVC 框架,向高手进阶
转载自 从 0 开始手写一个 Spring MVC 框架,向高手进阶 Spring框架对于Java后端程序员来说再熟悉不过了,以前只知道它用的反射实现的,但了解之后才知道有很多巧妙的设计在里面.如 ...
- jquery手写轮播图_用jQuery如何手写一个简单的轮播图?(附代码)
用jQuery如何手写一个简单的轮播图?下面本篇文章通过代码示例来给大家介绍一下.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. 用 jQuery 手写轮播图 先上个效果截图: 主要 ...
- 从 0 开始手写一个 Mybatis 框架,三步搞定!
最近研究了一下Mybatis,给大家磕叨磕叨,MyBatis框架的核心功能其实不难,无非就是动态代理和jdbc的操作,难的是写出来可扩展,高内聚,低耦合的规范的代码.本文完成的Mybatis功能比较简 ...
- 从0开始手写一个 SpringMVC 框架,向高手进阶!
Spring框架对于Java后端程序员来说再熟悉不过了,以前只知道它用的反射实现的,但了解之后才知道有很多巧妙的设计在里面.如果不看Spring的源码,你将会失去一次和大师学习的机会:它的代码规范,设 ...
- javascript从0到0.9手写一个windows计算器
说实话,最初想用javascript模拟着windows的计算器写一个的时候,感觉也就是10分钟搞定,但写着写着发现,其实并不是那么容易的事,window的这个计算器逻辑挺多的. 而且还想给别人把这个 ...
- python 如何判断一个函数执行完成_三步搞定 Python 中的文件操作
当程序运行时,变量是保存数据的好方法,但变量.序列以及对象中存储的数据是暂时的,程序结束后就会丢失,如果希望程序结束后数据仍然保持,就需要将数据保存到文件中. Python 提供了内置的文件对象,以及 ...
- 从0开始手写一个类似Linux的操作系统
一.效果 这个操作系统取名为winux 主要实现了内存管理.线程.进程.文件系统.shell.进程间通信,是用来学习linux操作系统的不错选择,自从我做完这么一个系统再去看<linux内核设计 ...
- python手写一个迭代器_搞清楚 Python 的迭代器、可迭代对象、生成器
很多伙伴对 Python 的迭代器.可迭代对象.生成器这几个概念有点搞不清楚,我来说说我的理解,希望对需要的朋友有所帮助. 1 迭代器协议 迭代器协议是核心,搞懂了这个,上面的几个概念也就很好理解了. ...
- 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理
动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...
最新文章
- V3S中换2.0寸LCD时MINIGUI无显示的问题的解决过程
- 速读训练软件_记忆力训练:如何提高注意力呢?
- python字符串能减吗_在python中减去两个字符串(Subtract two strings in python)
- 使用Async对Angular异步代码进行单元测试
- 如何在Java中同步ArrayList?
- hikari如何切换数据源_如何使用Spring为HikariCP设置数据源?
- 重造车轮—基于JQ的商品展示放大镜
- MAC可以在.zshrc中修改PATH
- 点石成金:访客至上的网页设计秘笈(原书第2版) 中文PDF版
- TensorFlow构建模型(图片数据加载)六
- f4 OF DATE FIELD IN DYNPRO PROGRAME
- 如何用PS缩小图片而清晰度不变?
- Cesium 概述 (一)
- SHT2x系列数字温湿度传感器
- The server time zone value ‘‘ is unrecognized or represents more than one time zone.
- flutter安装_在macOS上搭建Flutter开发环境
- Android笔记:将布局转换成图片
- 杜骡的前端面试题(大全)
- 【填充插件】自定义填充图案制作插件
- 《“桥板灯”的来历》——游城“明经胡氏”
热门文章
- 历史数据导出excel_在数据产品中对导出功能的思考
- php变量值传递,PHP将值传递到包含文件中的变量
- radiobuttonlist 后面追加操作按钮_【进口知识】通关无纸化签约及代理报关委托收发操作指南...
- frida hook java 函数_使用 Frida 来 Hook Java 类中的构造函数(构造函数带重载),获取解密后的js脚本...
- Python界面程序实例:按钮漂移,用Python小套路来撩女神
- 人脸关键点:DAN-Deep Alignment Network: A convolutional neural network for robust face alignment
- AcWing1083. Windy数(数位DP)题解
- 保存一个 Python 对象,之后使用时直接读取
- Python 判断变量类型
- ~~队列(数据结构)(附模板题 AcWing 829. 模拟队列)