Интересная проблема с Hibernate, JPA, Lombok

задняя часть Hibernate SQL Project Lombok

предисловие

Первое использование Я не бог наркотиков, постер фильма, здание города, этот фильм действительно хорош, я рекомендую его всем.

image.png

Подготовить

Прежде чем объяснять Hibernate, сначала создайте два класса сущностей, один — класс Student, а другой — класс School. Отношения между школой и учеником являются отношениями «один ко многим».

@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")

Выборка используется для ассоциаций, а область действия — операции чтения. @OneToMany по умолчанию имеет значение FetchType.LAZY (ленивая загрузка) @ManyToOne по умолчанию — FetchType.EAGER (аварийная загрузка)

Поскольку в школе есть несколько учеников, мы можем использовать @OneToMany для поддержания этой связи. Аналогичные аннотации: @OneToOne, @ManyToOne и @ManyToMany. Стоит отметить, что mappedBy можно применять только к таким аннотациям, как @OneToOne, @OneToMany и @ManyToMany. mappedBy используется на одной стороне основной таблицы. Для нас школа — главный стол, а ученик — подчиненный. Отношение «один ко многим» поддерживается ведомой таблицей.

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

Давайте поговорим об аннотации @JoinColumn, которая является взаимоисключающей с mappedBy. @JoinColumn используется для стороны, которая владеет внешним ключом первичной таблицы, то есть вторичной таблицы.

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

Атрибут mappedBy должен указывать на поле подчиненной таблицы, которое поддерживает связь с главной таблицей. Для класса School mappedBy должен указывать на свойство school в классе Student.

Чтобы главная таблица знала, какие поля в подчиненной таблице связаны с ней самой, на стороне главной таблицы вы можете использовать mappedBy, чтобы указать на объект в подчиненной таблице, который связан с ней самой. На стороне подчиненной таблицы вы можете использовать аннотацию @JoinColumn для связи с главной таблицей в форме поля внешнего ключа.

Каскад для каскадирования, добавления и удаления к объему операции. 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 доставляется в Spring для управления. После каждой операции с базой данных сессия будет закрыта.Когда мы хотим использовать ленивую загрузку для получения данных, исходная сессия была закрыта и данные не могут быть получены, поэтому будет выброшено такое исключение.

Мы можем решить эту проблему с помощью OpenSessionInViewFilter, предоставленного Spring, который связывает сеанс Hibernate с фильтром сервлета всего потока для обработки запросов, которые должны зависеть от контейнера сервлета и не подходят для нашего модульного тестирования.

@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 для использования стратегии ленивой загрузки в контейнере сервлетов и модульных тестах.

#将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 — это sql, который выполняет запрос «Студент», второй sql — это sql, который выполняет запрос «школа», третий sql — это sql, который выполняет всех учащихся в школе, а четвертый sql — это sql. SQL-запрос, который выполняет запрос школы Все следующие SQL-запросы — это SQL-запросы, которые выполняют запрос для всех учащихся в школе.

Очевидно, что циркулярная зависимость происходит. Ломбок @data эквивалентен @egetter, @Stetter, @tostring, @equalsandhashcode, @requiredargsconstructor antanations.

если мы удалимSystem.out.println("student=" + student);Эта строка кода, а затем запуск модульного теста, не найдет ошибок.

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

image.png

Мы можем локализовать проблему циклической ссылки в методе toString() классов Student и School. Аннотация 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);Будет вызван метод toString() в классе Student. Метод toString() вызовет ленивую загрузку свойства школы и вызовет метод toString() класса School. Метод toString() в классе School( ) активирует свойство studentList, а ленивая загрузка вызовет метод toString() в классе Student. Вышеупомянутый процесс циклической ссылки.

image.png

Мы будем @data комментарий удалены, заменили @ setter, @ getter, @ @ comalandhashcode комментарий. Мы владеем переписыванием ToString Student Class и School Class ().

   // School类
    @Override
    public String toString() {
        return "School{" +
                "id='" + id + '\'' +
                ", schoolName='" + schoolName + '\'' +
                '}';
    }

    // Student类
    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                ", studentName='" + studentName + '\'' +
                '}';
    }

Студенческие тестовые примеры для повторного запуска запроса.

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

Мы обнаружили, что вывод информации о студенте не запрашивал информацию о школе. Докажите, что стратегия ленивой загрузки работает.

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

Информация о школе запрашивается только тогда, когда мы посещаем сведения о школе учащегося.

    @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()

Давайте запрос школьной информации

    @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=?

Сдвиньте дальше, мы видим, что ошибка исключения стека находится в 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)

При каких обстоятельствах вызывается функция hashCode() в классах Student и School? StudentList представляет собой коллекцию Set. Внутренняя реализация HashSet на самом деле осуществляется через HashMap. Элементы HashSet на самом деле являются ключами внутренней HashMap. Ключ HashMap нельзя повторять для определения HashSet Элементы не могут повторяться. Когда мы добавляем элементы в HashSet, мы фактически вызываем hashCode() и equals(), чтобы определить конкретное расположение элементов, хранящихся в HashMap.

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

Декомпилировав классы School и Student, мы обнаружили циклические ссылки в их методе hashCode(). Глядя на метод hashCode() в классе School, studentList представляет собой коллекцию HashSet.Метод вычисления hashCode() коллекции HashSet будет проходить по всем элементам, накапливать и суммировать значение hashCode каждого элемента. Однако тип элемента в studentList — Student, а hashCode() в классе Student будет зависеть от метода hashCode() в классе School, таким образом образуя циклическую зависимость.

    // 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;
    }

Метод hashCode() класса HashSet происходит от родительского класса 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;
    }

Теперь, когда мы обнаружили, что метод hashCode(), сгенерированный аннотацией @Data, вызывает у нас проблемы, давайте перепишем сами методы hashCode() и equals() в классах Student и Teacher.

    @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(). Тогда будет проблема, вы получите неправильные результаты при сравнении объектов на равенство. Поскольку equals() и hashCode(), сгенерированные @EqualsAndHashCode, не используют свойства родительского класса. Далее, давайте протестируем его.


Яма @EqualsAndHashCode

Определите класс отца.

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

    private String sonName;

}

Сын определяет класс.

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

    private String sonName;

}

Мы запускаем приведенный ниже код, чтобы сравнить объекты son1 и son2 на равенство. Результат возвращает true.Очевидно, что сравниваются только свойства объекта Son, а свойства родительского класса Son не сравниваются.

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 с точки зрения исходного кода.


Просто прочитайте комментарии, чтобы еще раз упомянуть об этом. По умолчанию equals() и hashCode(), сгенерированные @EqualsAndHashCode Lombok, не вызывают реализацию родительского класса. Если для его свойства 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;

equals.png

hashcode.png

Последние слова

Не доверяйте фреймворку, не понимая, что он делает. Мы должны понимать, что делает фреймворк Lombok, иначе будет куча проблем и мы запутаемся.