用更轻的解决方案替换完整的ORM(JPA /休眠):推荐的加载/保存模式?

2022-09-01 04:59:40

我正在开发一个新的Java Web应用程序,我正在探索新的方法来保存数据(对我来说是新的!)。我大多有JPA和Hibernate的经验,但是,除了简单的情况,我认为这种完整的ORM可能会变得非常复杂。另外,我不喜欢和他们一起工作。我正在寻找一个新的解决方案,可能更接近SQL。

我目前正在研究的解决方案:

但是,与Hibernate相比,这些解决方案有两个用例我担心。我想知道这些用例的推荐模式是什么。


用例 1 - 获取实体并访问其一些关联的子项和孙项实体。

  • 假设我有一个实体。Person
    • 这有一个关联的实体。PersonAddress
      • 这有一个关联的实体。AddressCity
        • 此实体具有属性。Cityname

从个人实体开始,访问城市名称的完整路径为:

person.address.city.name

现在,假设我使用此方法从 中加载 Person 实体:PersonService

public Person findPersonById(long id)
{
    // ...
}

使用Hibernate,可以根据需要延迟加载与 关联的实体,因此可以访问并确保我有权访问此属性(只要该链中的所有实体都不可为空)。Personperson.address.city.name

但是使用我正在研究的3种解决方案中的任何一种,它都更复杂。有了这些解决方案,有哪些推荐的模式来处理这个用例?首先,我看到了3种可能的模式:

  1. 所有必需的关联子项和孙项实体都可以由所使用的 SQL 查询预先加载。

    但是我看到此解决方案的问题是,可能还有其他一些代码需要从实体访问其他实体/属性路径。例如,也许某些代码需要访问 。如果我想重用我已经拥有的方法,那么SQL查询将需要加载更多信息!不仅是关联实体,还包括关联实体。Personperson.job.salary.currencyfindPersonById()address->cityjob->salary

    现在,如果有 10 个其他位置需要从个人实体开始访问其他信息,该怎么办?我是否应该总是急切地加载所有可能需要的信息?或者可能有12种不同的服务方法来加载个人实体?:

    findPersonById_simple(long id)
    
    findPersonById_withAdressCity(long id)
    
    findPersonById_withJob(long id)
    
    findPersonById_withAdressCityAndJob(long id)
    
    ...
    

    但是,每次我使用一个实体时,我都必须知道它加载了什么,什么没有......这可能很麻烦,对吧?Person

  2. 在实体的 getter 方法中,是否可以检查地址是否已加载,如果没有,则懒惰地加载它?这是现实生活中常用的模式吗?getAddress()Person

  3. 是否有其他模式可用于确保我可以从加载的模型中访问所需的实体/属性?


用例 2 - 保存实体并确保其关联和修改的实体也已保存。

我希望能够使用此方法保存实体:PersonPersonService

public void savePerson(Person person)
{
    // ...
}

如果我有一个实体,并且我更改为其他实体,我如何确保在保存 ?使用 Hibernate,可以轻松地将保存操作级联到关联的实体。我正在研究的解决方案呢?Personperson.address.city.nameCityPerson

  1. 我是否应该使用某种标志来了解在保存人员时还必须保存哪些关联的实体?

  2. 是否有任何其他已知的模式可用于处理此用例?


更新 :在JOOQ论坛上有关于这个问题的讨论。


答案 1

这种问题在不使用真正的ORM时是典型的,并且没有银弹。对于带有iBatis(myBatis)的(不是很大的)web应用程序,一个简单的设计方法对我来说是使用两层来实现持久性:

  • 一个愚蠢的低级层:每个表都有其Java类(POJO或DTO),其字段直接映射到表列。假设我们有一个表,其中包含指向表的字段;然后,我们将有一个类,只有一个(整数)字段;我们没有方法,只有平原。因此,这些Java类非常愚蠢(它们不知道持久性或相关类)。相应的类知道如何加载/保留此对象。该层易于使用iBatis + iBator(或MyBatis + MYBatisGenerator)等工具创建和维护。PERSONADDRESS_IDADRESSPersonDbaddressIdpersonDb.getAdress()personDb.getAdressId()PersonDao

  • 包含丰富域对象的更高级别层:每个对象通常都是上述 POJO 的图形。这些类还具有通过调用相应的DAO来加载/保存图形的智能(可能是懒惰的,也许是带有一些脏标志)。然而,重要的是,这些丰富的域对象不会一对一映射到 POJO 对象(或 DB 表),而是映射到域用例。每个图的“大小”是确定的(它不会无限增长),并且像特定类一样从外部使用。因此,这并不是说您有一个丰富的类(具有一些相关对象的不确定图)是几个用例或服务方法;相反,你有几个丰富的类,,...每个都包装一个特定的有限图,具有自己的持久性逻辑。这可能看起来效率低下或笨拙,在某些情况下可能是这样,但是当您需要保存对象的完整图形时,经常发生的情况实际上是有限的。PersonPersonWithAddresesPersonWithAllData

  • 此外,对于像表格报告这样的东西(特定的SELECTS,返回一堆要显示的列),你不会使用上面的,而是直接和愚蠢的POJO(甚至可能是地图)

在这里查看我的相关答案


答案 2

您的许多问题的答案很简单。您有三种选择。

  1. 使用您提到的三个以SQL为中心的工具之一(MyBatisjOOQDbUtils)。这意味着您应该停止考虑OO域模型和对象关系映射(即实体和延迟加载)。SQL是关于关系数据的,RBDMS非常擅长计算执行计划,以“预先获取”几个连接的结果。通常,甚至不需要过早缓存,如果您确实需要缓存偶尔的数据元素,您仍然可以使用像EhCache这样的东西

  2. 不要使用任何那些以SQL为中心的工具,并坚持使用Hibernate / JPA。因为即使你说你不喜欢Hibernate,你也在“思考Hibernate”。休眠非常擅长将对象图持久化到数据库中。这些工具都不能像Hibernate那样被迫工作,因为它们的使命是别的什么。他们的任务是在SQL上运行。

  3. 采用完全不同的方式,选择不使用关系数据模型。其他数据模型(例如图形)可能更适合您。我把这个作为第三种选择,因为你实际上可能没有这个选择,而且我对替代模型没有太多的个人经验。

请注意,您的问题不是专门关于 jOOQ 的。尽管如此,使用 jOOQ,您可以通过外部工具(如 ModelMapper)将平面查询结果(由联接的表源生成)到对象图的映射外部化。关于模型映射器用户组上的这种集成,有一个持续的线程。

(免责声明:我为jOOQ背后的公司工作)


推荐