使用 lombok 的 @Data 注釋時,我從 IntelliJ IDEA 得到了這個建議。
有問題的類是@Entity。有人可以解釋一下:
- 它到底做了什么(尤其是Hibernate的部分)
- 與逐一比較每個欄位相比,這種方法是否更受歡迎?如果是,為什么?
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o))
return false;
MyObject that = (MyObject ) o;
return id != null && Objects.equals(id, that.id);
}
該專案包含/使用 Spring boot、Hibernate、Lombok。
謝謝
uj5u.com熱心網友回復:
作業中存在一個基本問題,這是 JPA/Hibernate 固有的問題。在這個例子中,假設我們有一個名為 的 db 表User,并且我們有一個名為 的類User來建模它。
問題歸結為簡單的:
java類User代表什么?它代表'資料庫表“用戶”中的一行',還是代表一個用戶?
根據您的回答,您對 equals 方法的要求截然不同。根據您選擇的 equals 方法,錯誤地回答這個問題會導致代碼錯誤。據我所知,沒有實際的“標準”,人們只是做了一些事情,大多數人沒有意識到這是一個基本問題。
它代表資料庫中的一行
這樣的解釋然后會建議您的 equals 方法的以下實作:
如果在 DB 定義中對主鍵列建模的所有欄位在兩個實體之間相等,則它們是相等的,即使其他(非主鍵)欄位不同。畢竟,這就是 DB 確定相等性的方式,因此 Java 代碼應該與之匹配。
在處理 NULL 時,java 代碼應該像 SQL。也就是說,與幾乎每個相等定義不同的是,equals 方法代碼生成器(包括 lombok、intellij和eclipse),甚至在這種模式下的
Objects.equals方法也應該是 FALSE,就像在 SQL 中一樣!具體來說,如果任何主鍵欄位具有空值,則該物件不能等于任何其他物件,甚至是其自身的副本;為了遵守java規則,它可以(必須,真的)等于它自己的參考。null == null
換句話說:
- 如果 [A] 它們實際上是同一個物件 (
this == other),或者 [B] BOTH 物件的unid欄位已初始化且相等,則任何 2 個物件都相等。無論您是使用null還是0跟蹤“尚未寫入資料庫”,該值都會立即取消該行與任何其他行相等的資格,即使是另一個具有 100% 相同值的行。
畢竟,如果您創建 2 個單獨的新物件并且save()它們都創建,它們將變成 2 個單獨的行。
它代表一個用戶物件
然后發生的情況是 equals 規則執行 180。假設它是unid樣式主鍵而不是自然主鍵,主鍵本質上是一個實作細節。想象一下,在您的資料庫中,您最終為完全相同的用戶提供了2行(大概是有人搞砸了并且未能在用戶名上添加 UNIQUE 約束,也許)。在系統上的用戶語意模型中,用戶是通過用戶名來唯一標識的,因此,平等性僅由用戶名來定義。具有相同用戶名但不同 unid 值的 2 個物件仍然相等。
那我拿哪一個?
我不知道。幸運的是,您的問題要求解釋而不是答案!
IntelliJ 告訴您的是采用第一個解釋(DB 中的行),甚至null正確應用這些奇怪的東西,因此在 IntelliJ 中撰寫建議工具的人至少似乎了解正在發生的事情。
就其價值而言,我認為“代表資料庫中的一行”是更“有用”的解釋(因為不這樣做涉及呼叫 getter,這會使相等性檢查變得非常昂貴,因為它可能會導致數百個 SELECT 呼叫和大量當您將一半的 DB 放入堆記憶體中時!),但是,“類的實體User代表系統中的用戶”是更像 Java 的解釋,也是大多數 Java 程式員會的解釋(錯誤的是,如果您使用intellij 的建議在這里)默默地假設。
我在自己的編程作業中解決了這個問題,首先從不使用 hibernate/JPA,而是使用 JOOQ 或 JDBI 之類的工具。但是,缺點是通常你最終會得到更多的代碼——你確實有時有一個物件,例如呼叫UserRow,代表一個用戶行,和一個物件,例如呼叫User,代表系統上的用戶。
另一個技巧可能是決定將所有 Hibernate 模型類命名為XRow. 名稱很重要,并且是最好的檔案:這使得此代碼的所有用戶都沒有任何關于他們將如何解釋其語意含義的線索和線索:DB 中的行。因此,intellij 建議將是您的 equals 實作。
NB: Lombok is java and not Hibernate specific, so it makes the 'represents a user in the system' choice. You can try to push lombok towards the 'row in DB' interpretation by telling lombok to only use the id field (stick an @EqualsAndHashCode.Include on that field), but lombok would still consider 2 null values / 2 0 values identical even though it shouldn't. This is on hibernate, as it is breaking all sorts of rules and specs.
(NB: Added due to a comment on another answer)
Why is .getClass() being invoked?
Java has sensible rules about what equals is supposed to mean. This is in the javadoc of the equals method and these rules can be relied upon (and are, by e.g. HashSet and co). The rules are:
- If
aequals(b)is true ,a.hashCode() == b.hashCode()must also be true. a.equals(a)must be true.- If
a.equals(b)thenb.equals(a)must also be true. - If
a.equals(b)andb.equals(c)thena.equals(c)must also be true.
Sensible and simple, right?
Nope. That's actually really complex.
Imagine you make a subclass of ArrayList: You decide to give lists a colour. You can have a blue list of strings and a red list of strings.
Right now the equality method of ArrayList checks if the that is a list and if so, compares elements. Seems sensible, right? We can see it in action:
List<String> a = new ArrayList<String>();
a.add("Hello");
List<String> b = new LinkedList<String>();
b.add("Hello");
System.out.println(a.equals(b));
This prints true.
Let's now make our coloured arraylist implementation: class ColoredList<T> extends ArrayList<T> { .. }. Surely, a red empty list is no longer equal to a blue empty list right?
Nope, you'd be breaking rules if you do that!
List<String> a = new ArrayList<String>();
List<String> b = new ColoredList<String>(Color.RED);
List<String> c = new ColoredList<String>(Color.BLUE);
System.out.println(a.equals(b));
System.out.println(a.equals(c));
System.out.println(b.equals(c));
That prints true/true/false which is invalid. The conclusion is that it is in fact impossible to make any list subclass that adds some semantically relevant information. The only subclasses that can exist are those which either actively break spec (bad idea), or whose additions have no impact on equality.
There is a different view of things which says that you ought to be able to make such classes. Again we're struggling, just like with the JPA/Hibernate case, about what equals is even supposed to mean.
A more common and far better default behaviour for your equals implementations is to simply state that any 2 objects can only be equal if they are of the exact same type: An instance of Dog cannot be equal to an instance of Animal.
The only way to accomplish this, given that the rule a.equals(b)? Then b.equals(a) exists, is that animal checks the class of that and returns false if it isn't exactly Animal. In other words:
Animal a = new Animal("Betsy");
Cow c = new Cow("Betsy");
a.equals(c); // must return false!!
The .getClass() check accomplishes this.
Lombok gives you the best of both worlds. It can't perform miracles, so it won't take away the rule that at the type level you need to choose extensibility, but lombok has the canEqual system to deal with this: The equals code of Animal will ask the that code if the two things can be equal. In this mode, if you have some non-semantically-different subclass of animal (such as ArrayList, which is a subclass of AbstractList and doesn't change the semantics at all, it just adds implementation details that have no bearing on equality), it can say that it can be equal, whereas if you have one that is semantically different, such as your coloured list, it can say that none are.
In other words, going back to the coloured lists, IF ArrayList and co were written with lombok's canEqual system, this could have worked out, you could have had the results (where a is an arraylist, b is a red list, and c is a blur list):
a.equals(b); // false, even though same items
a.equals(c); // false, same reason.
b.equals(c); // false and now it's not a conflict.
Lombok's default behaviour is that all subtypes add semantic load and therefore any X cannot be equal to any Y where Y is a subclass of X, but you can override this by writing out the canEqual method in Y. You would do that if you write a subclass that doesn't add semantic load.
This isn't going to help you in the slightest with the problems above about hibernate.
Who knew something as seemingly simple as equality is hiding 2 intractably difficult philosophical treatises, huh?
有關 canEqual 的更多資訊,
IntelliJ 默認使用檔案“equalsHelper.vm”。我在https://github.com/JetBrains/intellij-community/blob/master/java/java-impl/src/com/intellij/codeInsight/generation/equalsHelper.vm找到了該檔案版本的可能來源
它包含以下內容:
#macro(addInstanceOfToText)
#if ($checkParameterWithInstanceof)
if(!($paramName instanceof $classname)) return false;
#else
if($paramName == null || getClass() != ${paramName}.getClass()) return false;
#end
#end
很明顯你有那個檔案的不同版本?或者你使用不同的模板?也許一些插件改變了它?
uj5u.com熱心網友回復:
如果兩個物件屬于不同的類,則它們不相等。
對于“首選”,這取決于“ID”是什么。最后一行似乎有點多余;本來可以
return Objects.equals(id, that.id);
因為 null 的情況是由 Objects.equals 處理的。但按照我的口味,寫起來更清楚
return id != null && id.equals(that.id);
額外的層沒有添加我在示例中看到的任何內容。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/330354.html
