關於 Hibernate StackOverflowError 異常的一個解決方法
關於 Hibernate StackOverflowError 異常的一個解決方法
踩坑經歷
今天在學習 Hibernate 一對多對映的時候,使用 Hibernate 的建表策略進行建立資料庫表。執行測試類自動建立表並新增資料時,出現了個 bug 。報了 StackOverflowError 異常。報錯如下:
下面這個報錯是解決後重新模擬的,一開始報錯指向的是實體類的 toString 方法,這裡是指向 hashCode 方法。但是他們報錯的源頭都是相同的。
環境
我的實體類也很簡單,一個 User 類,一個 Car 類。他們之間的對應關係是一對多。一個使用者可以有很多輛小轎車。
先說下我的環境,拋開環境談異常都是耍流氓【狗頭】。但如果不想看環境可以直接跳到結果處
實體類使用了Lombok外掛,@Data 是自動給加了註解的類生成setter/getter、equals、canEqual、hashCode、toString方法。@AllArgsConstructor 是自動生成全參構造器。@NoArgsConstructor 是自動生成無參構造器
User
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { /** * id */ private Integer id; /** * 使用者名稱 */ private String name; /** * 密碼 */ private String password; /** * 使用者擁有的車輛 */ private Set<Car> cars; }
Car
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
/**
* 車輛id
*/
private Integer id;
/**
* 車輛名
*/
private String name;
/**
* 車輛顏色
*/
private String color;
}
Hibernate 核心配置如下:
hibernate.cfg.xml
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 四個必填項 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate?useTimezone=true&serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=UTF-8&useSSL=false</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <!-- 可選項 --> <!-- 顯示 SQL 語句 --> <property name="hibernate.show_sql">true</property> <!-- 顯示的 SQL 語句是否格式化,也就是說打印出來的 SQL 語句是一行還是多行顯示 --> <property name="hibernate.format_sql">true</property> <!-- 配置方言 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</property> <!-- 配置建表策略 --> <property name="hibernate.hbm2ddl.auto">update</property> <!-- 配置 mapping --> <mapping resource="mapper/User.hbm.xml"/> <mapping resource="mapper/Car.hbm.xml"/> </session-factory> </hibernate-configuration>
Hibernate mapping配置如下:
User.hbm.xml
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.xp.model.entity">
<!-- orm 對映 實體類和資料表進行對映 -->
<class name="User" table="user">
<!-- 對映主鍵,其中name表示實體類中的成員變數,column 表示對應表中的欄位 -->
<id name="id" column="id">
<!-- 設定主鍵策略 -->
<generator class="native"/>
</id>
<!-- 對映欄位,其中name表示實體類中的成員變數,column 表示對應表中的欄位 -->
<property name="name" column="name"/>
<property name="password" column="password"/>
<!-- 一對多關係對映 -->
<set name="cars" >
<key column="cid"/>
<one-to-many class="Car"/>
</set>
</class>
</hibernate-mapping>
Car.hbm.xml
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.xp.model.entity">
<class name="Car" table="car">
<!-- 主鍵對映 -->
<id name="id" column="id">
<!-- 設定主鍵對映規則 -->
<generator class="native"/>
</id>
<!-- 其他欄位對映 -->
<property name="name" column="name"/>
<property name="color" column="color"/>
<!-- 多對一關係對映 -->
<many-to-one name="user" class="User" column="cid"/>
</class>
</hibernate-mapping>
測試類
@Test
public void test() {
// 獲取會話
Session session = HibernateUtil.getSession();
// 初始化使用者和汽車
User user = new User(null, "李老闆", "888", null);
Car car1 = new Car(null, "寶馬", "紅色", null);
Car car2 = new Car(null, "賓士", "白色", null);
Car car3 = new Car(null, "保時捷", "藍色", null);
// 讓使用者擁有三輛車
Set<Car> cars = new HashSet<>();
cars.add(car1);
cars.add(car2);
cars.add(car3);
user.setCars(cars);
// 讓車擁有主人
car1.setUser(user);
car2.setUser(user);
car3.setUser(user);
// 開啟事務,並將物件中儲存的資料寫入資料庫中
Transaction transaction = session.beginTransaction();
session.save(user);
session.save(car1);
session.save(car2);
session.save(car3);
transaction.commit();
session.close();
}
一開始,我以為是 Lombok 的問題(也可以說是Lombok的問題,畢竟這個偷懶導致自己實體類的方法看不到),將 lombok 的註解註釋掉然後手動按照常規實體類編寫 getter,setter,構造器,toString() ,結果還是報錯。一開始用 lombok 時報錯時 hashcode 方法,現在報 toString 方法,然後我嘗試將 toString 方法去掉,然後就解決了。但是,報錯是解決了,但是整個人還是懵的。查了百度,發現其實他們講的並不是很全:刪除掉 toString 中關聯關係的物件。然後結合之前的 hashCode 方法的報錯以及報錯圖(在本文最上面)中重複刪除建立表,我推測是這樣的:
報錯原因及解決方法
主要原因就是父子表出現了雙向屬性,會導致 Hibernate 呼叫方法自動建立表或插入資料的時候死迴圈,方法不斷入棧,最終導致棧記憶體溢位。
回看剛剛的程式碼,Lombok 幫我完成了 toString() 和 hashCode() 方法的重寫 ,所以這兩個方法裡面的具體演算法我是不知道的。而我自己手動寫實體類中的方法時,是用 idea 自動生成的(這年頭也沒多少人是完全手動寫的吧[狗頭]),預設toString 是輸出所有的成員變數。
問題就出在這!
我們在方法中使用了關聯關係的物件。
所以解決方法就是:將實體類中事用了關聯關係的物件的方法進行修改,不單單是 toString() 方法。比如 hashCode 方法等