最近详细了解了一下ORM,这里记录一下。
当下我们使用的ORM("对象-关系 映射"(Object Relational Mapping))框架中,JPA/Hibernate/Mybatis占了半边天,它们都有各自的优势和使用场景。
最近发现了一个之前从来没用的ORM框架jOOQ,非常有意思,为数据处理提供了一种全新的方式
一、ORM
1.1、ORM概述
面向对象编程和关系型数据库,都是目前最流行的技术,但是它们的模型是不一样的。
面向对象编程把所有实体看成对象(object),关系型数据库则是采用实体之间的关系(relation)连接数据。很早就有人提出,关系也可以用对象表达,这样的话,就能使用面向对象编程,来操作关系型数据库。
简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。
ORM 把数据库映射成对象。
- 数据库的表(table) --> 类(class)
- 记录(record,行数据)--> 对象(object)
- 字段(field)--> 对象的属性(attribute)
举例来说,下面是一行 SQL 语句。
SELECT id, first_name, last_name, phone, birth_date, sex FROM persons WHERE id = 10
程序直接运行 SQL,操作数据库的写法如下。
1 res = db.execSql(sql); 2 name = res[0]["FIRST_NAME"];
改成 ORM 的写法如下。
p = Person.get(10); name = p.first_name;
一比较就可以发现,ORM 使用对象,封装了数据库操作,因此可以不碰 SQL 语言。开发者只使用面向对象编程,与数据对象直接交互,不用关心底层数据库。
总结起来,ORM 有下面这些优点。
- 数据模型都在一个地方定义,更容易更新和维护,也利于重用代码。
- ORM 有现成的工具,很多功能都可以自动完成,比如数据消毒、预处理、事务等等。
- 它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。
- 基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。
- 你不必编写性能不佳的 SQL。
但是,ORM 也有很突出的缺点。
- ORM 库不是轻量级工具,需要花很多精力学习和设置。
- 对于复杂的查询,ORM 要么是无法表达,要么是性能不如原生的 SQL。
- ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。
1.2、命名规定
许多语言都有自己的 ORM 库,最典型、最规范的实现公认是 Ruby 语言的 Active Record。Active Record 对于对象和数据库表的映射,有一些命名限制。
(1)一个类对应一张表。类名是单数,且首字母大写;表名是复数,且全部是小写。比如,表books
对应类Book
。
(2)如果名字是不规则复数,则类名依照英语习惯命名,比如,表mice
对应类Mouse
,表people
对应类Person
。
(3)如果名字包含多个单词,那么类名使用首字母全部大写的骆驼拼写法,而表名使用下划线分隔的小写单词。比如,表book_clubs
对应类BookClub
,表line_items
对应类LineItem
。
(4)每个表都必须有一个主键字段,通常是叫做id
的整数字段。外键字段名约定为单数的表名 + 下划线 + id,比如item_id
表示该字段对应items
表的id
字段。
二、MyBatis
查看我的博客:
https://blog.csdn.net/yangyangye/category_9227808.html
最大的体会是:MyBatis能让我控制SQL
对于任何使用关系数据库的严重项目,您无法逃避学习SQL。大多数ORM试图通过提供更高级别的抽象来使您与SQL隔离。但是作为交换,他们强迫你学习一个新的API或一个抽象的查询语言。这些API /查询语言无论如何都会生成SQL,所以唯一的区别是你不知道它们是什么,直到你打开引擎盖。在很多情况下,我可以用单个语句编写的查询将以两个或更多语句生成,从而降低性能。
现在我突然回到ORM并调整它以产生更高效的查询。有时候,API不够丰富,无法满足所需要的任何事情,无论如何,我都必须逃避SQL语言。为什么要这么麻烦?如果我必须知道SQL,为什么不直接自己写,直接优化呢?教学框架将结果映射到对象中要容易得多。
猜猜看,这正是MyBatis所做的 - 在这里看到结果图。它教MyBatis如何将查询结果映射到一个Transaction对象,并引用一个Account和一个Category。我最终得到更清晰和可理解的代码。
所以:我希望你能明白为什么我喜欢MyBatis。这将是我未来Java的默认ORM
三、JOOQ
3.1 介绍
JOOQ(Java Object Oriented Query)是一个开源框架,它可以把数据库模型的基本信息,比如表名,字段名自动生成相应的Java类;并在此基础上提供了一整套数据处理的API。
jOOQ(Java Object Oriented Querying,即面向Java对象查询)是一个高效地合并了复杂SQL、类型安全、源码生成、ActiveRecord、存储过程以及高级数据类型的Java API的类库。
Hibernate致力于以面向对象的方式处理数据,隐藏了所有SQL相关处理;
Mybatis则是在XML文件中写SQL。
jOOQ与它们都不同,它致力于通过java语言以最简单的形式写SQL。使用jOOQ DSL(Domain-Specific Language), SQL看起来几乎是由Java本地支持的。
对于写Java的码农来说ORMS再也熟悉不过了,不管是Hibernate或者Mybatis,都能简单的使用实体映射来访问数据库。但有时候这些 ‘智能’的对象关系映射又显得笨拙,没有直接使用原生sql来的灵活和简单,而且对于一些如:joins,union, nested selects等复杂的操作支持的不友好。JOOQ 既吸取了传统ORM操作数据的简单性和安全性,又保留了原生sql的灵活性,它更像是介于 ORMS和JDBC的中间层。对于喜欢写sql的码农来说,JOOQ可以完全满足你控制欲,可以是用Java代码写出sql的感觉来。就像官网说的那样 :
get back in control of your sql
SELECT TITLE FROM BOOK WHERE BOOK.PUBLISHED_IN = 2011 ORDER BY BOOK.TITLE
1 create.select(BOOK.TITLE) 2 3 .from(BOOK) 4 5 6 .where(BOOK.PUBLISHED_IN.eq(2011)) 7 8 9 .orderBy(BOOK.TITLE)
从mvnrepository上查询jOOQ,发现它的第一个版本早在2011年,到现在已经有9个年头了,社区依然活跃。
jOOQ之所以诞生,大概是人们厌倦了直接写SQL,用java以流式的方式写SQL,上手成本并不算高,熟练以后应该很舒服。
3.2. jOOQ解决了什么问题
jOOQ是将SQL语言集成到Java中的一种简单方法,它使开发人员可以直接用Java快速,安全地编写高质量的SQL,从而使他们可以专注于自己的业务。
绝大部分数据库函数,都转化为了java方法,使用起来自然方便,它还能进行必要的类型检查,规避了大多数语法错误。
它有下面这几个优势:
- 数据库优先,它不提倡隐藏SQL;与Mybatis一样,以SQL优先,同时可以快速安全的编写SQL。
- DSL(Domain Specific Language )风格,代码够简单和清晰。遇到不会写的sql可以充分利用IDEA代码提示功能轻松完成。
- 类型安全的SQL,它支持列类型检查、行值表达式检查、SQL语法检查。保留了传统ORM 的优点,简单操作性,安全性,类型安全等。不需要复杂的配置,并且可以利用Java 8 Stream API 做更加复杂的数据转换。
- 代码自动生成,自动生成一份Model类,不需要再手动维护它们。
- SQL标准化,各个数据库方言存在很多细微差别,jOOQ可以自动进行转换
- 支持区分不同环境,可以动态切换开发数据库、测试数据库等不同环境的数据库
- 查询生命周期,jOOQ不尝试隐藏SQL,围绕整个生命周期开放了接口,我们可以做日志自定义,事件触发,SQL转换等处理。
- 支持编写存储过程
- 支持主流的RDMS和更多的特性,如self-joins,union,存储过程,复杂的子查询等等。
- 丰富的Fluent API和完善文档。
- runtime schema mapping 可以支持多个数据库schema访问。简单来说使用一个连接池可以访问N个DB schema,使用比较多的就是SaaS应用的多租户场景。
3.3. 更多的例子
java
1 2 3 4 5 6 SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, COUNT(*) 7 8 FROM AUTHOR 9 10 JOIN BOOK ON AUTHOR.ID = BOOK.AUTHOR_ID 11 12 13 WHERE BOOK.LANGUAGE = 'DE' 14 15 16 AND BOOK.PUBLISHED > DATE '2008-01-01' 17 18 19 GROUP BY AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME 20 21 22 HAVING COUNT(*) > 5 23 24 25 ORDER BY AUTHOR.LAST_NAME ASC NULLS FIRST 26 27 28 LIMIT 2 29 30 31 OFFSET 1 32 33 34
c
1 reate.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, count()) 2 3 .from(AUTHOR) 4 5 .join(BOOK).on(AUTHOR.ID.equal(BOOK.AUTHOR_ID)) 6 7 .where(BOOK.LANGUAGE.eq("DE")) 8 9 .and(BOOK.PUBLISHED.gt(date("2008-01-01"))) 10 11 .groupBy(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) 12 13 .having(count().gt(5)) 14 15 .orderBy(AUTHOR.LAST_NAME.asc().nullsFirst()) 16 17 .limit(2) 18 19 .offset(1)
//类型检查 select().from(t).where(t.a.eq(select(t2.x).from(t2)); // Type-check here: ---------------> ^^^^ select().from(t).where(t.a.eq(any(select(t2.x).from(t2))); // Type-check here: -------------------> ^^^^ select().from(t).where(t.a.in(select(t2.x).from(t2)); // Type-check here: ---------------> ^^^^ //表达式类型检查 select().from(t).where(row(t.a, t.b).eq(1, 2)); // Type-check here: -----------------> ^^^^ select().from(t).where(row(t.a, t.b).overlaps(date1, date2)); // Type-check here: ------------------------> ^^^^^^^^^^^^ select().from(t).where(row(t.a, t.b).in(select(t2.x, t2.y))); // Type-check here: -------------------------> ^^^^^^^^^^ update(t).set(row(t.a, t.b), select(t2.x, t2.y).where(...)); // Type-check here: --------------> ^^^^^^^^^^ insertInto(t, t.a, t.b).values(1, 2); // Type-check here: ---------> ^^^^
- 这里简单介绍了JOOQ以及为什么要使用它,作为一个强力的ORM框架,其从一个新的方向尝试更快更好的编写SQL,很值得我们学习。
参考项目:jOOQ-spring-boot-example,
4、总结
以前项目用过JPA,后面为了控制SQL使用MyBatis(强大组件,包含缓存事务等处理),等以后项目再试试JOOQ吧。
5、参考:
http://www.ruanyifeng.com/blog/2019/02/orm-tutorial.html
https://segmentfault.com/a/1190000006748584?utm_source=tuicool&utm_medium=referral
https://segmentfault.com/a/1190000020490982