Ebean:一款被低估的ORM框架

a60ba9d05cd1cdb2873ec243a7ff77ca.jpeg

ORM框架为什么不香?

对ORM框架的偏见

看了一些MyBaties与Hibernate进行对比的文章。可能是因为一些Hibernate历史原因,国内对于Hibernate普遍存在偏见,我摘抄了几点:

  1. 1. hibernate是全自动,而mybatis是半自动

    hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。
  2. 2. sql直接优化上,mybatis要比hibernate方便很多

    由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql
  3. 3. 应用场景

    MyBatis 适合需求多变的互联网项目,例如电商项目、金融类型、旅游类、售票类项目等。 Hibernate 适合需求明确、业务固定的项目,例如 OA 项目、ERP 项目和 CRM 项目等。

也不知道是不是因为这些对Hibernate的偏见,导致大家对ORM框架也普遍存在偏见。

现状是不论大小公司,国内清一色地使用MyBaties。有时,我都不敢说,我喜欢使用ORM框架。

本文并不是一篇为Hibernate洗地的文章,而是介绍另一款比较小众的ORM框架:Ebean。

领域问题分析

介绍Ebean之前,我们需要弄清楚一个问题:为什么会有MyBaties和ORM这些框架?对于这个问题,我们无从下手,那么,我们将问题倒置:如果没有这些框架,会怎么样?

问题倒置的好处是我们立马就有了可下手的方向。我们找到了不使用框架的情况下,Java代码与数据库进行交互的代码:

public static void viewTable(Connection con) throws SQLException {String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";try (Statement stmt = con.createStatement()) {ResultSet rs = stmt.executeQuery(query);while (rs.next()) {String coffeeName = rs.getString("COF_NAME");int supplierID = rs.getInt("SUP_ID");float price = rs.getFloat("PRICE");int sales = rs.getInt("SALES");int total = rs.getInt("TOTAL");}} catch (SQLException e) {JDBCTutorialUtilities.printSQLException(e);}}

这样的代码存在什么问题呢?

  1. 1. 代码不易于维护:你需要知道每个字段在数据库中的类型,才知道该调用ResultSet的哪个方法;

  2. 2. 代码重复:像COF_NAME这样的字段名,在整个代码仓库可能会飘落得到处都是;

  3. 3. 不安全:手工拼装SQL带来的安全问题,不须多言。

以上三个问题,我们称之为ORM领域核心问题。

从目前市面上的解决方案来看,解决这些核心问题的方案,至少需要包含以下三个能力:

  1. 1. 自动映射:在数据库与Java对象之间自动进行字段类型映射,而不是手工进行映射;

  2. 2. 自动生成SQL:根据Java API自动生成SQL,而不是手写;

  3. 3. 自动执行:自动执行,而不是手工直接操作JDBC接口。

MyBaties如何解决核心问题

MyBaties通过Mapper实现自动映射、自动执行。但是并没有实现自动生成SQL,也正是它称之为Mapper的原因。

public interface PersonMapper {@Insert("Insert into person(name) values (#{name})")public Integer save(Person person);@Select("Select personId, name from Person where personId=#{personId}")@Results(value = {@Result(property = "personId", column = "personId"),@Result(property="name", column = "name"),@Result(property = "addresses", javaType = List.class,column = "personId", many=@Many(select = "getAddresses"))})public Person getPersonById(Integer personId);// ...
}

我个人很好奇,为什么MyBatise没有使用JPA规范,而是自己又创造一种注解。

MyBaties另一种通过XML的配置方式配置的,本文就不介绍了。想想当年,Spring也是使用XML进行配置Bean的,现在好像已经没有人这么干了。

说到底,MyBaties也是一个ORM框架。MyBatis-Plus插件的流行程度正好证明了这一点。所以,大家没有必要对ORM框架抱有偏见。:P

JPA小传

在介绍Ebean前,我们回顾一下JPA的历史。

JPA全称:Java Persistent API(Java持久化API)。它只是规范,并不是具体技术,其中Hibernate应该是最出名的实现之一了。Ebean也是具体实现之一。

值得注意的是我们应该可以认定这个规范没有限制我们只能用它将数据持久化到数据库(思路要打开)。即使,我们绝大多数时候,只用它持久化数据到数据库中。

它的版本历史如下:

  • • 2006.5.11: JPA1.0作为JSR220规范的一部分发布。Ebean同年11月发布Bate测试版本;

  • • 2009年:JPA2.0发布;

  • • 2013年和2017年:JPA2.1和JPA2.2分别发布;

  • • 2019年:JPA更名为Jakarta Persistence。

  • • 2020年和2022年:Jakarta Persistence3.0和3.1版本分别发布。

此部分内容来自: https://handwiki.org/wiki/Java_Persistence_API https://en.wikipedia.org/wiki/Jakarta_Persistence

Ebean:一款被低估的ORM框架

Ebean最早于2006年11月13日发布了Bate测试版本。然后v1.0.0版本,在2008年11月24日,由它的作者Rob Bygrave发布到了SourceForge。

后来Ebean迁到了Github。目前最新版本是2023年11月22日。从Github组织来看,Ebean的主要维护人只有:Rob Bygrave。18年的坚持,不得不佩服作者的毅力。

但这也成为我认为Ebean目前最大的问题:如果作者突然有个什么三长两短?社区应该如何应对。即使,它目前有将近100个contributor。

个人在2011左右接触到Play Framework的时候,了解到Ebean。Play框架使用Ebean作为它的JPA实现。当时就被它优秀的设计所吸引。

但是真正让我使用的是:它的设计非常符合我的DDD口味,同时鼓励充血模型的实体。

Ebean是如何实现自动映射的

在上文中,我们已经介绍了ORM领域核心问题:自动映射。这是JPA规范要解决的最重要的问题之一。Ebean实现了JPA规范定义的注解。

用户在字段上加上JPA的注解,然后在真正需要映射的时候,Ebean自动进行映射。以下是定义一个实体Person,它对应的表名是people。实体字段与数据库字段也有相应的映射:

@Entity
@Table(name="people") 
public class Person { @Id@GeneratedValueprivate int id; @Column(name="first_name", length=10)private String firstName; @Column(name="last_name", length=10)private String lastName;

在实体类上中定义Java类与数据库之间的映射关系,最大的好处就是DDL语句和数据库迁移SQL可以被工具自动生成。

假如你在Person类中增加一个email的字段,Ebean的的DDL特性就可以为你生成相应的建表语句。而Ebean的Migration工具就为你生成相应的alert语句。当然,这些语句的执行时机,还是由用户控制。

JPA本身提供了大量注解,Ebean还扩展了一些有用的注解:

  1. 1. @DbJson注解,自动将对象转成JSON进行存储。如果你所使用的数据库支持JSON模式,你会非常喜欢这个注解;有了这个注解,你可能就不需要值对象注解了;

  2. 2. @WhenCreated注解:自动设置对象的创建时间;

  3. 3. @WhenModified注解:自动设置对象的修改时间;

  4. 4. @DbMap注解:自动将Map结构,构建数据库映射到不同的数据类型,如果是Postgre就映射到HSTORE,其它数据库则映射到VARCHAR。

  5. 5. @SoftDelete 软删除注解:当调用实体的delete方法时,只是软删除。只需要在实体中增加一个字段:

@SoftDeleteboolean deleted;

更多相关信息:https://ebean.io/docs/mapping/

Ebean是如何自动生成SQL并执行的

以下我们通过一个实例来展示Ebean相关的能力。

// Database是Ebean与数据库进行交互的主要接口
@Autowired
Database database;
@Test
public void crud() {Person customer = new Person();customer.setFirstName("Jack");customer.setLastName("J");// 这是为了让大家对Ebean的database类有一个感性认知database.save(customer);// 实际应用中,我通常是在Person类中定义一个save方法,并在内容调用database.save(this)。// 最终就是实现这样的调用效果:customer.save()// 批量执行存储。你猜这里应该是生成一条语句,还是多条语句?database.saveAll(customerList);// 根据ID查询对象。Ebean生成相应的select-where语句并执行Customer customerA = database.find(Customer.class, 1);// 当然你也可以只查询其中一个字段的值,Ebean将生成并执行:// select first_name from people where id=1;database.find(Customer.class).select("first_name").where().idEq(1).findSingleAttribute();customerA.setFirstName("Jane");// Ebean会识别出customerA要做的是修改,而不是创建新的记录。所以,生成alert语句并执行。database.save(customerA);// 当然,少不了大家关心的能否执行原生SQLString sql = "select id, first_name from customer where first_name like ?";Customer customer = database.findNative(Customer.class, sql).setParameter("Jo%").findOne();// 另,有时,我们会想念DTO,则可以这么写:List<CustomerDto> beans = database.findDto(CustomerDto.class, "select id, first_name from customer where first_name = :name").setParameter("name", "Rob").findList();// CustomerDto是需要提前定义好的。// 删除记录database.delete(customer);}

至此,已经把Ebean的解决方案介绍完成,由于篇幅有限,还请感兴趣的同学到官网学习。

Ebean的实体类增强技术

在Ebean的官网或者一些网上的文章,你会发现只要实体类继承了Ebean的BaseModel类,都会自动多出save方法以及其它方法。这Lombok与类似,只要加一个@Setter注解,类中就自动出多了相应的setter方法。

又或者,你会看到:

Person contact = new QContact().firstName.equalTo("rob").findOne();

QContact类是由Ebean生成的(在实体类前加一个Q字母代表查询类),方便用户使用链式调用来查询自己想要数据。而不是需要像database.find(Customer.class).select("first_name")这样手工写字段名。

发生以上的魔法是因为Ebean使用了增强(Enhancement)技术。这项技术必须嵌入到我们的IDE和构建工具中,否则相关代码的编译都不可能通过。

Enhancement技术虽然让我们少写代码,但是我们也要认清这门技术所带来的成本:它使我们的开发环境强依赖相应的插件。比如Maven必须要安装它的插件才能构建通过、IDE必须安装插件才能正常写代码。

幸运的是,我们可以选择不使用它的编译时生成的代码。IDE也就不需要安装相应的插件了。

我个人宁愿自己在实体中手写save方法,也不使用这项技术生成。另一个重要的考虑因素是:我不希望领域实体类依赖于具体实现技术。

然而,运行时,还是必须加上agent,即-javaagent:<路径>/ebean-agent.jar,以便Ebean对实体进行脏检查和懒加载支持。

使用经验

以下是一些个人的使用经验,仅供参考:

  1. 1. JPA的所有API,并不是每一个都必须用到。比如字段上的@Basic(fetch=FetchType.LAZY)懒式加载,我就不建议使用。因为在实际工作中,你不能确保每个人都理解懒式加载的应用场景;比如它的JPQL,我们完全没有必要又另学一种SQL,再者Java API的调用方式才应该是推荐;

  2. 2. 使用Java配置类对Ebean进行配置,而不是使用官网介绍properties配置。只有这样才足够灵活应对将来的多数据源需求;

  3. 3. 在实体类中不要直接使用Ebean的技术,而是在实体类中调用repository接口,再由repository的实现调到Ebean。

如果各位读者想看更多的Ebean如何实现DDD repository的文章,请点赞并转发。

关于DDD的文章:

  • 我是如何将同事的代码改成DDD风格的

  • 这十年,我所经历的领域驱动设计(DDD)

  • 领域驱动设计(DDD)下没有POJO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/321845.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

05-微服务-RabbitMQ-概述

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…

Bayes贝叶斯识别Spam Email垃圾邮件

目录 介绍&#xff1a; 一、Gaussian Naive Bayes(连续型变量) 1.1数据处理 1.2建模 1.3cross_val_score函数评估 1.4classification_report函数评估 1.5classification_report函数和cross_val_score函数的区别 二、 Multinomial Naive Bayes&#xff08;离散型变量&…

代码随想录day20 开始二叉搜索树

654.最大二叉树 题目 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。左子树是通过数组中最大值左边部分构造出的最大二叉树。右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给定的数组构…

h5页面---点击左侧导航栏按钮,按钮自动跳转到页面顶部

1.效果图 2.html <div><header><div class"arrow"><div class"left"></div></div><div class"search"><div class"search-img"><img src"../小米官网/images/search.png&qu…

CCNP课程实验-07-OSPF-Trouble-Shooting

目录 实验条件网络拓朴 环境配置开始排错错点1&#xff1a;R1-R2之间认证不匹配错误2&#xff1a;hello包的时间配置不匹配错误3&#xff1a;R2的e0/1接口区域配置不正确错误4&#xff1a;R4的e0/1接口没有配置进OSPF错误5&#xff1a;R2的区域1没有配置成特殊区域错误6&#x…

代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇

代码随想录 (programmercarl.com) 647. 回文子串 1.dp数组及下标含义 我们在判断字符串S是否是回文&#xff0c;那么如果我们知道 s[1]&#xff0c;s[2]&#xff0c;s[3] 这个子串是回文的&#xff0c;那么只需要比较 s[0]和s[4]这两个元素是否相同&#xff0c;如果相同的话&…

LeetCode刷题:面试题 02.02. 返回倒数第 k 个节点

题目&#xff1a; 是否自己写出来&#xff1a;是 解题思路&#xff1a;这题和昨天的876.链表的中间节点很像&#xff0c;观察完题目发现返回k下标其实和计数器count有关所以我们只需要&#xff0c;利用计数器遍历链表记录链表里面数据的个数&#xff0c;然后再写个函数实现就行…

Windows PowerShell的安全目标——安全警报

Windows PowerShell的安全目标——安全警报 1. 保证Shell安全 ​ 自从2006年年底PowerShell发布以来&#xff0c;微软在安全和脚本方面并没有取得很好的名声。毕竟那个时候&#xff0c;**VBScript和Windows Script Host(WSH)**是两个最流行的病毒和恶意软件的载体&#xff0c…

74. 搜索二维矩阵

给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。…

判断完全数-第11届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第27讲。 判断完全数&#…

ssm基于BS的仓库在线管理系统的设计与实现论文

摘 要 如今的时代&#xff0c;是有史以来最好的时代&#xff0c;随着计算机的发展到现在的移动终端的发展&#xff0c;国内目前信息技术已经在世界上遥遥领先&#xff0c;让人们感觉到处于信息大爆炸的社会。信息时代的信息处理肯定不能用之前的手工处理这样的解决方法&#x…

在智能制造中使用数字孪生的优势

在线工具推荐&#xff1a;3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 介绍 在当今快节奏和不断发展的制造业中&#xff0c;公司一直在寻找提高效率…