1.问题展现
使用环境:
jdk1.8
maven工程
SpringData JPA
lombok
hibernate
pom文件:
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
<hibernate.version>5.0.7.Final</hibernate.version>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
<c3p0.version>0.9.1.2</c3p0.version>
<mysql.version>5.1.6</mysql.version>
</properties>
<dependencies>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring beg -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring对orm框架的支持包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring end -->
<!-- hibernate beg -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.1.Final</version>
</dependency>
<!-- hibernate end -->
<!-- c3p0 beg -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency>
<!-- c3p0 end -->
<!-- log end -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- spring data jpa 的坐标-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.9.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- el beg 使用spring data jpa 必须引入 -->
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.0.12.Final</version>
<scope>compile</scope>
</dependency>
<!-- el end -->
<!-- 哎,这个要是不了解原理慎用@Data注解 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
有关框架知识和工程搭建这里就不说了。
简单阐释问题所在,在使用SpringData JPA的级联操作的时候,就是使用级联查询的时候,hibernate在帮我们生成查询语句的时候会无限生成查询语句执行直到栈内存溢出。(关于SpringDataJPA的搭建和级联操作这里就不说了)
2.找出问题和解决方案
按着错误挨个找,直到找到了hashCode()方法报错(这里找问题所在,我就是根据程序报错一行一行找,尤其是那些有额外错误说明的),才大概知道原因,hashCode()方法不就是通过lombok帮我们生成的吗?
于是乎尝试一下,仅仅手动设置getter、setter方法,程序正常运行!
3.万恶之源
这里已经知道了是使用lombok才出错了,于是乎百度去了,long time passed...
这里通过这位仁兄找到了原因,在这里感谢一波。地址
这里人家看了官方文档,于是乎我也去看了一波,哎,英语真的重要啊!
不过呢,我这里的问题不是这个,通过文档查看(当然是翻译后),这里有一段说明:
Arrays are 'deep' compared/hashCoded, which means that arrays that contain themselves will result in StackOverflowErrors. However, this behaviour is no different from e.g. ArrayList.
数组是“深度”比较/哈希编码的,这意味着包含自身的数组将导致stackoverflowerror。然而,这种行为与例如ArrayList没有什么不同。
大概意思知道了,就是方法调用陷入无限循环之中呗,然后看一下有关hashCode()的ArrayList源码:
//这个是在父类AbstractList
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
好了,意思就是说,lombok生成的hashCode()就是通过这个形式实现的。
至于为什么造成了stackoverflowerror
,相信大概已经明了了,我的实体类代码(大概内容,注解什么的就不解释了,下面才是重点):
/**
* 用户表
*/
@Entity
@Table(name = "tab_user")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long userId;
@Column(name = "user_name")
private String userName;
@ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
@JoinTable(
name = "user_role",
joinColumns = {@JoinColumn(name = "user_role_userId",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "user_role_roleId",referencedColumnName = "role_id")}
)
private Set<Role> roleSet = new HashSet<Role>();
}
/**
* 角色表
*/
@Entity
@Table(name = "tab_role")
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;
@ManyToMany(mappedBy = "roleSet")
private Set<User> userSet = new HashSet<User>();
}
解决之前就是这样写的,MD,就是它private Set<Role> roleSet = new HashSet<Role>();
(HashSet通过HashMap实现,而HashMap的hashCode也是深度哈希的),级联操作,我的另一个实体类中也有一段代码private Set<User> userSet = new HashSet<User>();
,看到我的集合中的参数了吗?互相依赖,跟线程死锁一样,当执行hashCode()时,相互调用,无限循环开始了,最终导致stackoverflowerror
。
这里我们就直接只使用getter、setter方法就好了,即不用@Data
注解,使用@Setter
和@Getter
注解。(没办法,谁让lombok让代码更简洁,就用它)
4.总结
知其然,需知其所以然。(会用就行的那些大佬不要鄙视我)