使用ORM提取数据很容易! 是吗?

介绍

几乎任何系统都以某种方式与外部数据存储一起运行。 在大多数情况下,它是一个关系数据库,并且数据获取通常委托给某些ORM实现。 ORM涵盖了很多例程,并带来了一些新的抽象作为回报。

Martin Fowler写了一篇有关ORM的有趣文章 ,其中的主要思想之一是:“ ORM帮助我们处理大多数企业应用程序中非常实际的问题。 …他们不是很好的工具,但是他们解决的问题也不是很可爱。 我认为他们应该得到更多的尊重和更多的理解。”

在CUBA框架中,我们非常频繁地使用ORM,并且由于它在世界范围内有各种各样的项目,所以对它的局限性了解很多。 有很多事情可以讨论,但我们将集中讨论其中之一:懒惰与急切的数据获取。 我们将讨论数据获取的不同方法(主要是在JPA API和Spring中),我们如何在CUBA中处理数据以及我们为提高CUBA中的ORM层所做的RnD工作。 我们将研究一些基本要素,这些要素可能会帮助开发人员避免使用ORM带来糟糕的性能问题。

取数据:懒惰还是渴望?

如果您的数据模型仅包含一个实体,那么使用ORM不会有任何问题。 让我们看一个例子。 我们有一个具有ID和名称的用户:

 public class User { @Id @GeneratedValue private int id; private String name; //Getters and Setters here  } 

要获取它,我们只需要很好地询问EntityManager即可:

 EntityManager em = entityManagerFactory.createEntityManager();  User user = em.find(User. class , id); 

当实体之间存在一对多关系时,事情就会变得有趣起来:

 public class User { @Id @GeneratedValue private int id; private String name; @OneToMany private List<Address> addresses; //Getters and Setters here  } 

如果要从数据库中获取用户记录,则会出现一个问题:“我们也应该获取地址吗?”。 正确的答案将是:“取决于”。 在某些用例中,我们可能需要其中一些地址-不需要。 通常,ORM提供两个用于获取数据的选项:惰性和渴望。 它们中的大多数默认情况下都设置了惰性提取模式。 而当我们编写以下代码时:

 EntityManager em = entityManagerFactory.createEntityManager();  User user = em.find(User. class , 1 );  em.close();  System.out.println(user.getAddresses().get( 0 )); 

我们得到了所谓的“LazyInitException” ,这使ORM新手非常困惑。 在这里,我们需要解释“附加”和“分离”对象上的概念,并讲述数据库会话和事务。

然后,将一个实体实例附加到会话,这样我们就可以获取详细信息属性。 在这种情况下,我们遇到了另一个问题–交易时间越来越长,因此陷入僵局的风险增加了。 而且,由于短查询的数量增加,将我们的代码拆分为一系列短事务可能会导致数据库“百万蚊子死亡”。

如前所述,您可能需要也可能不需要获取Addresses属性,因此仅在某些用例中需要“触摸”集合,从而添加更多条件。 嗯... 看起来越来越复杂。

好的,另一种提取类型会有所帮助吗?

 public class User { @Id @GeneratedValue private int id; private String name; @OneToMany (fetch = FetchType.EAGER) private List<Address> addresses; //Getters and Setters here  } 

好吧,不完全是。 我们将摆脱烦人的懒惰init异常,并且不应该检查实例是已附加还是已分离。 但是我们遇到了性能问题,因为同样,我们并不需要所有情况的地址,而是始终选择它们。 还有其他想法吗?

Spring JDBC

一些开发人员对ORM感到非常恼火,以至于他们使用Spring JDBC切换到“半自动”映射。 在这种情况下,我们为唯一的用例创建唯一的查询,并返回包含仅对特定用例有效的属性的对象。

它给了我们极大的灵活性。 我们只能得到一个属性:

 String name = this .jdbcTemplate.queryForObject( "select name from t_user where id = ?" , new Object[]{1L}, String. class ); 

或整个对象:

 User user = this .jdbcTemplate.queryForObject( "select id, name from t_user where id = ?" , new Object[]{1L}, new RowMapper<User>() { public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User(); user.setName(rs.getString( "name" )); user.setId(rs.getInt( "id" )); return user; } }); 

您也可以使用ResultSetExtractor来获取地址,但是它涉及编写一些额外的代码,并且您应该知道如何编写SQL联接以避免n + 1 select问题 。

好吧,它又变得越来越复杂。 您可以控制所有查询并可以控制映射,但是您必须编写更多代码,学习SQL并知道如何执行数据库查询。 尽管我认为了解SQL基础知识对于几乎每个开发人员都是必不可少的技能,但其中一些人并不这么认为,因此我不会与他们争论。 如今,了解x86汇编器也不是每个人都至关重要的技能。 让我们考虑一下如何简化开发。

JPA实体图

让我们退后一步,尝试了解我们将要实现什么? 似乎我们需要做的就是准确告诉我们要在不同用例中获取哪些属性。 那我们做吧! JPA 2.1引入了新的API –实体图。 该API背后的想法很简单–您只需编写一些注释来描述应获取的内容。 让我们看一个例子:

 @Entity  @NamedEntityGraphs ({ @NamedEntityGraph (name = "user-only-entity-graph" ), @NamedEntityGraph (name = "user-addresses-entity-graph" , attributeNodes = { @NamedAttributeNode ( "addresses" )}) })  public class User { @Id @GeneratedValue private int id; private String name; @OneToMany (fetch = FetchType.LAZY) private Set<Address> addresses; //Getters and Setters here  } 

对于这个实体,我们描述了两个实体图– user-only-entity-graph不获取Addresses属性(标记为惰性),而第二个图则指示ORM选择地址。 如果我们将属性标记为渴望,则实体图设置将被忽略,并且将获取该属性。

因此,从JPA 2.1开始,您可以通过以下方式选择实体:

 EntityManager em = entityManagerFactory.createEntityManager();  EntityGraph graph = em.getEntityGraph( "user-addresses-entity-graph" );  Map<String, Object> properties = Map.of( "javax.persistence.fetchgraph" , graph);  User user = em.find(User. class , 1 , properties);  em.close(); 

这种方法极大地简化了开发人员的工作,无需“接触”惰性属性并创建长事务。 很棒的事情是,实体图可以应用于SQL生成级别,因此不会从数据库中获取额外的数据到Java应用程序。 但是仍然有一个问题。 我们不能说获取了哪些属性,哪些没有。 有一个API,可以使用PersistenceUnit类检查属性:

 PersistenceUtil pu = entityManagerFactory.getPersistenceUnitUtil();  System.out.println( "User.addresses loaded: " + pu.isLoaded(user, "addresses" "User.addresses loaded: " + pu.isLoaded(user, "addresses" )); 

但这很无聊。 我们可以简化一下,只是不显示未提取的属性吗?

Spring预测

Spring Framework提供了一个很棒的工具,称为Projections (它与Hibernate的Projections不同)。 如果我们只想获取实体的某些属性,则可以指定一个接口,Spring将从数据库中选择接口“实例”。 让我们看一个例子。 如果我们定义以下接口:

 interface NamesOnly { String getName();  } 

然后定义一个Spring JPA存储库以获取我们的User实体:

 interface UserRepository extends CrudRepository<User, Integer> { Collection<NamesOnly> findByName(String lastname);  } 

在这种情况下,调用findByName方法后,我们将无法访问未提取的属性! 同样的原则也适用于详细实体类。 因此,您可以通过这种方式获取主记录和明细记录。 此外,在大多数情况下,Spring会生成“适当的” SQL,并且仅获取投影中指定的属性,即,投影的工作方式类似于实体图描述。

这是一个非常强大的概念,您可以使用SpEL表达式,使用类而不是接口等。如果您有兴趣,可以在文档中查看更多信息。

投影的唯一问题是在幕后将它们实现为地图,因此是只读的。 因此,考虑到您可以为投影定义setter方法,则既不能使用CRUD存储库也不能使用EntityManager保存更改。 您可以将投影视为DTO,并且必须编写自己的DTO到实体的转换代码。

CUBA实施

从CUBA框架开发的开始,我们就尝试优化可与数据库一起使用的代码。 在框架中,我们使用EclipseLink来实现数据访问层API。 关于EclipseLink的好处-它从一开始就支持部分实体加载,这就是为什么我们首先选择它而不是Hibernate的原因。 在此ORM中,您可以指定在JPA 2.1成为标准之前应确切加载哪些属性。 因此,我们将类似内部“实体图”的概念添加到我们的框架CUBA Views中 。 视图非常强大-您可以扩展它们,合并等等。CUBA视图创建背后的第二个原因-我们想使用短事务,并专注于主要处理分离对象,否则,我们将无法快速,快速地响应丰富的Web UI 。

在CUBA视图中,描述存储在XML文件中,如下所示:

 <view class = "com.sample.User" extends = "_local" name= "user-minimal-view" > <property name= "name" /> <property name= "addresses" view= "address-street-only-view" /> </property>  </view> 

该视图指示CUBA DataManager提取具有其本地名称属性的User实体,并应用地址仅街道视图来获取地址,同时在查询级别获取它们(重要!)。 定义视图后,可以使用DataManager类将其应用于获取实体:

 List<User> users = dataManager.load(User. class ).view( "user-edit-view" ).list(); 

它的工作原理很像,并且由于不加载未使用的属性而节省了大量网络流量,但是像在JPA Entity Graph中一样,存在一个小问题:我们无法说出用户实体的哪些属性已加载。 在CUBA中,我们有令人讨厌的“IllegalStateException: Cannot get unfetched attribute [...] from detached object” 。 像在JPA中一样,您可以检查是否未提取属性,但是为每个要提取的实体编写这些检查是一项无聊的工作,并且开发人员对此不满意。

CUBA View Interfaces PoC

如果我们能充分利用两个世界的优势,该怎么办? 我们决定使用Spring的方法来实现所谓的实体接口,但是这些接口在应用程序启动期间会转换为CUBA视图,然后可以在DataManager中使用。 这个想法很简单:定义一个指定实体图的接口(或一组接口)。 它看起来像Spring Projections,并且像Entity Graph一样工作:

 interface UserMinimalView extends BaseEntityView<User, Integer> { String getName(); void setName(String val); List<AddressStreetOnly> getAddresses(); interface AddressStreetOnly extends BaseEntityView<Address, Integer> { String getStreet(); void setStreet(String street); }  } 

请注意,如果仅在一种情况下使用,则AddressStreetOnly接口可以嵌套。

在CUBA应用程序启动期间(实际上,大多数情况是Spring Context初始化),我们为CUBA视图创建了程序化表示并将其存储在Spring上下文中的内部存储库bean中。

之后,我们需要调整DataManager,以便它除了可以接受CUBA View字符串名称之外,还可以接受类名称,然后我们只需传递接口类即可:

 List<User> users = dataManager.loadWithView(UserMinimalView. class ).list(); 

我们为每个从数据库获取的实例生成代理来实现实体视图,就像冬眠一样。 而且,当您尝试获取属性的值时,代理会将调用转发给真实实体。

通过这种实现,我们试图用一块石头杀死两只鸟:

  • 接口中未声明的数据不会加载到Java应用程序代码中,从而节省了服务器资源
  • 开发人员仅使用获取的属性,因此不会再出现“ UnfetchedAttribute”错误(在Hibernate中也称为LazyInitException )。

与Spring Projections相比,实体视图包装实体并实现CUBA的实体接口,因此可以将它们视为实体:您可以更新属性并将更改保存到数据库。

这里的“第三只鸟” –您可以定义一个仅包含吸气剂的“只读”接口,从而完全防止实体在API级别进行修改。

另外,我们可以对分离的实体执行一些操作,例如将该用户的名称转换为小写:

 @MetaProperty  default String getNameLowercase() { return getName().toLowerCase();  } 

在这种情况下,所有计算出的属性都可以从实体模型中移出,因此您不必将数据获取逻辑与用例特定的业务逻辑混合在一起。

另一个有趣的机会–您可以继承接口。 这使您可以准备具有不同属性集的多个视图,然后根据需要将它们混合。 例如,您可以有一个包含用户名和电子邮件的界面,以及另一个包含名称和地址的界面。 而且,如果您需要第三个视图接口,其中应该包含名称,电子邮件和地址,则可以通过将二者结合起来来实现–这要归功于Java中接口的多重继承。 请注意,您可以将此第三个接口传递给使用第一个或第二个接口的方法,OOP原理照常在这里工作。

我们还实现了视图之间的实体转换–每个实体视图都有reload()方法,该方法接受另一个视图类作为参数:

 UserFullView userFull = userMinimal.reload(UserFullView. class ); 

UserFullView可能包含其他属性,因此该实体将从数据库中重新加载。 实体重新加载是一个懒惰的过程,仅当您尝试获取实体属性值时才执行。 我们之所以这样做是因为在CUBA中,我们有一个“网络”模块,可呈现丰富的UI,并可能包含自定义的REST控制器。 在此模块中,我们使用相同的实体,并且可以将其部署在单独的服务器上。 因此,每个实体重新加载都会通过核心模块(aka中间件)向数据库发出附加请求。 因此,通过引入惰性实体重新加载,我们节省了一些网络流量和数据库查询。

PoC可以从GitHub下载-随时使用。

结论

ORM将在不久的将来在企业应用程序中大量使用。 我们只需要提供一些将数据库行转换为Java对象的工具即可。 当然,在复杂的高负载应用程序中,我们将继续看到独特的解决方案,但是ORM的生存时间将与RD​​BMSes一样长。

在CUBA框架中,我们试图简化ORM的使用,以使开发人员尽可能地轻松。 在下一版本中,我们将引入更多更改。 我不确定这些接口是视图接口还是其他接口,但是我可以肯定的是,使用CUBA在下一版本中使用ORM将得到简化。

翻译自: https://www.javacodegeeks.com/2019/09/fetching-data-with-orm-easy.html

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

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

相关文章

分段式多级离心泵_离心泵与多级离心泵工作原理

离心泵工作原理&#xff1a;离心泵工作时&#xff0c;液体注满泵壳&#xff0c;叶轮高速旋转&#xff0c;液体在离心力作用下产生高速度&#xff0c;高速液体经过逐渐扩大的泵壳通道&#xff0c;动压头转变为静压头。性能特点&#xff1a;高效节能&#xff1a;泵有高效的水力形…

java8 javafx_JavaFX技巧8:美丽深层

java8 javafx如果您正在开发JavaFX的UI框架&#xff0c;请养成一种习惯&#xff0c;始终将自定义控件拆分为控件类和外观类。 来自Swing自己&#xff0c;这对我来说并不明显。 Swing还使用MVC概念&#xff0c;并将实际的组件呈现委托给UI委托&#xff0c;但是扩展Swing的人们大…

牛客网数据开发题库_数据库刷题—牛客网(21-30)

21.查找所有员工自入职以来的薪水涨幅情况&#xff0c;给出员工编号emp_no以及其对应的薪水涨幅growth&#xff0c;并按照growth进行升序CREATE TABLE employees ( emp_no int(11) NOT NULL, birth_date date NOT NULL, first_name varchar(14) NOT NULL, last_name varchar(16…

弹性堆栈介绍

当您运行对公司至关重要的软件时&#xff0c;您将无法获得仅用于分析一段时间前发生的事情的日志&#xff0c;让客户端告诉您您的应用程序已损坏&#xff0c;而您甚至不知道发生了什么是真实的问题。 解决该问题的方法之一是使用监视和日志记录。 大多数应用程序都将具有日志记…

access统计没有选课的人数_当代大学生发愁求职就业,更发愁“选课”,自主选课变成了负担...

当代大学生除了求职就业&#xff0c;最发愁的就是“选课”。不得不说&#xff0c;随着科技的发展&#xff0c;各行各业都发生了翻天覆地的变化。而在大学里的选课&#xff0c;也因此有了巨大的改变。过去&#xff0c;大学生上课&#xff0c;其实课程都是被安排好的&#xff0c;…

产线数字化软件源码_品质笔记⑥丨卢宇聪:把握数字化趋势,坚定创新发展道路...

6天5夜&#xff0c;跨越3座城市&#xff0c;深度走访7家企业&#xff0c;对话多位企业家……这是一趟开阔视野之旅。我接触了很多之前极少有机会接触的企业&#xff0c;比如做光缆的法尔胜泓晟集团、做节能装备的双良集团、做密封件的天生密封件有限公司等。我以前经常接触的是…

es 安装kopf_Elasticsearch-kopf导览

es 安装kopf当我需要一个插件来显示Elasticsearch的集群状态时&#xff0c;或者需要深入了解通常为经典插件elasticsearch-head所达到的索引时。 由于有很多建议&#xff0c;而且似乎是非官方的继任者&#xff0c;所以我最近更详细地研究了elasticsearch-kopf 。 我喜欢它。 我…

会导致小程序onhide码 手机息屏_小程序onshow事件

问题描述onShow 事件在小程序里面非常重要&#xff0c;场景之多&#xff0c;导致处理起来很复杂。很多业务场景依赖与onShow与onHide事件。比如分享给他人&#xff0c;在群里PK等等。息屏&#xff0c;新页面返回、Home键操作&#xff0c;也会触发onShow事件。以下是官网的说明&…

Spring@主要注释

介绍&#xff1a; 当存在多个相同类型的bean时&#xff0c;使用Spring Primary批注为标记的bean提供更高的优先级。 默认情况下&#xff0c;Spring按类型自动连线。 因此&#xff0c;当Spring尝试自动装配并且有多个相同类型的bean时&#xff0c;我们将获得NoUniqueBeanDefini…

python帮助文档快捷键_Pycharm快捷键手册

AltEnter 自动添加包Ctrlt SVN更新Ctrlk SVN提交Ctrl / 注释(取消注释)选择的行CtrlShiftF 高级查找CtrlEnter 补全Shift Enter 开始新行TAB ShiftTAB 缩进/取消缩进所选择的行Ctrl Alt I 自动缩进行Ctrl Y 删除当前插入符所在的行Ctrl D 复制当前行、或者选择的块Ctrl …

arm 交叉编译找不到so_搭建交叉编译环境并验证

1. 搭建编译环境并验证1.1 实验目的 掌握嵌入式开发环境、交叉编译器的搭建、安装和配置方法 熟悉Linux应用程序的编译、调试方法&#xff0c;能够验证X86平台和ARM平台的差异1.2 实验内容 交叉编译器环境搭建 编写一个典型的Linux应用程序 使用GDB调试Linux程序(PC平台) 用Mak…

雷达的工作原理示意图_电磁阀的构成和工作原理示意图

电磁阀符号的含义&#xff1a;电磁阀符号由方框、箭头、“T”和字符构成。电磁阀图形符号的含义一般如下&#xff1a;1、用方框表示阀的工作位置&#xff0c;每个方块表示电磁阀的一种工作位置&#xff0c;即“位”。有几个方框就表示有几“位”&#xff0c;如二位三通表示有两…

JDK 14 Rampdown:内部版本27

马克 雷因霍尔德&#xff08; Mark Reinhold&#xff09;最近的帖子“ JDK 14现在处于Rampdown第一阶段 ”宣布“我们现在处于Rampdown第一阶段”&#xff0c;并且“整体功能已冻结”。 JDK 14 Early Access Build &#xff03;27&#xff08;2019/12/12&#xff09;是一个繁重…

从金蝶k3到金税盘_经典全套金蝶K3操作流程大全

—结帐—期末结帐注意点&#xff1a;不能结帐的原因&#xff1a;(1)有未过帐的凭证(2)无权限(3)其他子系统未结帐(4)与其他用户冲突八、套打1、套打格式凭证&#xff1a;*上海TR101记帐凭证上海TR102收款凭证 纸张大小&#xff1a;自定义大小上海TR103付款凭证 宽度&#xff1a…

sqlserver拼接字符串换行_1.3【Python】第三章 字符串

人工智能入门与实战第一季&#xff1a;python基础语法字符串是比较常见的数据类型&#xff0c;在第一章中我们最早接触的数据类型就是字符串&#xff1a;"hello world"&#xff0c;字符串可以用单引号’‘或双引号""来表示代码示例&#xff1a;name "…

jsf按钮响应事件_如何从JSF获取JSON响应?

jsf按钮响应事件许多JavaScript小部件都希望使用JSON格式的数据和选项。 如今&#xff0c;选择一个很酷的小部件并将其包装在一个复合组件中确实很容易。 但是第一个问题是如何发送AJAX请求并以正确的JSON格式接收响应。 JSF用户经常会提出这个问题 。 您需要的只是一个XHTML f…

mot数据集_谈谈ReID与MOT的关系

1.ReID与MOT的联系在MOT任务中&#xff0c;一般常用的特征模型有运动模型和表观模型&#xff0c;其中表观模型以行人重识别(ReID)类算法为主流。Re-ID任务主要解决的是跨摄像头场景下行人的识别与检索&#xff0c;其中存在给定了身份的图片序列query&#xff0c;需要为不同摄像…

jClarity:在Azure上升级到Java

在互联世界公共基础结构的新时代&#xff0c;最大和最重要的两个方面是Java和OpenJDK的诞生和兴起。 因此&#xff0c;许多公司将时间和资源投入到构建最先进的技术上&#xff0c;以确保整个行业在未来几年内在AdoptOpenJDK上拥有丰富的质量&#xff0c;而且免费的OpenJDK二进制…

装饰器模式应用场景_装饰器设计模式的应用

装饰器模式应用场景嗨&#xff0c;您好&#xff01; 今天&#xff0c;我将展示装饰设计模式的实际应用。 装饰器设计模式是一种广泛使用的设计模式&#xff0c;同时在运行期间处理图形&#xff0c;树木和动态更改。 如果您正在寻找或尝试进行递归&#xff0c;这也是一个不错的…

Auto Lisp 标注子样式_CSS 核心样式

CSS核心样式粗细font-weight作用:设置文字是否加粗显示属性名: font-weight, 属于font属性的一个单-属性属性值有两种方式:单词类型、数字类型单词类型数字类型100-900之间的整百数字数字越大&#xff0c;文字显示越粗其中400等价于normal, 700等价于bold字体风格font-style作用…