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

时间:2022-12-21 02:20:13

I'm developing a new Java web application and I'm exploring new ways (new for me!) to persist the data. I mostly have experience with JPA & Hibernate but, except for simple cases, I think this kind of full ORM can become quite complex. Plus, I don't like working with them that much. I'm looking for a new solution, probably closer to SQL.

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

The solutions I'm currently investigating :

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

  • MyBatis
  • MyBatis
  • JOOQ
  • JOOQ
  • Plain SQL/JDBC, potentially with DbUtils or some other basic utility libraries.
  • 纯SQL/JDBC,可能带有DbUtils或其他一些基本的实用程序库。

But there are two use cases I'm worrying about with those solutions, compared to Hibernate. I'd like to know what are the recommended patterns for those use cases.

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


Use Case 1 - Fetching an entity and accessing some of its associated children and grandchildren entities.

  • Let's say I have a Person entity.
    • This Person has an associated Address entity.
      • This Address has an associated City entity.
        • This City entity has a name property.
        • 这个城市实体有一个名称属性。
      • 这个地址有一个关联的城市实体。这个城市实体有一个名称属性。
    • 这个人有一个关联的地址实体。这个地址有一个关联的城市实体。这个城市实体有一个名称属性。
  • 假设我有一个Person实体。这个人有一个关联的地址实体。这个地址有一个关联的城市实体。这个城市实体有一个名称属性。

The full path to access the name of the city, starting from the person entity, would be :

从person实体开始,访问城市名称的完整路径是:

person.address.city.name

Now, let's say I load the Person entity from a PersonService, with this method :

现在,假设我用这个方法从PersonService加载Person实体:

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

Using Hibernate, the entities associated to the Person could be lazily loaded, on demand, so it would be possible to access person.address.city.name and be sure I have access to this property (as long as all the entities in that chain are not nullable).

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

But using anyone of the 3 solutions I'm investigating, it's more complicated. With those solutions, what are the recommended patterns to take care of this use case? Upfront, I see 3 possible patterns:

但是使用我正在调查的3个解决方案中的任何人,情况就更复杂了。有了这些解决方案,有什么推荐的模式来处理这个用例?首先,我看到了三种可能的模式:

  1. All the required associated children and grandchildren entities could be eagerly loaded by the SQL query used.

    可以通过使用的SQL查询来急切地加载所需的所有相关子孙孙实体。

    But the issue I see with this solution is that there may be some other code that needs to access other entities/properties paths from the Person entity. For example, maybe some code will need access to person.job.salary.currency. If I want to reuse the findPersonById() method I already have, the SQL query will then need to load more information! Not only the associated address->city entity but also the associated job->salary entity.

    但是我看到的这个解决方案的问题是,可能还有其他代码需要从Person实体访问其他实体/属性路径。例如,有些代码可能需要访问person.job. salari .currency。如果我想重用已经拥有的findPersonById()方法,那么SQL查询将需要加载更多的信息!不仅关联地址——>城市实体,还关联工作——>薪酬实体。

    Now what if there are 10 other places that need to access other information starting from the person entity? Should I always eagerly load all the potentially required information? Or maybe have 12 different service methods to load a person entity? :

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

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

    But then everytime I would use a Person entity, I would have to know what has been loaded with it and what hasn't... It could be quite cumbersome, right?

    但是,每当我使用Person实体时,我就必须知道它装载了什么,还有什么没有……它可能很麻烦,对吧?

  2. In the getAddress() getter method of the Person entity, could there be a check to see if the address has already been loaded and, if not, lazily load it? It this a frequently used pattern in real life applications?

    在Person实体的getAddress() getter方法中,是否可以检查地址是否已经被加载,如果没有,是否可以延迟加载?这是实际应用中经常使用的模式吗?

  3. Are there other patterns that can be used to make sure I can access the entities/properties I need from a loaded Model?

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


Use Case 2 - Saving an entity and making sure its associated and modified entities are also saved.

I want to be able to save a Person entity using this PersonService's method :

我希望能够使用这个PersonService的方法来保存一个Person实体:

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

If I have a Person entity and I change person.address.city.name to something else, how can I make sure the City entity modifications will be persisted when I save the Person? Using Hibernate, it can be easy to cascade the save operation to the associated entities. What about the solutions I'm investigating?

如果我有一个Person实体,而我将Person .address. cit. name更改为其他实体,我如何确保在保存该Person时,该城市实体修改将被保留?使用Hibernate,可以很容易地将保存操作串级到相关的实体。我正在调查的解决方案呢?

  1. Should I use some kind of dirty flag to know what associated entities also have to be saved when I save the person?

    我是否应该使用某种肮脏的标志来了解在我保存person时,还需要保存哪些关联实体?

  2. Are there any other known patterns useful to deal with this use case?

    有其他已知的模式对处理这个用例有用吗?


Update : There is a discussion about this question on the JOOQ forum.

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

7 个解决方案

#1


26  

This kind of problem is typical when not using a real ORM, and there is no silver bullet. A simple design approach that worked for me for a (not very big ) webapp with iBatis (myBatis), is to use two layers for persistence:

这种问题在没有使用真金白银时是很典型的,而且也没有什么灵丹妙药。对我来说,一个简单的设计方法是使用两个层来进行持久性:

  • A dumb low-level layer: each table has its Java class (POJO or DTO), with fields that maps directly to the table columns. Say we have a PERSON table with a ADDRESS_ID field that points to an ADRESS table; then, we'd have a PersonDb class, with just a addressId (integer) field; we have no personDb.getAdress() method, just the plain personDb.getAdressId(). These Java classes are, then, quite dumb (they don't know about persistence or about related classes). A corresponding PersonDao class knows how to load/persist this object. This layer is easy to create and maintain with tools like iBatis + iBator (or MyBatis + MYBatisGenerator).

    哑底层:每个表都有自己的Java类(POJO或DTO),字段直接映射到表列。比方说,我们有一个PERSON表,它的ADDRESS_ID字段指向一个ADRESS表;然后,我们有一个PersonDb类,只有一个addressId (integer)字段;我们没有person . getadress()方法,只有普通的person . getadressid()。因此,这些Java类是相当愚蠢的(它们不知道持久性或相关类)。对应的PersonDao类知道如何加载/持久化这个对象。使用iBatis + iBator(或MyBatis + MYBatisGenerator)这样的工具可以很容易地创建和维护这个层。

  • A higher level layer that contains rich domain objects: each of these is typically a graph of the above POJOs. These classes have also the intelligence for loading/saving the graph (perhaps lazily, perhaps with some dirty flags), by calling the respective DAOs. The important thing, however, is that these rich domain objects do not map one-to-one to the POJO objects (or DB tables), but rather with domain use cases. The "size" of each graph is determined (it doesn't grow indefinitely), and is used from the outside like a particular class. So, it's not that you have one rich Person class (with some indeterminate graph of related objects) that is used is several use cases or service methods; instead, you have several rich classes, PersonWithAddreses, PersonWithAllData... each one wraps a particular well-limited graph, with its own persistence logic. This might seem inefficient or clumsy, and in some context it might be, but it happens often that the use cases when you need to save a full graph of objects are actually limited.

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

  • Additionally, for things like tabular reports, (specific SELECTS that return a bunch of columns to be displayed) you'd not use the above, but straight and dumb POJO's (perhaps even Maps)

    此外,对于表格报表之类的东西(特定的选择返回一组要显示的列),您不会使用上面的方法,而是使用直的和哑的POJO(可能甚至是映射)

See my related answer here

请看我的相关答案

#2


27  

The answer to your many questions is simple. You have three choices.

你的许多问题的答案很简单。你有三个选择。

  1. Use one of the three SQL-centric tools you've mentioned (MyBatis, jOOQ, DbUtils). This means you should stop thinking in terms of your OO domain model and Object-Relational Mapping (i.e. entities and lazy loading). SQL is about relational data and RBDMS are pretty good at calculating execution plans for "eager fetching" the result of several joins. Usually, there isn't even a lot of need for premature caching, and if you do need to cache the occasional data element, you can still use something like EhCache

    使用您提到的三个以sql为中心的工具(MyBatis, jOOQ, DbUtils)。这意味着您应该停止考虑OO域模型和对象关系映射(例如实体和延迟加载)。SQL是关于关系数据的,RBDMS非常擅长计算执行计划,以便“快速获取”多个连接的结果。通常,甚至不太需要提前缓存,如果需要缓存偶尔出现的数据元素,还可以使用EhCache之类的东西

  2. Don't use any of those SQL-centric tools and stick with Hibernate / JPA. Because even if you said you don't like Hibernate, you're "thinking Hibernate". Hibernate is very good at persisting object graphs to the database. None of those tools can be forced to work like Hibernate, because their mission is something else. Their mission is to operate on SQL.

    不要使用任何以sql为中心的工具,使用Hibernate / JPA。因为即使你说你不喜欢冬眠,你也在“思考冬眠”。Hibernate非常擅长将对象图持久化到数据库中。这些工具都不能像Hibernate那样工作,因为它们的任务是别的。他们的任务是对SQL进行操作。

  3. Go an entirely different way and choose not to use a relational data model. Other data models (graphs for instance) may better suit you. I'm putting this as a third option, because you might not actually have that choice, and I don't have much personal experience with alternative models.

    采用完全不同的方式,选择不使用关系数据模型。其他数据模型(例如图表)可能更适合您。我把这个作为第三个选择,因为你可能没有这个选择,我也没有太多的个人经验来选择其他的模型。

Note, your question wasn't specifically about jOOQ. Nonetheless, with jOOQ, you can externalise the mapping of flat query results (produced from joined table sources) to object graphs through external tools such as ModelMapper. There's an intersting ongoing thread about such an integration on the ModelMapper User Group.

注意,你的问题不是关于jOOQ的。尽管如此,通过jOOQ,您可以通过诸如ModelMapper之类的外部工具将平面查询结果(由连接表源生成)映射到对象图。关于在ModelMapper用户组上进行这样的集成,有一个有趣的正在进行的线程。

(disclaimer: I work for the company behind jOOQ)

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

#3


11  

Persistence Approaches

持久的方法

The spectrum of solutions from simple/basic to sophisticated/rich is:

从简单/基本到复杂/丰富的解决方案的范围是:

  • SQL/JDBC - hard-code SQL within objects
  • SQL/JDBC—对象内的硬编码SQL。
  • SQL-Based Framework (e.g. jOOQ, MyBatis) - Active Record Pattern (separate general object represents row data and handles SQL)
  • 基于SQL的框架(例如jOOQ、MyBatis) -活动记录模式(单独的通用对象表示行数据并处理SQL)
  • ORM-Framework (e.g. Hibernate, EclipseLink, DataNucleus) - Data Mapper Pattern (Object per Entity) plus Unit Of Work Pattern (Persistence Context / Entity Manager)
  • ORM-Framework(例如Hibernate、EclipseLink、DataNucleus)——数据映射器模式(每个实体的对象)加上工作模式单元(持久性上下文/实体管理器)

You seek to implement one of the first two levels. That means shifting focus away from the object model towards SQL. But your question asks for Use Cases involving the object model being mapped to SQL (i.e. ORM behaviour). You wish to add functionality from the third level against functionality from one of the first two levels.

您试图实现前两个级别中的一个。这意味着将关注点从对象模型转移到SQL。但是您的问题要求使用涉及对象模型映射到SQL(即ORM行为)的用例。您希望从第三层向前两层中的一个添加功能。

We could try to implement this behaviour within an Active Record. But this would need rich metadata to be attached to each Active Record instance - the actual entity involved, it's relationships to other entities, the lazy-loading settings, the cascade update settings. This would make it effectively a mapped entity object in hiding. Besides, jOOQ and MyBatis don't do this for Use Cases 1 & 2.

我们可以尝试在活动记录中实现此行为。但这需要将丰富的元数据附加到每个活动记录实例——涉及的实际实体、与其他实体的关系、延迟加载设置、级联更新设置。这将使它实际上成为隐藏的映射实体对象。此外,jOOQ和MyBatis在用例1和2中不会这么做。

How To Achieve Your Requests?

如何达到你的要求?

Implement narrow ORM behaviour directly into your objects, as a small custom layer on top of your framework or raw SQL/JDBC.

将窄的ORM行为直接实现到对象中,作为框架或原始SQL/JDBC之上的一个小自定义层。

Use Case 1: Store metadata for each entity object relationship: (i) whether relationship should be lazy-loaded (class-level) and (ii) whether lazy-load has occured (object-level). Then in the getter method, use these flags to determine whether to do lazy-load and actually do it.

用例1:为每个实体对象关系存储元数据:(i)关系是否应该延迟加载(类级),(ii)是否已经延迟加载(对象级)。然后在getter方法中,使用这些标志来确定是否执行延迟加载并实际执行。

Use Case 2: Similar to Use Case 1 - do it yourself. Store a dirty flag within each entity. Against each entity object relationship, store a flag describing whether the save should be cascaded. Then when an entity is saved, recursively visit each "save cascade" relationship. Write any dirty entities discovered.

用例2:类似于用例1——自己做。在每个实体中存储一个脏标志。针对每个实体对象关系,存储描述保存是否应该级联的标志。然后,当保存一个实体时,递归地访问每个“保存级联”关系。写出任何被发现的脏东西。

Patterns

模式

Pros

优点

  • Calls to SQL framework are simple.
  • 对SQL框架的调用很简单。

Cons

缺点

  • Your objects become more complicated. Take a look at the code for Use Cases 1 & 2 within an open source product. It's not trivial
  • 你的对象变得更复杂。看一看开放源码产品中的用例1和2的代码。它不是简单的
  • Lack of support for Object Model. If you're using object model in java for your domain, it will have lesser support for data operations.
  • 缺乏对对象模型的支持。如果您在java中为您的域使用对象模型,那么它对数据操作的支持就会减少。
  • Risk of scope creep & anti-patterns: the above missing functionality is the tip of the iceberg. May end up doing some Reinvent the Wheel & Infrastructure Bloat in Business Logic.
  • 范围渐变和反模式的风险:上面缺失的功能只是冰山一角。在商业逻辑中,最终可能会做一些重新发明*和基础设施的工作。
  • Education and Maintenance on non-standard solution. JPA, JDBC and SQL are standards. Other frameworks or custom solutions aren't.
  • 非标准解决方案的教育和维护。JPA、JDBC和SQL是标准。其他框架或定制解决方案则不然。

Worthwhile???

值得吗? ? ?

This solution works well if you have fairly simple data handling requirements and a data model with a smaller number of entities:

如果您有相当简单的数据处理需求和一个具有较少实体的数据模型,那么这个解决方案就可以很好地工作:

  • If so, great! Do above.
  • 如果是这样,太好了!上面做。
  • If not, this solution's a poor fit and represents false savings in effort - i.e. will end up taking longer and being more complicated than using an ORM. In that case, have another look at JPA - it might be simpler than you think and it supports ORM for CRUD plus raw SQL for complicated queries :-).
  • 如果不是这样,这个解决方案就不太合适,并且表示在工作上的错误节省——也就是说,与使用ORM相比,最终会花费更长的时间和更复杂。在这种情况下,再看一下JPA——它可能比您想象的要简单,它支持CRUD的ORM和复杂查询的原始SQL:-)。

#4


7  

The last ten years I was using JDBC, EJB entity beans, Hibernate, GORM and finally JPA (in this order). For my current project I have returned to using plain JDBC, because the emphasis is on performance. Therefore I wanted

在过去的十年中,我使用了JDBC、EJB实体bean、Hibernate、GORM以及最后的JPA(按此顺序)。对于我目前的项目,我重新使用了纯JDBC,因为重点是性能。因此我想要的

  • Full control on the generation of SQL statements: To be able to pass a statement to DB performance tuners, and put the optimized version back in the program
  • 完全控制SQL语句的生成:能够将语句传递给DB性能调优器,并将优化后的版本放回程序中
  • Full control on the number of SQL statements which are sent to the database
  • 完全控制发送到数据库的SQL语句的数量。
  • Stored procedures (triggers), stored functions (for complex calculations in SQL queries)
  • 存储过程(触发器)、存储函数(用于SQL查询中的复杂计算)
  • To be able to use all available SQL features without restrictions (recursive queries with CTEs, window aggregate functions, ...)
  • 要能够使用所有可用的SQL特性而不受限制(带有cte的递归查询、窗口聚合函数…)

The data model is defined in a data dictionary; using a model driven approach a generator creates helper classes, DDL scripts etc. Most operations on the database are read-only; only few use cases write.

数据模型在数据字典中定义;使用模型驱动的方法,生成器创建助手类、DDL脚本等。只有很少的用例编写。

Question 1: Fetching children

问题1:获取的孩子

The system is built on a use cases, and we have one dedicated SQL statement to get all data for a given use case/request. Some of the SQL statments are bigger than 20kb, they join, calculate using stored functions written in Java/Scala, sort, paginate etc. in a way that the result is directly mapped into a data transfer object which in turn is fed into the view (no further processing in the application layer). As a consequence the data transfer object is use case specific as well. It only contains the data for the given use case (nothing more, nothing less).

系统构建在用例之上,我们有一条专用的SQL语句来获取给定用例/请求的所有数据。有些SQL语句的大小大于20kb,它们通过使用Java/Scala编写的存储函数进行连接、计算、排序、分页等方式,将结果直接映射到数据传输对象中,而数据传输对象又被输入到视图中(在应用层中没有进一步的处理)。因此,数据传输对象也是特定于用例的。它只包含给定用例的数据(没有更多,没有更少)。

As the result set is already "fully joined" there is no need for lazy/eager fetching etc. The data transfer object is complete. A cache is not needed (the database cache is fine); the exception: If the result set is large (around 50 000 rows), the data transfer object is used as a cache value.

由于结果集已经“完全连接”,所以不需要延迟/热切抓取等。数据传输对象已经完成。不需要缓存(数据库缓存没问题);例外情况:如果结果集很大(大约5万行),则数据传输对象用作缓存值。

Question 2: Saving

问题2:储蓄

After the controller has merged back the changes from the GUI, again there is a specific object which holds the data: Basically the rows with a state (new, deleted, modified, ...) in a hierarchy. It's a manual iteration to save the data down the hierarchy: New or modified data is persisted using some helper classes with generate SQL insert or update commands. As for deleted items, this is optimized into cascaded deletes (PostgreSql). If multiple rows are to be deleted, this is optimized into a single delete ... where id in ... statement as well.

在控制器从GUI中合并回更改之后,又有一个特定的对象来保存数据:基本上,在层次结构中具有状态(新的、删除的、修改的…)的行。这是一种手动迭代,将数据保存到层次结构中:使用生成SQL insert或update命令的helper类持久化新的或修改过的数据。对于已删除的项,将其优化为级联删除(PostgreSql)。如果要删除多个行,这将优化为单个删除……在id…声明。

Again this is use case specific, so it's not dealing with a general approch. It needs more lines of code, but these are the lines which contain the optimizations.

这是用例的具体情况,所以不是一般的方法。它需要更多的代码行,但是这些行包含了优化。

The experiences so far

到目前为止的经验

  • One should not underestimate the effort to learn Hibernate or JPA. One should consider the time spent in configuring the caches, cache invalidation in a cluster, eager/lazy fetching, and tuning as well. Migrating to another Hibernate major version is not just a recompilation.
  • 我们不应该低估学习Hibernate或JPA的努力。应该考虑在配置缓存、集群中的缓存失效、快速/延迟抓取和调优中花费的时间。迁移到另一个Hibernate主要版本不仅仅是重新编译。
  • One should not overestimate the effort to build an application without ORM.
  • 没有ORM,我们不应该高估构建应用程序的工作量。
  • It's simpler to use SQL directly - being close to SQL (like HQL, JPQL) is not the same, especially if you talk to your DB performance tuner
  • 直接使用SQL更简单——接近SQL(比如HQL、JPQL)是不一样的,尤其是当您与DB性能调优器交谈时
  • SQL servers are incredibly fast when running long and complex queries, especially if combined with stored functions written in Scala
  • 当运行长而复杂的查询时,SQL服务器的速度非常快,尤其是与用Scala编写的存储函数结合使用时
  • Even with the use case specific SQL statements, the code size is smaller: "Fully joined" result sets save lots of lines in the application layer.
  • 即使使用特定于用例的SQL语句,代码大小也更小:“完全连接”结果集在应用程序层中保存了许多行。

Related information:

相关信息:

Update: Interfaces

更新:接口

If there is a Person entity in the logical data model, there is a class to persist a Person entity (for CRUD operations), just like a JPA entity.

如果在逻辑数据模型中存在一个Person实体,那么就有一个类来持久化一个Person实体(用于CRUD操作),就像一个JPA实体一样。

But the clou is that there is no single Person entity from the use case / query / business logic perspective: Each service method defines its own notion of a Person, and it only contains exactly the values required by that use case. Nothing more, nothing less. We use Scala, so the definition and usage of many small classes is very efficient (no setter/getter boiler plate code required).

但是,clou是,从用例/查询/业务逻辑的角度来看,没有一个人的实体:每个服务方法定义了自己的一个人的概念,并且它只包含了那个用例所要求的值。不多不少。我们使用Scala,所以许多小类的定义和使用非常高效(不需要setter/getter锅炉板代码)。

Example:

例子:

class GlobalPersonUtils {
  public X doSomeProcessingOnAPerson(
    Person person, PersonAddress personAddress, PersonJob personJob, 
    Set<Person> personFriends, ...)
}

is replaced by

取而代之的是

class Person {
  List addresses = ...
  public X doSomeProcessingOnAPerson(...)
}

class Dto {
  List persons = ...
  public X doSomeProcessingOnAllPersons()
  public List getPersons()
}

using use case specific Persons, Adresses etc: In this case, the Person already aggregates all relevant data. Requires more classes, but there is no need to pass around JPA entities.

使用用例特定的人、地址等等:在这种情况下,这个人已经聚集了所有相关的数据。需要更多的类,但是不需要传递JPA实体。

Note that this processing is read-only, the results are used by the view. Example: Get distinct City instances from a person's list.

注意,这个处理是只读的,结果由视图使用。例子:从一个人的列表中获取不同的城市实例。

If data is changed, this is another use case: If the city of a person is changed, this is processed by a different service method, and the person is fetched again from the database.

如果数据被更改,这是另一个用例:如果更改了一个人的城市,那么将使用另一个服务方法进行处理,然后从数据库中再次获取此人。

#5


2  

Warning: This is another shameless plug from a project author.

警告:这是另一个来自项目作者的无耻言论。

Check out JSimpleDB and see if it meets your criteria of simplicity vs. power. This is a new project born out of 10+ years of frustration trying to deal with Java persistence via SQL and ORM.

查看JSimpleDB,看看它是否满足您的简单性和强大性的标准。这是一个新项目,它诞生于10多年来试图通过SQL和ORM处理Java持久性的挫折之中。

It works on top of any database that can function as a key/value store, e.g., FoundationDB (or any SQL database, though everything will be jammed in a single key/value table).

它可以在任何可以作为键/值存储的数据库之上工作,例如FoundationDB(或任何SQL数据库,尽管所有内容都将阻塞在一个键/值表中)。

#6


1  

It sounds like the core issue in the question is with the relational model itself. From what's been described a graph database will map the problem domain very neatly. As an alternative, document stores are another way of approaching the problem because, while the impedance is still there, documents in general are simpler to reason about than sets. Of course, any approach is going to have its own quirks to attend to.

看起来问题的核心在于关系模型本身。根据所描述的,图形数据库将非常清晰地映射问题域。作为一种替代方法,文档存储是解决问题的另一种方法,因为尽管阻抗仍然存在,但是一般来说文档比设置更简单。当然,任何方法都会有自己的怪癖。

#7


0  

Since you want a simple and lightweight library and to use SQL, I can suggest take a look at fjorm. It allows you to use POJOs and CRUD operations without much effort.

既然您想要一个简单的、轻量级的库并使用SQL,我建议您看看fjorm。它允许您使用pojo和CRUD操作,而不需要花费太多精力。

Disclaimer: I'm an author of the project.

免责声明:我是这个项目的作者。

#1


26  

This kind of problem is typical when not using a real ORM, and there is no silver bullet. A simple design approach that worked for me for a (not very big ) webapp with iBatis (myBatis), is to use two layers for persistence:

这种问题在没有使用真金白银时是很典型的,而且也没有什么灵丹妙药。对我来说,一个简单的设计方法是使用两个层来进行持久性:

  • A dumb low-level layer: each table has its Java class (POJO or DTO), with fields that maps directly to the table columns. Say we have a PERSON table with a ADDRESS_ID field that points to an ADRESS table; then, we'd have a PersonDb class, with just a addressId (integer) field; we have no personDb.getAdress() method, just the plain personDb.getAdressId(). These Java classes are, then, quite dumb (they don't know about persistence or about related classes). A corresponding PersonDao class knows how to load/persist this object. This layer is easy to create and maintain with tools like iBatis + iBator (or MyBatis + MYBatisGenerator).

    哑底层:每个表都有自己的Java类(POJO或DTO),字段直接映射到表列。比方说,我们有一个PERSON表,它的ADDRESS_ID字段指向一个ADRESS表;然后,我们有一个PersonDb类,只有一个addressId (integer)字段;我们没有person . getadress()方法,只有普通的person . getadressid()。因此,这些Java类是相当愚蠢的(它们不知道持久性或相关类)。对应的PersonDao类知道如何加载/持久化这个对象。使用iBatis + iBator(或MyBatis + MYBatisGenerator)这样的工具可以很容易地创建和维护这个层。

  • A higher level layer that contains rich domain objects: each of these is typically a graph of the above POJOs. These classes have also the intelligence for loading/saving the graph (perhaps lazily, perhaps with some dirty flags), by calling the respective DAOs. The important thing, however, is that these rich domain objects do not map one-to-one to the POJO objects (or DB tables), but rather with domain use cases. The "size" of each graph is determined (it doesn't grow indefinitely), and is used from the outside like a particular class. So, it's not that you have one rich Person class (with some indeterminate graph of related objects) that is used is several use cases or service methods; instead, you have several rich classes, PersonWithAddreses, PersonWithAllData... each one wraps a particular well-limited graph, with its own persistence logic. This might seem inefficient or clumsy, and in some context it might be, but it happens often that the use cases when you need to save a full graph of objects are actually limited.

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

  • Additionally, for things like tabular reports, (specific SELECTS that return a bunch of columns to be displayed) you'd not use the above, but straight and dumb POJO's (perhaps even Maps)

    此外,对于表格报表之类的东西(特定的选择返回一组要显示的列),您不会使用上面的方法,而是使用直的和哑的POJO(可能甚至是映射)

See my related answer here

请看我的相关答案

#2


27  

The answer to your many questions is simple. You have three choices.

你的许多问题的答案很简单。你有三个选择。

  1. Use one of the three SQL-centric tools you've mentioned (MyBatis, jOOQ, DbUtils). This means you should stop thinking in terms of your OO domain model and Object-Relational Mapping (i.e. entities and lazy loading). SQL is about relational data and RBDMS are pretty good at calculating execution plans for "eager fetching" the result of several joins. Usually, there isn't even a lot of need for premature caching, and if you do need to cache the occasional data element, you can still use something like EhCache

    使用您提到的三个以sql为中心的工具(MyBatis, jOOQ, DbUtils)。这意味着您应该停止考虑OO域模型和对象关系映射(例如实体和延迟加载)。SQL是关于关系数据的,RBDMS非常擅长计算执行计划,以便“快速获取”多个连接的结果。通常,甚至不太需要提前缓存,如果需要缓存偶尔出现的数据元素,还可以使用EhCache之类的东西

  2. Don't use any of those SQL-centric tools and stick with Hibernate / JPA. Because even if you said you don't like Hibernate, you're "thinking Hibernate". Hibernate is very good at persisting object graphs to the database. None of those tools can be forced to work like Hibernate, because their mission is something else. Their mission is to operate on SQL.

    不要使用任何以sql为中心的工具,使用Hibernate / JPA。因为即使你说你不喜欢冬眠,你也在“思考冬眠”。Hibernate非常擅长将对象图持久化到数据库中。这些工具都不能像Hibernate那样工作,因为它们的任务是别的。他们的任务是对SQL进行操作。

  3. Go an entirely different way and choose not to use a relational data model. Other data models (graphs for instance) may better suit you. I'm putting this as a third option, because you might not actually have that choice, and I don't have much personal experience with alternative models.

    采用完全不同的方式,选择不使用关系数据模型。其他数据模型(例如图表)可能更适合您。我把这个作为第三个选择,因为你可能没有这个选择,我也没有太多的个人经验来选择其他的模型。

Note, your question wasn't specifically about jOOQ. Nonetheless, with jOOQ, you can externalise the mapping of flat query results (produced from joined table sources) to object graphs through external tools such as ModelMapper. There's an intersting ongoing thread about such an integration on the ModelMapper User Group.

注意,你的问题不是关于jOOQ的。尽管如此,通过jOOQ,您可以通过诸如ModelMapper之类的外部工具将平面查询结果(由连接表源生成)映射到对象图。关于在ModelMapper用户组上进行这样的集成,有一个有趣的正在进行的线程。

(disclaimer: I work for the company behind jOOQ)

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

#3


11  

Persistence Approaches

持久的方法

The spectrum of solutions from simple/basic to sophisticated/rich is:

从简单/基本到复杂/丰富的解决方案的范围是:

  • SQL/JDBC - hard-code SQL within objects
  • SQL/JDBC—对象内的硬编码SQL。
  • SQL-Based Framework (e.g. jOOQ, MyBatis) - Active Record Pattern (separate general object represents row data and handles SQL)
  • 基于SQL的框架(例如jOOQ、MyBatis) -活动记录模式(单独的通用对象表示行数据并处理SQL)
  • ORM-Framework (e.g. Hibernate, EclipseLink, DataNucleus) - Data Mapper Pattern (Object per Entity) plus Unit Of Work Pattern (Persistence Context / Entity Manager)
  • ORM-Framework(例如Hibernate、EclipseLink、DataNucleus)——数据映射器模式(每个实体的对象)加上工作模式单元(持久性上下文/实体管理器)

You seek to implement one of the first two levels. That means shifting focus away from the object model towards SQL. But your question asks for Use Cases involving the object model being mapped to SQL (i.e. ORM behaviour). You wish to add functionality from the third level against functionality from one of the first two levels.

您试图实现前两个级别中的一个。这意味着将关注点从对象模型转移到SQL。但是您的问题要求使用涉及对象模型映射到SQL(即ORM行为)的用例。您希望从第三层向前两层中的一个添加功能。

We could try to implement this behaviour within an Active Record. But this would need rich metadata to be attached to each Active Record instance - the actual entity involved, it's relationships to other entities, the lazy-loading settings, the cascade update settings. This would make it effectively a mapped entity object in hiding. Besides, jOOQ and MyBatis don't do this for Use Cases 1 & 2.

我们可以尝试在活动记录中实现此行为。但这需要将丰富的元数据附加到每个活动记录实例——涉及的实际实体、与其他实体的关系、延迟加载设置、级联更新设置。这将使它实际上成为隐藏的映射实体对象。此外,jOOQ和MyBatis在用例1和2中不会这么做。

How To Achieve Your Requests?

如何达到你的要求?

Implement narrow ORM behaviour directly into your objects, as a small custom layer on top of your framework or raw SQL/JDBC.

将窄的ORM行为直接实现到对象中,作为框架或原始SQL/JDBC之上的一个小自定义层。

Use Case 1: Store metadata for each entity object relationship: (i) whether relationship should be lazy-loaded (class-level) and (ii) whether lazy-load has occured (object-level). Then in the getter method, use these flags to determine whether to do lazy-load and actually do it.

用例1:为每个实体对象关系存储元数据:(i)关系是否应该延迟加载(类级),(ii)是否已经延迟加载(对象级)。然后在getter方法中,使用这些标志来确定是否执行延迟加载并实际执行。

Use Case 2: Similar to Use Case 1 - do it yourself. Store a dirty flag within each entity. Against each entity object relationship, store a flag describing whether the save should be cascaded. Then when an entity is saved, recursively visit each "save cascade" relationship. Write any dirty entities discovered.

用例2:类似于用例1——自己做。在每个实体中存储一个脏标志。针对每个实体对象关系,存储描述保存是否应该级联的标志。然后,当保存一个实体时,递归地访问每个“保存级联”关系。写出任何被发现的脏东西。

Patterns

模式

Pros

优点

  • Calls to SQL framework are simple.
  • 对SQL框架的调用很简单。

Cons

缺点

  • Your objects become more complicated. Take a look at the code for Use Cases 1 & 2 within an open source product. It's not trivial
  • 你的对象变得更复杂。看一看开放源码产品中的用例1和2的代码。它不是简单的
  • Lack of support for Object Model. If you're using object model in java for your domain, it will have lesser support for data operations.
  • 缺乏对对象模型的支持。如果您在java中为您的域使用对象模型,那么它对数据操作的支持就会减少。
  • Risk of scope creep & anti-patterns: the above missing functionality is the tip of the iceberg. May end up doing some Reinvent the Wheel & Infrastructure Bloat in Business Logic.
  • 范围渐变和反模式的风险:上面缺失的功能只是冰山一角。在商业逻辑中,最终可能会做一些重新发明*和基础设施的工作。
  • Education and Maintenance on non-standard solution. JPA, JDBC and SQL are standards. Other frameworks or custom solutions aren't.
  • 非标准解决方案的教育和维护。JPA、JDBC和SQL是标准。其他框架或定制解决方案则不然。

Worthwhile???

值得吗? ? ?

This solution works well if you have fairly simple data handling requirements and a data model with a smaller number of entities:

如果您有相当简单的数据处理需求和一个具有较少实体的数据模型,那么这个解决方案就可以很好地工作:

  • If so, great! Do above.
  • 如果是这样,太好了!上面做。
  • If not, this solution's a poor fit and represents false savings in effort - i.e. will end up taking longer and being more complicated than using an ORM. In that case, have another look at JPA - it might be simpler than you think and it supports ORM for CRUD plus raw SQL for complicated queries :-).
  • 如果不是这样,这个解决方案就不太合适,并且表示在工作上的错误节省——也就是说,与使用ORM相比,最终会花费更长的时间和更复杂。在这种情况下,再看一下JPA——它可能比您想象的要简单,它支持CRUD的ORM和复杂查询的原始SQL:-)。

#4


7  

The last ten years I was using JDBC, EJB entity beans, Hibernate, GORM and finally JPA (in this order). For my current project I have returned to using plain JDBC, because the emphasis is on performance. Therefore I wanted

在过去的十年中,我使用了JDBC、EJB实体bean、Hibernate、GORM以及最后的JPA(按此顺序)。对于我目前的项目,我重新使用了纯JDBC,因为重点是性能。因此我想要的

  • Full control on the generation of SQL statements: To be able to pass a statement to DB performance tuners, and put the optimized version back in the program
  • 完全控制SQL语句的生成:能够将语句传递给DB性能调优器,并将优化后的版本放回程序中
  • Full control on the number of SQL statements which are sent to the database
  • 完全控制发送到数据库的SQL语句的数量。
  • Stored procedures (triggers), stored functions (for complex calculations in SQL queries)
  • 存储过程(触发器)、存储函数(用于SQL查询中的复杂计算)
  • To be able to use all available SQL features without restrictions (recursive queries with CTEs, window aggregate functions, ...)
  • 要能够使用所有可用的SQL特性而不受限制(带有cte的递归查询、窗口聚合函数…)

The data model is defined in a data dictionary; using a model driven approach a generator creates helper classes, DDL scripts etc. Most operations on the database are read-only; only few use cases write.

数据模型在数据字典中定义;使用模型驱动的方法,生成器创建助手类、DDL脚本等。只有很少的用例编写。

Question 1: Fetching children

问题1:获取的孩子

The system is built on a use cases, and we have one dedicated SQL statement to get all data for a given use case/request. Some of the SQL statments are bigger than 20kb, they join, calculate using stored functions written in Java/Scala, sort, paginate etc. in a way that the result is directly mapped into a data transfer object which in turn is fed into the view (no further processing in the application layer). As a consequence the data transfer object is use case specific as well. It only contains the data for the given use case (nothing more, nothing less).

系统构建在用例之上,我们有一条专用的SQL语句来获取给定用例/请求的所有数据。有些SQL语句的大小大于20kb,它们通过使用Java/Scala编写的存储函数进行连接、计算、排序、分页等方式,将结果直接映射到数据传输对象中,而数据传输对象又被输入到视图中(在应用层中没有进一步的处理)。因此,数据传输对象也是特定于用例的。它只包含给定用例的数据(没有更多,没有更少)。

As the result set is already "fully joined" there is no need for lazy/eager fetching etc. The data transfer object is complete. A cache is not needed (the database cache is fine); the exception: If the result set is large (around 50 000 rows), the data transfer object is used as a cache value.

由于结果集已经“完全连接”,所以不需要延迟/热切抓取等。数据传输对象已经完成。不需要缓存(数据库缓存没问题);例外情况:如果结果集很大(大约5万行),则数据传输对象用作缓存值。

Question 2: Saving

问题2:储蓄

After the controller has merged back the changes from the GUI, again there is a specific object which holds the data: Basically the rows with a state (new, deleted, modified, ...) in a hierarchy. It's a manual iteration to save the data down the hierarchy: New or modified data is persisted using some helper classes with generate SQL insert or update commands. As for deleted items, this is optimized into cascaded deletes (PostgreSql). If multiple rows are to be deleted, this is optimized into a single delete ... where id in ... statement as well.

在控制器从GUI中合并回更改之后,又有一个特定的对象来保存数据:基本上,在层次结构中具有状态(新的、删除的、修改的…)的行。这是一种手动迭代,将数据保存到层次结构中:使用生成SQL insert或update命令的helper类持久化新的或修改过的数据。对于已删除的项,将其优化为级联删除(PostgreSql)。如果要删除多个行,这将优化为单个删除……在id…声明。

Again this is use case specific, so it's not dealing with a general approch. It needs more lines of code, but these are the lines which contain the optimizations.

这是用例的具体情况,所以不是一般的方法。它需要更多的代码行,但是这些行包含了优化。

The experiences so far

到目前为止的经验

  • One should not underestimate the effort to learn Hibernate or JPA. One should consider the time spent in configuring the caches, cache invalidation in a cluster, eager/lazy fetching, and tuning as well. Migrating to another Hibernate major version is not just a recompilation.
  • 我们不应该低估学习Hibernate或JPA的努力。应该考虑在配置缓存、集群中的缓存失效、快速/延迟抓取和调优中花费的时间。迁移到另一个Hibernate主要版本不仅仅是重新编译。
  • One should not overestimate the effort to build an application without ORM.
  • 没有ORM,我们不应该高估构建应用程序的工作量。
  • It's simpler to use SQL directly - being close to SQL (like HQL, JPQL) is not the same, especially if you talk to your DB performance tuner
  • 直接使用SQL更简单——接近SQL(比如HQL、JPQL)是不一样的,尤其是当您与DB性能调优器交谈时
  • SQL servers are incredibly fast when running long and complex queries, especially if combined with stored functions written in Scala
  • 当运行长而复杂的查询时,SQL服务器的速度非常快,尤其是与用Scala编写的存储函数结合使用时
  • Even with the use case specific SQL statements, the code size is smaller: "Fully joined" result sets save lots of lines in the application layer.
  • 即使使用特定于用例的SQL语句,代码大小也更小:“完全连接”结果集在应用程序层中保存了许多行。

Related information:

相关信息:

Update: Interfaces

更新:接口

If there is a Person entity in the logical data model, there is a class to persist a Person entity (for CRUD operations), just like a JPA entity.

如果在逻辑数据模型中存在一个Person实体,那么就有一个类来持久化一个Person实体(用于CRUD操作),就像一个JPA实体一样。

But the clou is that there is no single Person entity from the use case / query / business logic perspective: Each service method defines its own notion of a Person, and it only contains exactly the values required by that use case. Nothing more, nothing less. We use Scala, so the definition and usage of many small classes is very efficient (no setter/getter boiler plate code required).

但是,clou是,从用例/查询/业务逻辑的角度来看,没有一个人的实体:每个服务方法定义了自己的一个人的概念,并且它只包含了那个用例所要求的值。不多不少。我们使用Scala,所以许多小类的定义和使用非常高效(不需要setter/getter锅炉板代码)。

Example:

例子:

class GlobalPersonUtils {
  public X doSomeProcessingOnAPerson(
    Person person, PersonAddress personAddress, PersonJob personJob, 
    Set<Person> personFriends, ...)
}

is replaced by

取而代之的是

class Person {
  List addresses = ...
  public X doSomeProcessingOnAPerson(...)
}

class Dto {
  List persons = ...
  public X doSomeProcessingOnAllPersons()
  public List getPersons()
}

using use case specific Persons, Adresses etc: In this case, the Person already aggregates all relevant data. Requires more classes, but there is no need to pass around JPA entities.

使用用例特定的人、地址等等:在这种情况下,这个人已经聚集了所有相关的数据。需要更多的类,但是不需要传递JPA实体。

Note that this processing is read-only, the results are used by the view. Example: Get distinct City instances from a person's list.

注意,这个处理是只读的,结果由视图使用。例子:从一个人的列表中获取不同的城市实例。

If data is changed, this is another use case: If the city of a person is changed, this is processed by a different service method, and the person is fetched again from the database.

如果数据被更改,这是另一个用例:如果更改了一个人的城市,那么将使用另一个服务方法进行处理,然后从数据库中再次获取此人。

#5


2  

Warning: This is another shameless plug from a project author.

警告:这是另一个来自项目作者的无耻言论。

Check out JSimpleDB and see if it meets your criteria of simplicity vs. power. This is a new project born out of 10+ years of frustration trying to deal with Java persistence via SQL and ORM.

查看JSimpleDB,看看它是否满足您的简单性和强大性的标准。这是一个新项目,它诞生于10多年来试图通过SQL和ORM处理Java持久性的挫折之中。

It works on top of any database that can function as a key/value store, e.g., FoundationDB (or any SQL database, though everything will be jammed in a single key/value table).

它可以在任何可以作为键/值存储的数据库之上工作,例如FoundationDB(或任何SQL数据库,尽管所有内容都将阻塞在一个键/值表中)。

#6


1  

It sounds like the core issue in the question is with the relational model itself. From what's been described a graph database will map the problem domain very neatly. As an alternative, document stores are another way of approaching the problem because, while the impedance is still there, documents in general are simpler to reason about than sets. Of course, any approach is going to have its own quirks to attend to.

看起来问题的核心在于关系模型本身。根据所描述的,图形数据库将非常清晰地映射问题域。作为一种替代方法,文档存储是解决问题的另一种方法,因为尽管阻抗仍然存在,但是一般来说文档比设置更简单。当然,任何方法都会有自己的怪癖。

#7


0  

Since you want a simple and lightweight library and to use SQL, I can suggest take a look at fjorm. It allows you to use POJOs and CRUD operations without much effort.

既然您想要一个简单的、轻量级的库并使用SQL,我建议您看看fjorm。它允许您使用pojo和CRUD操作,而不需要花费太多精力。

Disclaimer: I'm an author of the project.

免责声明:我是这个项目的作者。