前言
先用我不是药神电影海报镇楼,这个电影真心不错,推荐大家。
准备
讲解Hibernate之前,首先创建两个实体类,一个是Student类,一个School类。School和Student的关系是一对多的关系
@Entity
@Table(name = "tbl_school")
@Data
public class School {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
@Column(name = "id")
private String id;
@Column(name = "school_name")
private String schoolName;
@OneToMany(mappedBy = "school", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Student> studentList = new HashSet<>();
@Column(name = "created_dt")
private Date createdDt;
@Column(name = "updated_dt")
private Date updatedDt;
@Column(name = "is_del")
private String isDel;
}
@Entity
@Table(name = "tbl_student")
@Data
public class Student {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
@Column(name = "id")
private String id;
@Column(name = "student_name")
private String studentName;
@Column(name = "school_id", insertable = false, updatable = false)
private String schoolId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "school_id")
private School school;
@Column(name = "created_dt")
private Date createdDt;
@Column(name = "updated_dt")
private Date updatedDt;
@Column(name = "is_del")
private String isDel;
}
基础概念
主键采用UUID策略
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
@Column(name = "id")
Fetch用于关联关系,作用域为读取操作
@OneToMany默认的是FetchType.LAZY(懒加载)
@ManyToOne默认的是FetchType.EAGER(急加载)
由于一个School有多个Student,我们可以用@OneToMany去维护这种关系。类似的还有@OneToOne、@ManyToOne,@ManyToMany这些注解。值得注意的话,mappedBy只能适用于@OneToOne,@OneToMany,@ManyToMany这些注解。mappedBy用于主表的一方。对于我们来说School就是主表,Student就是从表。一对多的关系由从表去负责维护。
@OneToMany(mappedBy = "school", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Student> studentList = new HashSet<>();
再说说与mappedBy互斥的@JoinColumn注解,@JoinColumn用于拥有主表外键的一方,也就是从表。
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "school_id")
private School school;
mappedBy属性应该指向从表中维护与主表关系的字段。对于School类来说,mappedBy就应该指向Student类中的school属性。
为了让主表知道从表中的那些字段关联自己,在主表一方可以用mappedBy指向从表中的一个关联到自己的对象。在从表一方可以用@JoinColumn注解以外键字段的形式关联到主表。
Cascade用于级联,作用域为增删改操作。CascadeType.ALL包含所有级联策略。(后面会具体演示不同级联策略的效果,加深理解)
public enum CascadeType {
/** Cascade all operations */
ALL,
/** Cascade persist operation */
PERSIST,
/** Cascade merge operation */
MERGE,
/** Cascade remove operation */
REMOVE,
/** Cascade refresh operation */
REFRESH,
/**
* Cascade detach operation
*
* @since Java Persistence 2.0
*
*/
DETACH
}
toString()方法造成的死循环
我们去查询一个学生,看其否则用了懒加载策略
@Test
public void query() {
Student student = studentDao.findOne("1");
System.out.println("student=" + student);
}
结果抛出了这样的异常...
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:148)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:266)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)
at cmazxiaoma.model.School_$$_jvstaa_0.toString(School_$$_jvstaa_0.java)
Hibernate跟Spring整合了,Hibernate的Session就交付给Spring去管理。每次数据库操作后,会关闭Session,当我们想要用懒加载方式去获得数据的时候,原来的Session已经关闭,不能获取数据,所以会抛出这样的异常。
我们可以通过Spring提供的OpenSessionInViewFilter去解决这种问题,将Hibernate的Session绑定到整个线程的Servlet过滤器去处理请求,而它必须依赖于Servlet容器,不适用于我们的单元测试。
@Configuration
public class FilterConfig {
/**
* 解决hibernate懒加载出现的no session问题
* @return
*/
// @Bean
// public FilterRegistrationBean filterRegistrationBean() {
// FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
// filterRegistrationBean.setFilter(new OpenSessionInViewFilter());
// filterRegistrationBean.addInitParameter("urlPatterns", "/*");
// return filterRegistrationBean;
// }
/**
* 解决jpa 懒加载出现的no session问题
* @return
*/
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new OpenEntityManagerInViewFilter());
filterRegistrationBean.addInitParameter("urlPatterns", "/*");
return filterRegistrationBean;
}
}
我们可以在application-dev.properties配置如下代码,就可以在Servlet容器和单元测试中使用懒加载策略了。
#将jpa的session绑定到整个线程的Servlet过滤器,处理请求
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
注意哟,Hibernate依赖SessionFactory去创建Session实例,而JPA依赖于EntityManagerFactory去创建EntityManager实例。
解决了Could not initialize proxy - no session
的异常,我们再去跑一下单元测试,出现了更大的错误"StackOverflowError"
java.lang.StackOverflowError
at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:131)
at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:108)
at org.apache.tomcat.jdbc.pool.interceptor.AbstractCreateStatementInterceptor.invoke(AbstractCreateStatementInterceptor.java:75)
at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:108)
我们可以通过日志看到sql的输出,发现了sql重复执行了好多次。以下我截取了前10条sql记录。
Hibernate: select student0_.id as id1_1_0_, student0_.created_dt as created_2_1_0_, student0_.is_del as is_del3_1_0_, student0_.school_id as school_i4_1_0_, student0_.student_name as student_5_1_0_, student0_.updated_dt as updated_6_1_0_ from tbl_student student0_ where student0_.id=?
Hibernate: select school0_.id as id1_0_0_, school0_.created_dt as created_2_0_0_, school0_.is_del as is_del3_0_0_, school0_.school_name as school_n4_0_0_, school0_.updated_dt as updated_5_0_0_ from tbl_school school0_ where school0_.id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select school0_.id as id1_0_0_, school0_.created_dt as created_2_0_0_, school0_.is_del as is_del3_0_0_, school0_.school_name as school_n4_0_0_, school0_.updated_dt as updated_5_0_0_ from tbl_school school0_ where school0_.id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
通过观察发现,第一条sql是执行查询Student的sql,第二条sql是执行查询School的sql,第三条sql是执行School里面所有学生的sql,第四条sql是执行查询School的sql,后面所有的sql都是执行查询School里面所有学生的sql。
很明显发生了循环依赖的情况。Lombok的@Data相当于@Getter、@Setter、@ToString、@EqualsAndHashCode、@RequiredArgsConstructor注解。
如果我们去掉System.out.println("student=" + student);
这行代码,再去跑单元测试,会发现没有报错。
@Test
public void query() {
Student student = studentDao.findOne("1");
System.out.println("student=" + student);
}
我们可以将循环引用的问题定位到Student和School类的toString()方法。Lombok的@Data注解为我们生成的toString()覆盖了整个类的属性。
// School类
@Override
public String toString() {
return "School{" +
"id='" + id + '\'' +
", schoolName='" + schoolName + '\'' +
", studentList=" + studentList +
", createdDt=" + createdDt +
", updatedDt=" + updatedDt +
", isDel='" + isDel + '\'' +
'}';
}
// Student类
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", studentName='" + studentName + '\'' +
", schoolId='" + schoolId + '\'' +
", school=" + school +
", createdDt=" + createdDt +
", updatedDt=" + updatedDt +
", isDel='" + isDel + '\'' +
'}';
}
我们可以确认System.out.println("student=" + student);
会调用Student类中toString()方法,toString()方法会触发school属性的懒加载,便会去调用School类的toString()方法,School()类中的toString()方法,会触发studentList属性的懒加载,接着会调用Student类中的toString()方法。以上就是循环引用的过程。
我们将@Data注解去掉,换成@Setter、@Getter、@EqualsAndHashCode注解。我们自己重写Student类和School类的toString()方法。
// School类
@Override
public String toString() {
return "School{" +
"id='" + id + '\'' +
", schoolName='" + schoolName + '\'' +
'}';
}
// Student类
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", studentName='" + studentName + '\'' +
'}';
}
再去跑查询Student的测试用例。
@Test
public void query() {
Student student = studentDao.findOne("1");
System.out.println("student=" + student);
}
我们发现输出Student的信息,并没有去查询School的信息。证明懒加载策略起了作用。
Hibernate: select student0_.id as id1_1_0_, student0_.created_dt as created_2_1_0_, student0_.is_del as is_del3_1_0_, student0_.school_id as school_i4_1_0_, student0_.student_name as student_5_1_0_, student0_.updated_dt as updated_6_1_0_ from tbl_student student0_ where student0_.id=?
student=Student{id='1', studentName='卷毛'}
当我们去访问Student的School详情信息时,才会去查询School信息。
@Test
public void query() {
Student student = studentDao.findOne("1");
System.out.println("student=" + student);
School school = student.getSchool();
System.out.println("school=" + school);
}
Hibernate: select student0_.id as id1_1_0_, student0_.created_dt as created_2_1_0_, student0_.is_del as is_del3_1_0_, student0_.school_id as school_i4_1_0_, student0_.student_name as student_5_1_0_, student0_.updated_dt as updated_6_1_0_ from tbl_student student0_ where student0_.id=?
student=Student{id='1', studentName='卷毛'}
Hibernate: select school0_.id as id1_0_0_, school0_.created_dt as created_2_0_0_, school0_.is_del as is_del3_0_0_, school0_.school_name as school_n4_0_0_, school0_.updated_dt as updated_5_0_0_ from tbl_school school0_ where school0_.id=?
school=School{id='1', schoolName='WE学校'}
hashCode()方法造成的死循环
我们去查询School的信息
@Test
public void query() throws Exception {
School school = schoolDao.findOne("1");
System.out.println(school);
Set<Student> studentList = school.getStudentList();
System.out.println("studentList=" + studentList);
}
特么,又发现了死循环。我们可以发现执行了查询学校信息的sql,成功输出了学习信息后,才发生死循环。
Hibernate: select school0_.id as id1_0_0_, school0_.created_dt as created_2_0_0_, school0_.is_del as is_del3_0_0_, school0_.school_name as school_n4_0_0_, school0_.updated_dt as updated_5_0_0_ from tbl_school school0_ where school0_.id=?
School{id='1', schoolName='WE学校'}
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select school0_.id as id1_0_0_, school0_.created_dt as created_2_0_0_, school0_.is_del as is_del3_0_0_, school0_.school_name as school_n4_0_0_, school0_.updated_dt as updated_5_0_0_ from tbl_school school0_ where school0_.id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
Hibernate: select studentlis0_.school_id as school_i4_1_0_, studentlis0_.id as id1_1_0_, studentlis0_.id as id1_1_1_, studentlis0_.created_dt as created_2_1_1_, studentlis0_.is_del as is_del3_1_1_, studentlis0_.school_id as school_i4_1_1_, studentlis0_.student_name as student_5_1_1_, studentlis0_.updated_dt as updated_6_1_1_ from tbl_student studentlis0_ where studentlis0_.school_id=?
通过进一步,看到栈异常的错误定位在School类和Student类中的hashCode()。
java.lang.StackOverflowError
at cmazxiaoma.model.School.hashCode(School.java:22)
at sun.reflect.GeneratedMethodAccessor38.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:84)
at cmazxiaoma.model.School_$$_jvstc33_0.hashCode(School_$$_jvstc33_0.java)
at cmazxiaoma.model.Student.hashCode(Student.java:20)
那Student和School类中的hashCode()还在什么情况下调用呢? studentList是Set集合,HashSet内部实现其实是通过HashMap,HashSet的元素其实就是内部HashMap的key,HashMap的key不能重复决定了HashSet的元素不能重复。我们往HashSet里面添加元素时,其实会调用hashCode()和equals()确定元素在HashMap存储的具体位置。
@OneToMany(mappedBy = "school", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Student> studentList = new HashSet<>();
通过反编译School类和Student类,我们发现它们的hashCode()方法存在循环引用。
看School类中的hashCode()方法,studentList是一个HashSet集合,HashSet集合的hashCode()计算方式会遍历所有元素,累加求和每个元素的hashCode值。但是studentList里面元素的类型是Student,Student类中的hashCode()又会依赖于School类的hashCode()方法,这样就形成了循环依赖。
// School类的hashCode()方法
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $schoolName = this.getSchoolName();
result = result * 59 + ($schoolName == null ? 43 : $schoolName.hashCode());
Object $studentList = this.getStudentList();
result = result * 59 + ($studentList == null ? 43 : $studentList.hashCode());
Object $createdDt = this.getCreatedDt();
result = result * 59 + ($createdDt == null ? 43 : $createdDt.hashCode());
Object $updatedDt = this.getUpdatedDt();
result = result * 59 + ($updatedDt == null ? 43 : $updatedDt.hashCode());
Object $isDel = this.getIsDel();
result = result * 59 + ($isDel == null ? 43 : $isDel.hashCode());
return result;
}
// Student类中的hashCode()方法
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $studentName = this.getStudentName();
result = result * 59 + ($studentName == null ? 43 : $studentName.hashCode());
Object $schoolId = this.getSchoolId();
result = result * 59 + ($schoolId == null ? 43 : $schoolId.hashCode());
Object $school = this.getSchool();
result = result * 59 + ($school == null ? 43 : $school.hashCode());
Object $createdDt = this.getCreatedDt();
result = result * 59 + ($createdDt == null ? 43 : $createdDt.hashCode());
Object $updatedDt = this.getUpdatedDt();
result = result * 59 + ($updatedDt == null ? 43 : $updatedDt.hashCode());
Object $isDel = this.getIsDel();
result = result * 59 + ($isDel == null ? 43 : $isDel.hashCode());
return result;
}
HashSet的hashCode()方法来自与父类AbstractSet。
public int hashCode() {
int h = 0;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
if (obj != null)
h += obj.hashCode();
}
return h;
}
既然发现了是@Data注解生成的hashCode()方法坑了我们,那我们自己重写Student和Teacher类中的hashCode()和equals()方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof School)) return false;
if (!super.equals(o)) return false;
School school = (School) o;
if (!getId().equals(school.getId())) return false;
if (!getSchoolName().equals(school.getSchoolName())) return false;
if (!getCreatedDt().equals(school.getCreatedDt())) return false;
if (!getUpdatedDt().equals(school.getUpdatedDt())) return false;
return getIsDel().equals(school.getIsDel());
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + getId().hashCode();
result = 31 * result + getSchoolName().hashCode();
result = 31 * result + getCreatedDt().hashCode();
result = 31 * result + getUpdatedDt().hashCode();
result = 31 * result + getIsDel().hashCode();
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (!getId().equals(student.getId())) return false;
if (!getStudentName().equals(student.getStudentName())) return false;
if (!getSchoolId().equals(student.getSchoolId())) return false;
if (!getCreatedDt().equals(student.getCreatedDt())) return false;
if (!getUpdatedDt().equals(student.getUpdatedDt())) return false;
return getIsDel().equals(student.getIsDel());
}
@Override
public int hashCode() {
int result = getId().hashCode();
result = 31 * result + getStudentName().hashCode();
result = 31 * result + getSchoolId().hashCode();
result = 31 * result + getCreatedDt().hashCode();
result = 31 * result + getUpdatedDt().hashCode();
result = 31 * result + getIsDel().hashCode();
return result;
}
记住我们重写equals()方法,就必须要重写hashCode()方法。可以看到Student类和School类都有id、createdDt、updatedDt、isDel的属性,我们如果把这些相同属性都提到父类中,让Student类和School类继承这个父类,同时使用@EqualsAndHashCode注解为其生成equals()和hashCode()方法。那么会出现一个问题,在比较对象是否相等时会得出错误的结果。因为@EqualsAndHashCode生成的equals()和hashCode()没有使用父类的属性。接下来,我们就测试一下吧。
@EqualsAndHashCode的坑
定义一个Father类。
@Getter
@Setter
@EqualsAndHashCode
public class Son extends Father {
private String sonName;
}
定义一个Son类。
@Getter
@Setter
@EqualsAndHashCode
public class Son extends Father {
private String sonName;
}
我们运行下面的代码,比较son1和son2对象是否相等。结果返回true,很显然只比较Son对象的属性,没有比较Son的父类Father里面的属性。
public class SonTest {
@Test
public void test() {
Son son1 = new Son();
son1.setSonName("son1");
son1.setFatherName("baseFather");
Son son2 = new Son();
son2.setSonName("son1");
son2.setFatherName("baseFather2");
System.out.println(son1.equals(son2));
}
}
查看反编译后的Son类代码,恍然大悟。
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Son)) {
return false;
} else {
Son other = (Son)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$sonName = this.getSonName();
Object other$sonName = other.getSonName();
if (this$sonName == null) {
if (other$sonName != null) {
return false;
}
} else if (!this$sonName.equals(other$sonName)) {
return false;
}
return true;
}
}
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $sonName = this.getSonName();
int result = result * 59 + ($sonName == null ? 43 : $sonName.hashCode());
return result;
}
项目地址
会陆续更新使用Hibernate、Mybatis、JPA碰到的有趣问题,会打算从源码角度分析MyBatis
刚才看了评论,顺便再提一下。Lombok的@EqualsAndHashCode生成的equals()和hashCode()默认是不调用父类的实现。
设置其属性callSuper为true时,就可以了。
/**
* Call on the superclass's implementations of {@code equals} and {@code hashCode} before calculating
* for the fields in this class.
* <strong>default: false</strong>
*/
boolean callSuper() default false;
尾言
在没有真正理解框架干了什么之前,不要对框架充分信任。我们要明白Lombok框架干了什么,不然出现一堆问题就懵逼了。