preface

First use I am not dying for god movie poster town building, this movie is really good, recommend everyone.

To prepare

To start with Hibernate, first create two entity classes: Student and School. The relationship between School and Student is one-to-many

@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;
}
Copy the code
@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;

}
Copy the code

Basic concept

The primary key adopts the UUID policy

    @Id
    @GenericGenerator(name = "idGenerator", strategy = "uuid")
    @GeneratedValue(generator = "idGenerator")
    @Column(name = "id")
Copy the code

Fetch is used for association, scope is read @onetomany default is fetchType.lazy @manytoone default is fetchType.eager

Since a School has multiple students, we can use @onetomany to maintain this relationship. The same goes for @onetoone, @manytoone, @manytomany. It’s worth noting that mappedBy only works with @onetoone, @onetomany, and @Manytomany. MappedBy is used on one side of the main table. For us, School is the master table and Student is the slave table. The one-to-many relationship is maintained by the slave table.

    @OneToMany(mappedBy = "school", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Set<Student> studentList = new HashSet<>();
Copy the code

Then there is the @JoinColumn annotation, which is mutually exclusive with mappedBy. @JoinColumn is used for the party that has the foreign key of the primary table, i.e. the secondary table.

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "school_id")
    private School school;
Copy the code

The mappedBy attribute should point to the fields from the table that maintain a relationship with the main table. For the School class, mappedBy should point to the School attribute in the Student class.

In order for the main table to know which fields in the table are associated with it, mappedBy can be used on the side of the main table to point to an object that is associated with it from the table. The secondary table can be associated with the main table in the form of the @JoinColumn annotation of other key fields.

Cascade Indicates the Cascade operation. The scope is add, delete, and modify operations. Cascadetype. ALL Contains ALL cascading policies. (The effects of different strategies will be demonstrated in detail later to deepen understanding.)

public enum CascadeType { /** Cascade all operations */ ALL, /** Cascade persist operation */ PERSIST, /** Cascade merge operation */ MERGE, /** Cascade remove operation */ REMOVE, /** Cascade refresh operation */ REFRESH, /** * Cascade Persistence ** @since Java Persistence 2.0 ** / detach}Copy the code

An infinite loop caused by the toString() method

Let’s query a student to see if it is otherwise using a lazy load strategy

    @Test
    public void query() {
        Student student = studentDao.findOne("1");
        System.out.println("student=" + student);
    }
Copy the code

The result is an exception like this…

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)
Copy the code

With Hibernate integrated with Spring, Hibernate sessions are delivered to Spring to manage. After each database operation, the Session is closed. When we try to retrieve data in lazy loading mode, the original Session is closed and cannot retrieve data, so we will throw this exception.

We can solve this problem with Spring’s OpenSessionInViewFilter, binding Hibernate sessions to a threaded Servlet filter to handle requests, which must depend on the Servlet container and is not suitable for our unit tests.

@configuration public class FilterConfig {/** * Solve no session problem * @return
     */
//    @Bean
//    public FilterRegistrationBean filterRegistrationBean() {
//        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
//        filterRegistrationBean.setFilter(new OpenSessionInViewFilter());
//        filterRegistrationBean.addInitParameter("urlPatterns"."/ *");
//        returnfilterRegistrationBean; //} /** * Solve the jPA lazy loading no session problem * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new OpenEntityManagerInViewFilter());
        filterRegistrationBean.addInitParameter("urlPatterns"."/ *");
        returnfilterRegistrationBean; }}Copy the code

You can configure the following code in application-dev.properties to use the lazy loading strategy in the Servlet container and unit tests.

# Bind jPA's session to the entire thread's Servlet filter to handle requests
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
Copy the code

Note that Hibernate relies on SessionFactory to create Session instances, while JPA relies on EntityManagerFactory to create EntityManager instances.


“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.jav a:75) at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:108)Copy the code

We can look at the output of the SQL from the log and see that the SQL has been repeated several times. Below I have captured the first 10 SQL records.

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=?
Copy the code

The first SQL is to query Student, the second SQL is to query School, the third SQL is to query all students in School, and the fourth SQL is to query School. All the SQL that follows is SQL that executes a query for all students in School.

It is clear that circular dependency has occurred. Lombok’s @Data is equivalent to the @Getter, @setter, @ToString, @EqualSandHashCode, and @RequiredargsConstructor annotations.

If we remove system.out.println (“student=” + student); This line of code, then run the unit test, will find no error.

    @Test
    public void query() {
        Student student = studentDao.findOne("1");
        System.out.println("student=" + student);
    }
Copy the code

We can locate the problem of circular references to the toString() methods of the Student and School classes. Lombok’s @data annotation gives us toString() that overrides the entire class attribute.

// School class @override public StringtoString() {
        return "School{" +
                "id='" + id + '\'' + ", schoolName='" + schoolName + '\'' + ", studentList=" + studentList + ", createdDt=" + createdDt + ", updatedDt=" + updatedDt + ", isDel='" + isDel + '\' ' +
                '} '; } class @override public StringtoString() {
        return "Student{" +
                "id='" + id + '\'' + ", studentName='" + studentName + '\'' + ", schoolId='" + schoolId + '\' ' +
                ", school=" + school +
                ", createdDt=" + createdDt +
                ", updatedDt=" + updatedDt +
                ", isDel='" + isDel + '\''+'}'; }Copy the code

System.out.println(“student=” + student); The toString() method in the Student class is called, and the toString() method triggers lazy loading of the school property, so the toString() method in the school class is called, the toString() method in the school class is called, This triggers a lazy load of the studentList property, which then calls the toString() method on the Student class. So that’s the circular reference.

Let’s get rid of the @data annotation and replace it with @setter, @getter, and @EqualSandHashCode annotations. We override the toString() methods of the Student and School classes ourselves.

// School class @override public StringtoString() {
        return "School{" +
                "id='" + id + '\'' + ", schoolName='" + schoolName + '\'' + '}'; } @override public String toString() {return"Student{"+"id='" + id + '\' ' +
                ", studentName='" + studentName + '\''+'}'; }Copy the code

Run the query Student’s test case.

    @Test
    public void query() {
        Student student = studentDao.findOne("1");
        System.out.println("student=" + student);
    }
Copy the code

We found that we output the information of Student, but did not query the information of School. Prove that the lazy load strategy works.

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='卷毛'}
Copy the code

When we visit the Student’s School details, we will query the School information.

    @Test
    public void query() {
        Student student = studentDao.findOne("1");
        System.out.println("student=" + student);

        School school = student.getSchool();
        System.out.println("school=" + school);
    }
Copy the code
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 school'}
Copy the code

An infinite loop caused by the hashCode() method

Let’s look up the information about 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);
    }
Copy the code

Oh, shit. I found an infinite loop again. We can find that the execution of the SQL query school information, the successful output of learning information, before the infinite loop.

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 school'}
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=?
Copy the code

Further, you can see that the stack exception error is located at hashCode() in the School and Student classes.

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)
Copy the code

Where else is hashCode() called in the Student and School classes? StudentList is a Set, and the internal implementation of a HashSet is a HashMap. The elements of a HashSet are the keys of the internal HashMap. The keys of a HashMap cannot be repeated, so the elements of a HashSet cannot be repeated. When we add an element to a HashSet, we actually call hashCode() and equals() to determine where the element is stored in the HashMap.

    @OneToMany(mappedBy = "school", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Set<Student> studentList = new HashSet<>();
Copy the code

By decompiling the School and Student classes, we found circular references to their hashCode() methods. If you look at the hashCode() method in the School class, studentList is a collection of hashsets, and hashCode() of a HashSet is computed by iterating over all the elements, summing up the hashCode value of each element. But the element in the studentList is of type Student, and the hashCode() in the Student class depends on the hashCode() method in the School class, creating a circular dependency.

/ / School classhashCode() method public inthashCode() {
        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());
        returnresult; } // in the Student classhashCode() method public inthashCode() {
        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;
    }
Copy the code

The hashCode() method of the HashSet comes from the parent class 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;
    }
Copy the code

Now that we’ve found out that the @data annotation generated hashCode() method has cheated us, let’s override the hashCode() and equals() methods in the Student and Teacher classes ourselves

    @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;
    }
Copy the code
    @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;
    }
Copy the code

Remember that if we override equals(), we must override hashCode(). You can see that the Student class and the School class both have the id, createdDt, updatedDt, isDel properties, and if we put those same properties in the parent class, and let the Student class and the School class inherit from that parent class, Generate equals() and hashCode() methods for it with the @EqualSandHashCode annotation. Then there is the problem of getting the wrong result when comparing objects for equality. Because the equals() and hashCode() generated by @EqualSandHashCode do not use the properties of the parent class. Now, let’s put it to the test.


@ EqualsAndHashCode pit

Define a Father class.

@Getter
@Setter
@EqualsAndHashCode
public class Son extends Father {

    private String sonName;

}
Copy the code

Define a Son class.

@Getter
@Setter
@EqualsAndHashCode
public class Son extends Father {

    private String sonName;

}
Copy the code

Let’s run the following code to compare whether the SON1 and SON2 objects are equal. Return true. Obviously, only properties of Son objects are compared, not properties of Son’s parent class, 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)); }}Copy the code

When you look at the decompiled Son class code, it’s an Epiphany.

    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;
    }
Copy the code

The project address

Will continue to update using Hibernate, Mybatis, JPA encountered interesting problems, will plan to analyze Mybatis from the perspective of source code


I just read the comments, and I will mention it again by the way. By default, Lombok’s @EqualSandHashCode generates equals() and hashCode() without calling the superclass’s implementation. Set the property callSuper to true.

	/**
	 * Call on the superclass's implementations of {@code equals} and {@code hashCode} before calculating * for the fields in this class. * default: false */ boolean callSuper() default false;Copy the code

Stern said

Don’t trust frameworks until you really understand what they do. We need to understand what the Lombok framework does, or we’ll be stuck with a bunch of problems.