前言
先用我不是药神电影海报镇楼,这个电影真心不错,推荐大家。
image.png
准备
讲解Hibernate之前,首先创建两个实体类,一个是Student类,一个School类。School和Student的关系是一对多的关系
@Entity@Table(name = "tbl_school")@Datapublic 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")@Datapublic 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容器,不适用于我们的单元测试。
@Configurationpublic 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=truespring.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);
}image.png
我们可以将循环引用的问题定位到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()方法。以上就是循环引用的过程。
image.png
我们将@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@EqualsAndHashCodepublic class Son extends Father { private String sonName;
}定义一个Son类。
@Getter@Setter@EqualsAndHashCodepublic 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));
}
}image.png
查看反编译后的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框架干了什么,不然出现一堆问题就懵逼了。
作者:cmazxiaoma
链接:https://www.jianshu.com/p/61d4e28ee254