如何开发一个ORM框架

ORM(Object Relational Mapping)对象关系映射,ORM的数据库框架有hibernate,mybatis。我该如何开发一个类似这样的框架呢?

为什么会有这种疑问?

首先我想快速(注意快速一词)对数据库CURD发现,用hibernate要一堆配置、mybatis更多配置,于是我gitee上随便搜发现一堆,顺手拿一个推荐 300左右start的来用,用了几个小时看了看底层源码,连基本查询都有bug,selectOne竟然是全表扫描的list.get(0),无语了,多数据库兼容也不知道说了啥。 其实性能不性能无所谓,能快速CURD就行啦,但是你有bug我就受不了了,你又不是萌妹子,我不想深入了解你。((小声:)你的架构很糟糕)

于是 final-sql 诞生了:https://gitee.com/lingkang_top/final-sql
性能对比:mybatis、hibernate、final-sql性能对比https://gitee.com/lingkang_top/final-sql/wikis/nature%20%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94hibernate%E3%80%81mybatis

扯远了,回到标题,如何开发一个ORM,首先要有以下认知。

  • 连接池原理
  • java的jdbc接口基本调用
  • 实体对应SQL生成
  • 事务处理
  • 不同数据库兼容(可以参考方言处理)
    基于以上思路,开始开发。

1、连接池+jdbc基本调用

首先创建一个普通springboot项目<spring-boot.version>2.5.12</spring-boot.version>
连接池就自己去查资料吧,我们直接拿一个连接池来用 druid 引入Maven

 <!--使用阿里的连接池监控 http://druid.apache.org/-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>

简单配置一下

spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource #使用阿里数据源druid
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456

druid 已经帮我们在springboot中自动装配了DruidDataSourceAutoConfigure
直接使用

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet; /**
* @author lingkang
* Created by 2022/4/20
*/
@RestController
public class DemoController { @Autowired // DruidDataSourceAutoConfigure
private DataSource dataSource; @GetMapping("demo")
public Object demo() throws Exception {
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("select * from user");
ResultSet resultSet = preparedStatement.executeQuery();
String res = "";
while (resultSet.next()) {
for (int i = 1; i < resultSet.getMetaData().getColumnCount(); i++) {
res += resultSet.getMetaData().getColumnName(i) + "=" + resultSet.getObject(i) + ", ";
}
res += "\n";
}
System.out.println(res); // DataSource是 druid 的实现,不会真正释放连接,而是放回到 druid 中
// 开发框架时一定要释放连接,否则会一直占用不放回连接池,导致数据库连接耗尽
connection.close();
return res;
}
}


上面就实现了基本的jdbc调用,接下来就是实体映射了

2、实体映射

实体映射我们得定义表注解、列注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Table {
String value() default "";// 用于声明表名
}
import java.lang.annotation.*;

/**
* @author lingkang
* Created by 2022/4/11
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
String value() default "";// 列名称
}

再用上面的注解创建我们的user表映射

/**
* @author lingkang
* Created by 2022/4/20
*/
@Data
@Table("user")
public class MyUser {
@Column
private Integer id;
@Column
private Integer num;
@Column
private String username;
@Column
private String password; private Date createTime;
}

接下来将它生成对应的sql,这时有许多数据库sql协议,我们按熟悉的mysql来。

 // 生成执行的sql
private <T> Map<String,Object> entityToSelectSql(T entity) throws Exception{
String sql = "select "; List<String> where=new ArrayList<>();
List<Object> param=new ArrayList<>();
// 获取列名
Field[] declaredFields = entity.getClass().getDeclaredFields();
for (Field field : declaredFields) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
String value = column.value();
if ("".equals(value)) {// 说明没有自定义列名称
// 直接拿对象属性当做列,,,,,,例如做一些驼峰命名处理
value = field.getName();
}
sql += value + ", ";
// 参数条件
field.setAccessible(true);
Object o = field.get(entity);
if (o!=null){
param.add(o);
where.add(value);
}
}
} sql = sql.substring(0, sql.length() - 2);// 删除后面的 ", " // 获取表名
Table tableAnn = entity.getClass().getAnnotation(Table.class);
String table = tableAnn.value();
sql+=" from "+table; // 整理条件
sql+=" where 1=1 "; // 别问为什么 1=1 因为偷懒
for (String w:where){
sql+="and "+w+"=? ";
}
Map<String,Object> map=new HashMap<>();// 偷懒,直接用map
map.put("sql",sql);
map.put("param",param);
return map;
} // 处理结果
private <T> List<T> handlerResult(ResultSet resultSet,T entity)throws Exception{
List<T> list=new ArrayList<>();
// 获取列名
Class<?> clazz = entity.getClass();
while (resultSet.next()){
T en =(T) clazz.newInstance();// 实例化
for (Field field : clazz.getDeclaredFields()) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
String value = column.value();
if ("".equals(value)) {// 说明没有自定义列名称
// 直接拿对象属性当做列
value = field.getName();
}
Object object = resultSet.getObject(value, field.getType());
Field declaredField = en.getClass().getDeclaredField(value);
declaredField.setAccessible(true);
declaredField.set(en,object);// 设置值
}
}
list.add((T) en);// 结果处理完
}
return list;
}

调用查询

    @GetMapping("demo1")
public Object demo1()throws Exception{
MyUser user=new MyUser();
user.setUsername("lingkang");// 条件
// 生成sql
Map<String, Object> map = entityToSelectSql(user);
System.out.println(map.get("sql"));
PreparedStatement statement = dataSource.getConnection().prepareStatement(map.get("sql").toString());
// 添加条件
List<Object> param = (List<Object>) map.get("param");
for (int i=1;i<=param.size();i++){
statement.setObject(i,param.get(i-1));
}
// 执行查询
ResultSet resultSet = statement.executeQuery();
// 处理结果
List<MyUser> myUsers = handlerResult(resultSet, user);
System.out.println(myUsers);// 打印输出
return myUsers;
}

结果如下

3、事务处理

事务处理需要保证每次获取的连接都是同一个,我们用线程变量来实现 ThreadLocal

// 统一获取连接入口
private Connection getConnection() throws Exception {
Connection connection = threadLocal.get();
if (connection == null) {
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} private void begin() throws Exception {
Connection connection = threadLocal.get();
if (connection == null) {
connection=getConnection();
connection.setAutoCommit(false);// 开始事务
threadLocal.set(connection);
}
} private void commit() throws Exception {
Connection connection = threadLocal.get();
if (connection == null)
throw new Exception("事务未开启!");
connection.commit(); // 提交事务
connection.close();// 几等关闭连接
} private void rollback() throws Exception {
Connection connection = threadLocal.get();
if (connection == null)
throw new Exception("事务未开启!");
connection.rollback(); // 回滚事务
connection.close();// 几等关闭连接
}

调用

    @GetMapping("demo2")
public Object demo2() throws Exception {
try {
begin();// 开启事务
// getConnection() 获取连接
getConnection().prepareStatement("update user set username='lingkang123' where id=6").executeUpdate();
getConnection().prepareStatement("delete from user where id=6").executeUpdate();
if (1 == 1)
throw new Exception("手动抛出一个异常让事务回滚");
commit();// 提交事务
} catch (Exception e) {
rollback();// 异常回滚事务
}
return "ok";
}

执行以上请求后,数据库数据未变动!
若整合spring,可根据spring-tx的TransactionSynchronizationManager来做到@Transactional 控制!

4、方言

方言是用来处理不同数据库sql协议有所差别的,例如mysql中查询一行是limit 1 informix数据库却是 select first 1
这时我们就可以在上面返回SQL时添加一个处理,根据不同数据库方言对最终SQL进行调整替换SQL语句。

    private int dialect=1;// mysql
private String selectOne(String sql){
if (dialect==1){
return sql+" limit 1";
}else if (dialect==2){
return sql.substring(7)+"select first 1 ";
}
throw new RuntimeException("解析数据库类型错误,请自行扩展方言");
}

总结

以上就是如何开发一个ORM基本底层原理了,架构与封装应该根据你的开发经验来。觉得不错,别忘了给我点个start
https://gitee.com/lingkang_top/final-sql

完整代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.lingkang.finalsql.annotation.Column;
import top.lingkang.finalsql.annotation.Table;
import top.lingkang.yuecommunity.entity.MyUser; import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; /**
* @author lingkang
* Created by 2022/4/20
*/
@RestController
public class DemoController { @Autowired // DruidDataSourceAutoConfigure
private DataSource dataSource; @GetMapping("demo")
public Object demo() throws Exception {
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("select * from user");
ResultSet resultSet = preparedStatement.executeQuery();
String res = "";
while (resultSet.next()) {
for (int i = 1; i < resultSet.getMetaData().getColumnCount(); i++) {
res += resultSet.getMetaData().getColumnName(i) + "=" + resultSet.getObject(i) + ", ";
}
res += "\n";
}
System.out.println(res); // DataSource是 druid 的实现,不会真正释放连接,而是放回到 druid 中
// 开发框架时一定要释放连接,否则会一直占用不放回连接池,导致数据库连接耗尽
connection.close();
return res;
} @GetMapping("demo1")
public Object demo1() throws Exception {
MyUser user = new MyUser();
user.setUsername("lingkang");// 条件
// 生成sql
Map<String, Object> map = entityToSelectSql(user);
System.out.println(map.get("sql"));
PreparedStatement statement = dataSource.getConnection().prepareStatement(map.get("sql").toString());
// 添加条件
List<Object> param = (List<Object>) map.get("param");
for (int i = 1; i <= param.size(); i++) {
statement.setObject(i, param.get(i - 1));
}
// 执行查询
ResultSet resultSet = statement.executeQuery();
// 处理结果
List<MyUser> myUsers = handlerResult(resultSet, user);
System.out.println(myUsers);// 打印输出
return myUsers;
} ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); @GetMapping("demo2")
public Object demo2() throws Exception {
try {
begin();// 开启事务
// getConnection() 获取连接
getConnection().prepareStatement("update user set username='lingkang123' where id=6").executeUpdate();
getConnection().prepareStatement("delete from user where id=6").executeUpdate();
if (1 == 1)
throw new Exception("手动抛出一个异常让事务回滚");
commit();// 提交事务
} catch (Exception e) {
rollback();// 异常回滚事务
}
return "ok";
} // 统一获取连接入口
private Connection getConnection() throws Exception {
Connection connection = threadLocal.get();
if (connection == null) {
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} private void begin() throws Exception {
Connection connection = threadLocal.get();
if (connection == null) {
connection=getConnection();
connection.setAutoCommit(false);// 开始事务
threadLocal.set(connection);
}
} private void commit() throws Exception {
Connection connection = threadLocal.get();
if (connection == null)
throw new Exception("事务未开启!");
connection.commit(); // 提交事务
connection.close();// 几等关闭连接
} private void rollback() throws Exception {
Connection connection = threadLocal.get();
if (connection == null)
throw new Exception("事务未开启!");
connection.rollback(); // 回滚事务
connection.close();// 几等关闭连接
} private int dialect=1;// mysql
private String selectOne(String sql){
if (dialect==1){
return sql+" limit 1";
}else if (dialect==2){
return sql.substring(7)+"select first 1 ";
}
throw new RuntimeException("解析数据库类型错误,请自行扩展方言");
} // 生成执行的sql
private <T> Map<String, Object> entityToSelectSql(T entity) throws Exception {
String sql = "select "; List<String> where = new ArrayList<>();
List<Object> param = new ArrayList<>();
// 获取列名
Field[] declaredFields = entity.getClass().getDeclaredFields();
for (Field field : declaredFields) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
String value = column.value();
if ("".equals(value)) {// 说明没有自定义列名称
// 直接拿对象属性当做列,,,,,,例如做一些驼峰命名处理
value = field.getName();
}
sql += value + ", ";
// 参数条件
field.setAccessible(true);
Object o = field.get(entity);
if (o != null) {
param.add(o);
where.add(value);
}
}
} sql = sql.substring(0, sql.length() - 2);// 删除后面的 ", " // 获取表名
Table tableAnn = entity.getClass().getAnnotation(Table.class);
String table = tableAnn.value();
sql += " from " + table; // 整理条件
sql += " where 1=1 "; // 别问为什么 1=1 因为偷懒
for (String w : where) {
sql += "and " + w + "=? ";
}
Map<String, Object> map = new HashMap<>();// 偷懒,直接用map
map.put("sql", sql);
map.put("param", param);
return map;
} // 处理结果
private <T> List<T> handlerResult(ResultSet resultSet, T entity) throws Exception {
List<T> list = new ArrayList<>();
// 获取列名
Class<?> clazz = entity.getClass();
while (resultSet.next()) {
T en = (T) clazz.newInstance();// 实例化
for (Field field : clazz.getDeclaredFields()) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
String value = column.value();
if ("".equals(value)) {// 说明没有自定义列名称
// 直接拿对象属性当做列
value = field.getName();
}
Object object = resultSet.getObject(value, field.getType());
Field declaredField = en.getClass().getDeclaredField(value);
declaredField.setAccessible(true);
declaredField.set(en, object);// 设置值
}
}
list.add((T) en);// 结果处理完
}
return list;
} }
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`num` int(11) DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8;

如何开发一个ORM数据库框架的更多相关文章

  1. ORM数据库框架 greenDAO SQLite MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  2. ORM数据库框架 LitePal SQLite MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. ORM数据库框架 SQLite 常用数据库框架比较 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. ORM数据库框架 SQLite ORMLite MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  5. 我们一起来动手开发一个Orm框架,开源发布

    我们追求的方向 1)高性能. 这也是架构创建的目的之一,已经将它的性能提升到了极致.大家可以自己测试.我可以说其性能是数一数二的.连接地址:Moon洗冤录 2)易用性强 我想,用过Moon.ORM的应 ...

  6. Android开源库--ActiveAndroid(active record模式的ORM数据库框架)

    Github地址:https://github.com/pardom/ActiveAndroid 前言 我一般在Android开发中,几乎用不到SQLlite,因为一些小数据就直接使用Preferen ...

  7. android高效ORM数据库框架greenDao使用

    因为项目中多处用到了数据库,需要对数据库频繁的读写操作,虽然android 自带的SQLiteOpenHelper的.这种方式比较方便易懂,但是在使用过程中需要写很多的sql语句,而且需要及时的关闭和 ...

  8. 使用go语言开发一个后端gin框架的web项目

    用liteide来开发go的后端项目,需要注意的是环境变量要配置正确了 主要是GOROOT, GOPATH, GOBIN, PATH这几个, GOPATH主要用来存放要安的包,主要使用go get 来 ...

  9. SunSonic 3.0 ORM开源框架的学习

    SubSonic 3.0简介 接触到SubSonic3.0 ORM框架是看了AllEmpty大神的从零开始编写自己的C#框架(链接在此)系列的随笔接触到的,本文章学习内容源于AllEmpty大神. S ...

  10. 开源的.Net ORM微型框架SuperHelper

    SuperHelper——灵活通用的.开源的.Net ORM微型框架 SuperHelper是博主利用业余时间编写的一个ORM微型框架,除了可以提高开发效率,与其它ORM框架相比,博主更加喜欢Supe ...

随机推荐

  1. Strimzi Kafka Bridge(桥接)实战之一:简介和部署

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于<Strimzi Kafka Bridge( ...

  2. 最快速搭建个人服务器图床siuuuuu

    @ 目录 1.服务器准备 2.docker 安装 (1)通过命令行的方式 (2)宝塔面板上安装 3.开启端口访问 什么是docker 4.docker安装minio 什么是minio 5.配置mini ...

  3. Springboot集成Netty实现TCP通讯

    Netty测试客户端 package com.coremain; import com.coremain.handler.ServerListenerHandler; import io.netty. ...

  4. 字符串匹配|kmp笔记

    很久之前学的了. 我很懒,不太喜欢画图. 做个笔记回忆一下: kmp 朴素比对字符串 所谓字符串匹配,是这样一种问题:"字符串 T 是否为字符串 S 的子串?如果是,它出现在 S 的哪些位置 ...

  5. 各种flex布局,拿来即用用过的都说好

    开发过程中,很多布局,用antd的栅格还是不灵活,flex弹性布局会更好用 Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性. 注意 ...

  6. Visual Studio vs2010到2022各个版本的的永久激活密钥

    前言 以下密钥均收集于网络,但均可以正常激活 VS2022专业版和企业版的密钥 Visual Studio 2022 Pro(专业版) TD244-P4NB7-YQ6XK-Y8MMM-YWV2J Vi ...

  7. Excel 数据处理

    博客地址:https://www.cnblogs.com/zylyehuo/ 2023 年高教社杯全国大学生数学建模竞赛题目 -- B 题 多波束测线问题 图表格式 import numpy as n ...

  8. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-32-JavaScript的调用执行-下篇

    1.简介 在实际工作中,我们需要对处理的元素进行高亮显示,或者有时候为了看清楚操作过程和步骤我们需要跟踪鼠标点击了哪些元素需要标记出来.虽然很少遇到,但是为了以后大家可以参考或者提供一种思路,今天宏哥 ...

  9. 🔥🔥Java开发者的Python快速进修指南:面向对象--高级篇

    首先,让我来介绍一下今天的主题.今天我们将讨论封装.反射以及单例模式.除此之外,我们不再深入其他内容.关于封装功能,Python与Java大致相同,但写法略有不同,因为Python没有修饰符.而对于反 ...

  10. java String字符串总结

    这里我们将总结字符串相关的知识,除了总结String的API用法,同时我们还会总结一些相关的知识点,包括字符串常量池.StringBuffer.StringBuilder,以及equals和==的用法 ...