前言
先用我不是药神电影海报镇楼,这个电影真心不错,推荐大家。
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