关于 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 方法等