报错信息如下:
当使用Hibernate和Lombok处理表与表之间的关系时,在插入数据时可能会遇到栈溢出错误。这篇博客将详细讨论此问题的原因,并提供解决办法。
标题: Hibernate+Lombok进行表与表之间关系时插入数据时栈溢出
问题背景
Hibernate是一个Java持久化框架,通过对象关系映射(ORM)的方式,将Java对象映射到关系型数据库中的表。Lombok是一个Java库,通过自动代码生成的方式,简化了Java类的编写。
在使用Hibernate和Lombok时,我们通常使用注解来描述表与表之间的关系,例如一对多、多对一等关联关系。然而,有时在插入数据时会遇到栈溢出错误,这可能导致开发者困惑和疑惑。
源代码:
@Data
public class LinkMan {
private int uid;
private String username;
private String tel;
private String gender;
Customer customer;
}
@Data
public class Customer {
private int cid;
private String name;
private String address;
Set<LinkMan> linkManSet = new HashSet<>();
}
@Test
public void relationTest() {
Session session = HibernateUtil.getCurrSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setName("腾讯");
customer.setAddress("深圳");
Customer customer1 = new Customer();
customer1.setName("阿里巴巴");
customer1.setAddress("杭州");
LinkMan linkMan = new LinkMan();
linkMan.setUsername("李彦宏");
linkMan.setTel("110");
linkMan.setGender("男");
LinkMan linkMan1 = new LinkMan();
linkMan1.setUsername("陈迪凯");
linkMan1.setTel("119");
linkMan1.setGender("男");
LinkMan linkMan2 = new LinkMan();
linkMan2.setUsername("马化腾");
linkMan2.setTel("120");
linkMan2.setGender("女");
LinkMan linkMan3 = new LinkMan();
linkMan3.setUsername("张小龙");
linkMan3.setTel("114");
linkMan3.setGender("男");
customer.getLinkManSet().add(linkMan);
customer.getLinkManSet().add(linkMan1);
linkMan.setCustomer(customer);
linkMan1.setCustomer(customer);
customer1.getLinkManSet().add(linkMan2);
customer1.getLinkManSet().add(linkMan3);
linkMan2.setCustomer(customer1);
linkMan3.setCustomer(customer1);
//保存数据
session.save(customer);
session.save(customer1);
session.save(linkMan);
session.save(linkMan1);
session.save(linkMan2);
session.save(linkMan3);
transaction.commit();
session.close();
}
<?xml version="1.0" encoding="utf-8" ?>
<!-- 引入核心配置文件约束 -->
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- 配置hibernate核心配置文件 -->
<session-factory>
<!-- 1、数据库信息 必需 -->
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/hibernate_study?serverTimezone=GMT&useSSL=false&characterEncoding=utf8
</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 2、hibernate信息 非必需 -->
<!-- 在控制台输出hibernate底层生成的SQL语句 -->
<property name="show_sql">true</property>
<!-- 格式化hibernate底层生成的SQL语句 -->
<property name="format_sql">true</property>
<!-- 数据库方言 -->
<property
name="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect
</property>
<!-- 设置事务的隔离级别
值为1:事务隔离级别为“读未提交”
值为2:事务隔离级别为“读已提交”
值为3:事务隔离级别为“可重复读”
值为4:事务隔离级别为“可串行化”
-->
<property name="hibernate.connection.isolation">4</property>
<!-- session绑定本地线程 -->
<property name="current_session_context_class">thread</property>
<!-- hibernate自动创建数据库表,如果表已经存在,则更新表,如果不存在,则创
建表格 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 3、引用hibernate映射关系配置文件,mapping标签必须在property标签后
面 必需 -->
<mapping resource="mapper/user.hbm.xml"/>
<mapping resource="mapper/customer.hbm.xml"/>
<mapping resource="mapper/linkman.hbm.xml"/>
</session-factory>
</hibernate-configuration>
<?xml version="1.0" encoding="utf-8" ?>
<!-- 引入映射关系配置文件约束 -->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- 配置对象和数据库表之间的映射关系
name:实体类的全路径名
table:数据库表的名称
-->
<class name="icu.chiou.entity.User" table="tb_user">
<!-- 配置主键映射关系
name:实体类中的属性名
column:数据库表中主键名称,column属性可以省略,如果省略,则自动生成的数据
库表主键名称和实体类名称一致
-->
<id name="id" column="id">
<!-- 主键生成策略 -->
<generator class="native"/>
</id>
<!-- 配置其他属性的映射关系 -->
<property name="username" column="username"/>
<property name="password" column="password"/>
</class>
</hibernate-mapping>
查证问题
经过多方查证,发现栈溢出错误与Lombok的@Data
注解有关。@Data
注解是Lombok提供的一个实用注解,它自动生成了一些常用方法,包括equals()
、hashCode()
和toString()
等。然而,问题出现在hashCode()
方法的生成上。
在默认情况下,Lombok的@Data
注解会重写hashCode()
方法,并使用所有字段来计算哈希码。这其中包括了关联对象的字段,从而导致在计算哈希码时发生循环调用,最终导致栈溢出错误:
- 调用this.getLinkManSet();。
- linkmanset里面又包含customer
- 调用this.getCustomer();里面又包含linkman
为了更好地理解这个问题,我们可以查看编译后的类文件。编译后的类文件会展示出Lombok生成的代码。以下是一个示例:
Customer.class
public int hashCode() {
int PRIME = true;
int result = 1;
result = result * 59 + this.getCid();
Object $name = this.getName();
result = result * 59 + ($name == null ? 43 : $name.hashCode());
Object $address = this.getAddress();
result = result * 59 + ($address == null ? 43 : $address.hashCode());
Object $linkManSet = this.getLinkManSet();
result = result * 59 + ($linkManSet == null ? 43 : $linkManSet.hashCode());
return result;
}
LinkMan.class
public int hashCode() {
int PRIME = true;
int result = 1;
result = result * 59 + this.getUid();
Object $username = this.getUsername();
result = result * 59 + ($username == null ? 43 : $username.hashCode());
Object $tel = this.getTel();
result = result * 59 + ($tel == null ? 43 : $tel.hashCode());
Object $gender = this.getGender();
result = result * 59 + ($gender == null ? 43 : $gender.hashCode());
Object $customer = this.getCustomer();
result = result * 59 + ($customer == null ? 43 : $customer.hashCode());
return result;
}
从上述示例中可以看出,hashCode()
方法中使用了关联对象otherEntity
的hashCode()
方法,这可能导致循环调用,最终触发栈溢出错误。
解决办法
针对上述问题,我们可以采取以下解决办法:
- 使用
@Getter
和@Setter
代替@Data
:@Data
注解是Lombok提供的一个快捷注解,它包含了@Getter
、@Setter
、@EqualsAndHashCode
和@ToString
等注解
的功能。但在处理关联对象时,我们可以避免使用@Data
注解,而是手动添加@Getter
和@Setter
注解。这样就能避免在hashCode()
方法中出现循环调用的问题。
修改后的示例代码如下:
@Getter
@Setter
public class LinkMan {
private int uid;
private String username;
private String tel;
private String gender;
Customer customer;
}
通过手动添加@Getter
和@Setter
注解,我们可以避免在hashCode()
方法中引发循环调用的问题,从而解决栈溢出错误。
结论
在使用Hibernate和Lombok处理表与表之间的关系时,特别是在涉及关联对象的hashCode()
方法中,需要小心使用Lombok的@Data
注解。通过使用@Getter
和@Setter
注解,我们可以避免栈溢出错误,并确保对象关系映射的正确性。
探讨了栈溢出错误的原因和解决办法后,我们希望这篇博客能帮助到遇到类似问题的开发者们。
如果你在使用Hibernate和Lombok过程中遇到其他问题,或者有其他相关的疑问,欢迎在评论区留言,我们将竭诚为您解答。