I have the following Parameter entity which has a self-association, I m trying to make a unit test to check my implementation, but it feels that I m doing some errors in my mapping and I can't detect where I m being mistaken.
My entity is as below
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 "]";
}
}
My repository is like below
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);
}
My unit test is as below
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);
}
}
I m facing two exceptions: the first is the famous persist detached entity the other is query specified join fetching, but the owner of the fetched association was not present in the select list
Fullstacktrace
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
CodePudding user response:
I did fix the issue but I m still facing some interrogations as below :
The Fix
@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);
I believe that maybe selecting the entity element of param.consequences will help me avoid to fetch this collection, however what about every ExpertSystem entity element, this is using a lasy loading mechanism, shouldn't I be able to fetch join on it from the collection. Also shouldn't the entity graph help me avoid this issue by auto fetching collection or child elements !!!
You can reproduce this experiment based on this link