关于 Hibernate StackOverflowError 异常的一个解决方法

关于 Hibernate StackOverflowError 异常的一个解决方法

踩坑经历

今天在学习 Hibernate 一对多映射的时候,使用 Hibernate 的建表策略进行创建数据库表。运行测试类自动创建表并添加数据时,出现了个 bug 。报了 StackOverflowError 异常。报错如下:

下面这个报错是解决后重新模拟的,一开始报错指向的是实体类的 toString 方法,这里是指向 hashCode 方法。但是他们报错的源头都是相同的。

image-20200729190457548

环境

我的实体类也很简单,一个 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&amp;serverTimezone=GMT%2b8&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;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 方法等

posted @ 2020-07-29 19:22  Windows_XP  阅读(406)  评论(0编辑  收藏  举报