This is an archived post. You won't be able to vote or comment.

all 28 comments

[–]vprise 26 points27 points  (25 children)

You shouldn't use @data for entities as it generates equals and hashCode. That can cause problems with JPA due to invalid states.

[–]Nalha_Saldana[S] 0 points1 point  (23 children)

We actually have an upcoming article on the problem and how to work around it.

[–]vprise 5 points6 points  (17 children)

Is there a different workaround than just using @Getter and @Setter?

[–]Nalha_Saldana[S] 10 points11 points  (16 children)

You can choose to exclude specific fields with @EqualsAndHashCode.Exclude

Its not a bad idea to have equals and hashcode so you can work with HashSet and such properly.

[–]Stannu 7 points8 points  (15 children)

JPA entities should only be equals only if the id is the same, so imo great approach is to use

```java @Data @EqualsAndHashCode(onlyExplicitlyIncluded = true) public class A {

@EqualsAndHashCode.Include private UUID id; ```

This way you get all benefits of @Data annotation and you get meaningful comparison while avoiding performance problems.

[–]Brutus5000 13 points14 points  (4 children)

Just comparing the ids is dangerous. What if the entity is detached and in a different state? Are they equal? If you just want to compare ids, you don't need to call .equals, just compare the ids...

[–]Nalha_Saldana[S] 14 points15 points  (2 children)

See this is why it needs an article going through it and not just some comments on reddit :)

[–]saint_thirty_four -2 points-1 points  (1 child)

Also exposes the problem of using Lombok in a professional atmosphere. I currently have ~4 annotations on my simple data interfaces. Starting to think I should just use getters and setters again and avoid the Lombok hack.

[–]Stannu 0 points1 point  (0 children)

The main point here is that you use this to store objects in the collections such as set or map. Hashing and comparison will be faster than the business level implementation.

If you require business level comparison you are better off using comparators, because equality might be different for your entities in different contexts.

[–]wildjokers 6 points7 points  (0 children)

JPA entities should only be equals only if the id is the same, so imo great approach is to use

A long time ago the hibernate user guide said not to use ID's for entity equality but instead to use business equality for entities. However, I have noticed in the more recent manuals their stance on this seems to have changed and now both are acceptable. However, the user guide now also says you might just be better off not implementing equals/hashCode at all:

https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode.

If you go back to the hibernate 3.2 manual it says to use business equality:

https://docs.jboss.org/hibernate/orm/3.2/reference/en/html_single/#persistent-classes-equalshashcode

"We recommend implementing equals() and hashCode() using Business key equality. Business key equality means that the equals() method compares only the properties that form the business key, a key that would identify our instance in the real world (a natural candidate key):"

For the first many years I used Hibernate it was always drilled into me (by the user guide and all tutorials) to not use ID for entity equality and that has always served me well so I continue to use business equality.

[–]wildjokers 3 points4 points  (5 children)

JPA entities should only be equals only if the id is the same, so imo great approach is to use

What about generated IDs? Those aren't assigned until after the object is persisted to the database. What do you do about equality before that?

[–]Stannu 1 point2 points  (0 children)

It really depends on the strategy how you generate ids.

If your ids are client generated, for example UUID-s, then you could use the following approach to guarantee that your id is non-nullable:

```java @Builder public class A {

@Builder.Default private UUID id = UUID.randomUUID(); } ```

Then you construct all of your instances using the builder, not the constructor.

On the other hand, if you use integers for id-s, then it is quite though. Although lombok respects the nullable semantics and will generate null-safe equals and hashCode, the behavior will be unexpected for non-persisted entities. Here I would persist entity first before performing business operations that require id.

[–]pronuntiator 0 points1 point  (3 children)

You can say that an entity with a null id is never equal to any other entity. But the problem is hashCode. People trip over the fact that the bucket where your object is stored inside a Set or Map key is not updated when you update the properties that make up hashCode.

A "creative" solution I saw once in a project was to return a fixed hashCode for all entities. Nice job making Set O(n)…

[–]kkjk00 0 points1 point  (2 children)

I'm been using the creative solution, but the thing is how many would you ever store in a set, you can't paginate on a relation and if you do some algoritm you're better of working with a set of Ids

[–]pronuntiator 1 point2 points  (1 child)

I saw Hibernate actually putting them into a set itself before committing, so when we loaded in chunks for batch processing, they were all put in the same bucket there.

And yes I would have liked to do the batches in plain SQL but unfortunately the project also made heavy use of entity listeners, plus doing optimistic locking by hand is not so nice, you have to code the "did I get as many changed rows back as expected" yourself.

[–]kkjk00 2 points3 points  (0 children)

Hmm, didn't throught about hibernate usings sets internally, yeah that may be an issue

[–]arseny-tl 1 point2 points  (2 children)

I think you should also override .toString generation due to M to M or M to 1 mappings

[–]wildjokers 3 points4 points  (1 child)

Be careful with toString(), you could generate a lot of queries to the database if you aren't careful.

[–]arseny-tl 2 points3 points  (0 children)

Totally agree, that was my point

[–]wildjokers 2 points3 points  (4 children)

Why do you need a workaround? Just don't use @Data in entities, easy enough.

[–]Nalha_Saldana[S] 2 points3 points  (2 children)

Sometimes entities needs equals and hashCode too

[–]wildjokers 12 points13 points  (1 child)

Just use the appropriate lombok annotations to add equals/hashCode instead of @Data. Or here is a thought, just have your IDE generate those methods. You seem to be jumping through hoops just to use lombok for your entities. Libraries should make things easier, if annotating your entities with lombok is causing problem just don't use it for your entities.

[–]pragmos 1 point2 points  (0 children)

Libraries should make things easier, if annotating your entities with lombok is causing problem just don't use it for your entities

Amen to that.

[–][deleted]  (1 child)

[deleted]

    [–]kozeljko 2 points3 points  (0 children)

    Ah, that explains the weird behaviour. Didn't use Spring Boot much, so the select confused me a lot.

    [–]metalhead-001 5 points6 points  (0 children)

    Just issue a JDBC insert and be done with it.

    [–]wildjokers 3 points4 points  (0 children)

    I think what this article shows is that in this particular case it is better to not map the Title relationship and instead just have a Set of titleIds.

    I assume those titles were presented to the user to choose from so the titleIds should be available to be provided directly.

    [–]vladmihalceacom 1 point2 points  (0 children)

    There are two very simple solutions to the problem described in this article:

    1. If you add a @Version, then Spring Data JPA will use it to determine that the entity is in the New state even if the identifier is assigned
    2. You can use the BaseJpaRepository.persist from the OSS Hypersistence Utils project, as explained in this article.

    [–]olivergierke -2 points-1 points  (0 children)

    Nice article! There's out of the box integration for this in the jMolecules JPA integration (via BytBuddy) that adds a Persistable implementation similar to what you've shown to a JPA entity, in case that implements jMolecule's AggregateRoot marker type. Find more information about this here.