如何开发一个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. C++ RAII在HotSpot VM中的重要应用

    RAII(Resource Acquisition Is Initialization),也称为"资源获取就是初始化",是C++语言的一种管理资源.避免泄漏的惯用法.C++标准保证 ...

  2. 解决软件安装无法自定义文件夹,自动安装在C盘 (Windows系统)

    其实就是软链接的简单应用 1.软件已经自动安装 2.完全退出当前软件 3.通过软件图标的属性找到其实际的安装目录 4.进入该软件的安装目录 5.将该软件整个剪切(你没有看错)到指定文件夹(自定义的安装 ...

  3. Oracle-复制表结构存在的问题

    在生产中,创建一个新表tbl_A,要求与已有表结构tbl_B一致 create table tbl_A AS select * from tbl_B where 1=2; --拷贝表结构tbl_B给t ...

  4. Python面试题——网络与并发编程

    1.python的底层网络交互模块有哪些? socket, urllib,urllib3 , requests, grab, pycurl 2.简述OSI七层协议. OSI七层协议是一个用于计算机或通 ...

  5. 惊奇!Android studio内部在调用Eclipse

    现在用Android studio的人越来越多,主要是说谷歌不再支持Eclipse,而力推Android studio.但是as也太不给力了,我之前写过一篇博客提到. 今天要说的是一个惊天的消息,如题 ...

  6. JAVA类的加载(4) ——类之间能够隔离&类占用的资源能回收

    一.类加载体系

  7. 请教shell读写XML问题

    请教shell读写XML问题 现有 123.xml文件,内容是:<?xml version="1.0" encoding="GBK"?><vi ...

  8. 学习Hadoop不错的系列文章(转)

    http://www.cnblogs.com/xia520pi/archive/2012/04/22/2464934.html 1)Hadoop学习总结 (1)HDFS简介 地址:http://for ...

  9. 文心一言 VS 讯飞星火 VS chatgpt (129)-- 算法导论11.1 4题

    四.用go语言,我们希望在一个非常大的数组上,通过利用直接寻址的方式来实现一个字典.开始时该数组中可能包含一些无用信息,但要对整个数组进行初始化是不太实际的,因为该数组的规模太大.请给出在大数组上实现 ...

  10. python 数据可视化:直方图、核密度估计图、箱线图、累积分布函数图

    本文使用数据来源自2023年数学建模国赛C题,以附件1.附件2数据为基础,通过excel的数据透视表等功能重新汇总了一份新的数据表,从中截取了一部分数据为例用于绘制图表.绘制的图表包括一维直方图.一维 ...