Сопоставление самосвязанной двунаправленной сущности

У меня есть следующий объект Parameter, который имеет самоассоциацию, я пытаюсь выполнить модульный тест, чтобы проверить свою реализацию, но мне кажется, что я делаю некоторые ошибки в своем отображении, и я не могу определить, где я ошибаюсь.

Моя сущность, как показано ниже

import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import com.tuto.common.enums.ParameterCategory;
import com.tuto.common.enums.ParameterType;
import com.tuto.common.enums.converter.ParameterTypePersistenceConverter;

/**
 * This class represents the PARAMETERS SQL table as a java entity.
 *
*/

@Entity
@Table(name = "PARAMETERS")
public class Parameter implements Serializable {

    /**
     * serialVersionUID.
     */
    private static final long serialVersionUID = -732987999122243011L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_PARAMETER_ID")
    @SequenceGenerator(name = "SQ_PARAMETER_ID", sequenceName = "SQ_PARAMETER_ID", allocationSize = 1)
    @Column(name = "PARAMETER_ID", unique = true)
    private long id;
    
    @Column(name = "PARAMETER_NAME")
    private String name;
    
    @Column(name = "PARAMETER_LABEL")
    private String label;
    
    @Column(name = "PARAMETER_COMMENT")
    private String comment;

    @JoinColumn(name = "ES_ID")
    @OneToOne(fetch = FetchType.LAZY)
    private ExpertSystem expertSystem;

    @Column(name = "PARAMETER_CATEGORY")
    @Enumerated(EnumType.ORDINAL)
    private ParameterCategory category;
    
    @Column(name = "PARAMETER_TYPE")
    @Convert(converter = ParameterTypePersistenceConverter.class)
    private ParameterType type;


    @Column(name = "PARAMETER_CREATION_DATE",columnDefinition = "TIMESTAMP")
    private OffsetDateTime creationDate;

    @JoinColumn(name = "PARAMETER_CREATION_USER",referencedColumnName = "USER_FIRSTNAME")
    @OneToOne(fetch = FetchType.LAZY)
    private User creationUser;

    @Column(name = "PARAMETER_UPDATE_DATE",columnDefinition = "TIMESTAMP")
    private OffsetDateTime updateDate;

    @JoinColumn(name = "PARAMETER_UPDATE_USER",referencedColumnName = "USER_FIRSTNAME")
    @OneToOne(fetch = FetchType.LAZY)
    private User updateUser;

    @Column(name = "MULTIVALUE_SIZE")
    private int multivalueSize;
 
    @OneToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "dependences",
               joinColumns = {@JoinColumn(name  = "PARAMETER_ID")},
               inverseJoinColumns = {@JoinColumn(name  = "DEPENDANCE_ID")})
    private List<Parameter> dependences = new ArrayList<>();
    
    @OneToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "dependences",
               joinColumns = {@JoinColumn(name  = "DEPENDANCE_ID")},
               inverseJoinColumns = {@JoinColumn(name  = "PARAMETER_ID")})
    private List<Parameter> consequences = new ArrayList<>();
    
    
    public Parameter() {}

    public Parameter(long id, String name, String label, String comment, ExpertSystem expertSystem,
            ParameterCategory category, ParameterType parameterType, OffsetDateTime creationDate, User creationUser,
            OffsetDateTime updateDate, User updateUser, int multivalueSize,List<Parameter> dependences) {
        super();
        this.id = id;
        this.name = name;
        this.label = label;
        this.comment = comment;
        this.expertSystem = expertSystem;
        this.category = category;
        this.type = parameterType;
        this.creationDate = creationDate;
        this.creationUser = creationUser;
        this.updateDate = updateDate;
        this.updateUser = updateUser;
        this.multivalueSize = multivalueSize;
        this.dependences = dependences;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public ExpertSystem getExpertSystem() {
        return expertSystem;
    }

    public void setExpertSystem(ExpertSystem expertSystem) {
        this.expertSystem = expertSystem;
    }

    public ParameterCategory getCategory() {
        return category;
    }

    public void setCategory(ParameterCategory category) {
        this.category = category;
    }

    public ParameterType getType() {
        return type;
    }

    public void setType(ParameterType parameterType) {
        this.type = parameterType;
    }

    public OffsetDateTime getCreationDate() {
        return creationDate;
    }
    
    public List<Parameter> getDependences() {
        return dependences;
    }

    public List<Parameter> getConsequences() {
        return consequences;
    }
    
    public void setCreationDate(OffsetDateTime creationDate) {
        this.creationDate = creationDate;
    }

    public User getCreationUser() {
        return creationUser;
    }

    public void setCreationUser(User creationUser) {
        this.creationUser = creationUser;
    }

    public OffsetDateTime getUpdateDate() {
        return updateDate;
    }

    public void setUpdateDate(OffsetDateTime updateDate) {
        this.updateDate = updateDate;
    }

    public User getUpdateUser() {
        return updateUser;
    }

    public void setUpdateUser(User updateUser) {
        this.updateUser = updateUser;
    }

    public int getMultivalueSize() {
        return multivalueSize;
    }

    public void setMultivalueSize(int multivalueSize) {
        this.multivalueSize = multivalueSize;
    }
    
    public void setDependences(List<Parameter> dependences) {
        this.dependences = dependences;
    }

    public void setConsequences(List<Parameter> consequences) {
        this.consequences = consequences;
    }
    
    public void addDependence(Parameter dependence) {
        if (dependence != null ) {
             removeConsequence(dependence);
             this.dependences.add(dependence);
             dependence.getConsequences().add(this);
        }
    }

    public void removeDependence(Parameter dependence) {
        if (dependence != null && this.dependences.contains(dependence) ) {
            final  int indexOfConsequence = this.dependences.indexOf(dependence);
            Parameter parameter = this.dependences.get(indexOfConsequence) ;
            parameter.getDependences().remove(this);   
         }
    }
    
    public void addConsequence(Parameter consequence) {
        if (consequence != null ) {
             removeConsequence(consequence);
             this.consequences.add(consequence);
             consequence.getDependences().add(this);
        }
    }

    public void removeConsequence(Parameter consequence) {
        if (consequence != null && this.consequences.contains(consequence) ) {
            final  int indexOfConsequence = this.consequences.indexOf(consequence);
            Parameter parameter = this.consequences.get(indexOfConsequence) ;
            parameter.getDependences().remove(this);   
         }
    }
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (id ^ (id >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Parameter other = (Parameter) obj;
        if (id != other.id)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Parameter [id = " + id + ", name = " + name + ", label = " + label + ", comment = " + comment
                + ", expertSystem = " + expertSystem + ", category = " + category + ", type = " + type
                + ", creationDate = " + creationDate + ", creationUser = " + creationUser + ", updateDate = " + updateDate
                + ", updateUser = " + updateUser + ", multivalueSize = " + multivalueSize +  ", dependences = " + dependences + "]";
    }
}

Мой репозиторий, как показано ниже


import java.util.List;

import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.tuto.entity.Parameter;


/**
 * This interface defines the contract of the possible operations to manage the parameter entity in the database.
 *
 */
@Repository
public interface IParameterRepository extends JpaRepository<Parameter, Long> {

    /**
     * This method fetches the list of all available parameters. It could return an empty list.
     *
     * @return List<Parameter> The list of all Parameters found
     */
    @Transactional
    @Modifying(clearAutomatically = false)
    @Query(value = "SELECT param FROM Parameter param")
    List<Parameter> getAllParameters();

    /**
     * This method fetches the dependencies list (as Parameter list) for the given Param id.
     * It could return an empty list.
     *
     * @param idParam The id of Parameter.
     * @return List<Parameter> The dependencies list (as Parameter list) found for the given Param id.
     */
    @Transactional
    @Modifying(clearAutomatically = false)
    @EntityGraph(attributePaths = {"dependences","expertSystem"})
    @Query(value = "SELECT param.dependences FROM Parameter param join fetch param.dependences where param.id = :#{#idParam}  ", nativeQuery = false)
    List<Parameter> getDependancesByParamId(final long idParam);

    /**
     * This method fetches the consequences list (as Parameter list) for the given Param id.
     * It could return an empty list.
     *
     * @param idParam The id of Parameter.
     * @return List<Parameter> The dependencies list (as Parameter list) found for the given Param id.
     */
    @Transactional
    @Modifying(clearAutomatically = false)
    @EntityGraph(attributePaths = {"consequences","expertSystem"})
    @Query(value = "SELECT param.consequences FROM Parameter param join fetch param.consequences where param.id = :#{#idParam}  ", nativeQuery = false)
    List<Parameter> getConsequencesByParamId(final long idParam);

}

Мой модульный тест, как показано ниже


import static org.junit.Assert.assertEquals;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;

import com.tuto.common.enums.ParameterCategory;
import com.tuto.common.enums.ParameterType;
import com.tuto.entity.Parameter;
import com.tuto.core.profile.PfSpringProfiles;
import com.tuto.test.Tags;

@DataJpaTest(showSql = true)
@Tag(Tags.COMPONENT)
@ActiveProfiles(PfSpringProfiles.TEST)
public class ParameterRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private IParameterRepository parameterRepository;
    
    @BeforeEach
    public void intTests() {
        
        final Parameter parameterInput = new Parameter();
        parameterInput.setCategory(ParameterCategory.INPUT);
        parameterInput.setType(ParameterType.DECIMAL);
        parameterInput.setId(1L);
        
        final Parameter parameterIntermediate = new Parameter();
        parameterIntermediate.setCategory(ParameterCategory.INTERMEDIATE);
        parameterIntermediate.setType(ParameterType.DECIMAL);
        parameterIntermediate.setId(2L);
        
        final Parameter parameterOutput = new Parameter();
        parameterOutput.setCategory(ParameterCategory.OUTPUT);
        parameterOutput.setType(ParameterType.DECIMAL);
        parameterOutput.setId(3L);

        entityManager.merge(parameterInput);
        entityManager.merge(parameterIntermediate);
        entityManager.merge(parameterOutput);

//        parameterInput.getConsequences().add(parameterIntermediate);
        parameterInput.addConsequence(parameterIntermediate);
        parameterIntermediate.addConsequence(parameterOutput); 
        
//        parameterIntermediate.getConsequences().add(parameterOutput);

//        parameterOutput.getDependences().add(parameterIntermediate);
//        parameterIntermediate.getDependences().add(parameterInput);
        
        entityManager.merge(parameterOutput);
        entityManager.merge(parameterIntermediate);
        entityManager.merge(parameterInput);
    }
    
    @Test
    public void checkConsequencesAndDependencesAreNotEmpty() {
        List<Parameter> parameter = parameterRepository.getDependancesByParamId(2L);
        assertEquals(parameter.size(),1);
        assertEquals(parameter.get(0).getId(),1L);
    }
}

Я сталкиваюсь с двумя исключениями: первое — знаменитый сохранять отсоединенную сущность, второе — запрос указал выборку соединения, но владелец выбранной ассоциации не присутствовал в списке выбора.

Полная трассировка

Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=com.tuto.entity.Parameter.consequences,tableName=parameters,tableAlias=parameter2_,origin=parameters parameter0_,columns = {parameter0_.parameter_id,className=com.tuto.entity.Parameter}}] [SELECT param.consequences FROM com.tuto.entity.Parameter param join fetch param.consequences where param.id = :__$synthetic$__1  ]
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:757)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:114)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:362)
    at com.sun.proxy.$Proxy140.createQuery(Unknown Source)
    at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:90)
    ... 88 common frames omitted
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=com.tuto.entity.Parameter.consequences,tableName=parameters,tableAlias=parameter2_,origin=parameters parameter0_,columns = {parameter0_.parameter_id,className=com.tuto.entity.Parameter}}] [SELECT param.consequences FROM com.tuto.entity.Parameter param join fetch param.consequences where param.id = :__$synthetic$__1  ]
    at org.hibernate.QueryException.generateQueryException(QueryException.java:120)
    at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:220)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144)
    at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113)
    at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73)
    at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162)
    at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:636)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:748)
    ... 96 common frames omitted
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=com.tuto.entity.Parameter.consequences,tableName=parameters,tableAlias=parameter2_,origin=parameters parameter0_,columns = {parameter0_.parameter_id,className=com.tuto.entity.Parameter}}]
    at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:215)
    at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:1028)
    at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:796)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:694)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:330)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:278)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:276)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192)
    ... 102 common frames omitted

Ассаляму алейкум, можно адрес вашего репозитория? Я хотел бы попробовать это в моем местном.

Syed Mainul Hasan 11.05.2022 11:43

ва алейком салам, к сожалению, я не могу поделиться репозиторием, так как он размещен на локальном сервере, отделенном от внешнего мира

Mohammed Housseyn Taleb 11.05.2022 13:47

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

Syed Mainul Hasan 11.05.2022 14:05
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
46
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я исправил проблему, но я все еще сталкиваюсь с некоторыми вопросами, как показано ниже:

Исправление

 @Transactional
    @Modifying(clearAutomatically = false)
//    @EntityGraph(attributePaths = {"dependences","expertSystem"})
    @Query(value = "SELECT param.dependences FROM Parameter param  where param.id = :#{#idParam}  ", nativeQuery = false)
    List<Parameter> getDependancesByParamId(final long idParam);

    /**
     * This method fetches the consequences list (as Parameter list) for the given Param id.
     * It could return an empty list.
     *
     * @param idParam The id of Parameter.
     * @return List<Parameter> The dependances list (as Parameter list) found for the given Param id .
     */
    @Transactional
    @Modifying(clearAutomatically = false)
//    @EntityGraph(attributePaths = {"consequences","expertSystem"})
    @Query(value = "SELECT param.consequences FROM Parameter param where param.id = :#{#idParam}  ", nativeQuery = false)
    List<Parameter> getConsequencesByParamId(final long idParam);

Я считаю, что, возможно, выбор элемента сущности param.consequences поможет мне избежать извлечения этой коллекции, однако что касается каждого элемента сущности ExpertSystem, здесь используется механизм ленивой загрузки, и я не смогу получить соединение с ним из коллекции. Кроме того, граф сущностей не должен помогать мне избежать этой проблемы путем автоматического извлечения коллекции или дочерних элементов !!!

Вы можете воспроизвести этот эксперимент на основе этого связь.

Если я правильно понял, то вы хотите понять, почему у вас не работает аннотация @EntityGraph. Насколько мне известно, аннотация @EntityGraph загружает узлы родительского объекта. Он не используется для загрузки родственного узла. Поэтому, если вы запрашиваете объект, у которого есть несколько элементов коллекции, таких как @OneToMany, @ManyToMany или @ElementCollection, вы можете с готовностью получить их все с аннотацией @EntityGraph. В вашем случае, чтобы получить expertSystem и consequences, ваш метод должен возвращать Parameter в качестве родительского объекта вместо List<Parameter>.

Syed Mainul Hasan 11.05.2022 20:39

Обратите внимание, что в таком случае, если вы получите List, у вас есть хорошие шансы получить MultipleBagFetchException. Чтобы избежать этого, вы должны заменить List на Set.

Syed Mainul Hasan 11.05.2022 20:40

@SyedMainulHasan спасибо за объяснение. Для MultipleBagFetchException мой модульный тест в порядке. Я знаю об этом исключении, но буду хранить список до тех пор, пока не понадобится набор, потому что мне могут понадобиться дубликаты, у меня еще нет полного понимания природы сохраняемых данных, индексированный список также может ответить на эту проблему.

Mohammed Housseyn Taleb 12.05.2022 13:48

Другие вопросы по теме