I have three database tables that make up a many to many relationship. The tables are named Disposition, Disposition_Filter, and Dispositions_Disposition_Filter. Having mentioned that, Disposition_Disposition_Filter is the "join table".
The class DispositionFilterEntity looks like this:
@Getter
@Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "disposition_filter")
public class DispositionFilterEntity {
@GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
@GeneratedValue(generator = "uuid2")
@Id
private String id;
@Column private String name;
}
While the DispositionEntity class looks like this:
@Getter
@Setter
@ToString
@EqualsAndHashCode
@Entity(name = "dispositions")
public class DispositionEntity {
@GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
@GeneratedValue(generator = "uuid2")
@Id
private String id;
/** @see Disposition */
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String description;
@Column(nullable = false)
private String category;
@Column private boolean active;
@Column private String label;
@Column(name = "hidden_if_agent_not_assigned")
private Boolean hidden;
@Transient
public Disposition getAttributeTypeEnum() {
return Disposition.from(name);
}
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {@JoinColumn(name = "filter_id")},
inverseJoinColumns = {@JoinColumn(name = "disposition_id")})
private List<DispositionFilterEntity> filters;
}
When I run my code in the debugger and make a request to retrieve all the disposition filter objects from the database, I can see that the filters member on the disposition object is coming back empty despite the fact that data actually DOES exist in that relationship. If anyone knows why that list is coming back empty and could point me in the correct direction, I would very much appreciate it.
As you realized in your second update, your first problem have to do with the way you configured the dispositions
relationship in DispositionFilterEntity
, you interchanged the name of the columns in the joinColumns
and inverseJoinColumns
attributes. Instead of this:
@ManyToMany
@JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {@JoinColumn(name = "disposition_id")},
inverseJoinColumns = {@JoinColumn(name = "filter_id")})
private List<DispositionEntity> dispositions;
You need:
@ManyToMany
@JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {@JoinColumn(name = "filter_id")},
inverseJoinColumns = {@JoinColumn(name = "disposition_id")})
private List<DispositionEntity> dispositions;
I think the fetch type is not relevant for the problem although it is always advisable to fetch collections lazily.
Now, you are facing a stack overflow error.
The cause of this error is that you are resolving both relationships at the same time, in a circular fashion.
I mean, say for instance, that you are fetching a DispositionFilterEntity
from the database.
In your code you are resolving the relationship with dispositions
, either explicitly, by invoking getDispositions
in your code, or implicitly, by other means - we will see later that this is the case.
For every DispositionEntity
fetched, you are resolving the relationship with filters
, again, either explicitly, by invoking getFilters
in your code, or implicitly, by other means.
As indicated in the different comments, the first stack overflow you got was caused by the implementation of the toString
, and equals
and hashCode
of your entities. It is always a good practice to not include any relationship field in the implementation of these methods in an entity to avoid lazy initialization or other problems like the one that you are facing
As far as you are using Lombok, in order to prevent the error you need to annotate the dispositions
field in DispositionFilterEntity
with @ToString.Exclude
and with @EqualsAndHashCode.Exclude
.
In addition, you can also annotate in the same way the filters
field in DispositionEntity
.
Once this problem was resolved you faced a new stack overflow error. This time the error was caused by the logic you are using to convert your entities to DTOs.
You are using Mapstruct for that purpose.
First, the provided stack trace - although the source code you provided later does not include all the methods initially indicated - shows methods related with the conversion of different entity-DTOs pairs. I think it is always better to use one Mapper
for every entity-DTO pair.
Back to the stack overflow error, one option you have to avoid it is to ignore one of the fields, either dispositions
or filters
, in the corresponding mapping method by providing the corresponding @Mapping
annotation: which of them, it will depend on your actual use case.
It is important to annotate the right mapper method, in this case, the one that maps every entity to the corresponding DTO, not the contrary.
I initially advised you to take care of JSON serialization an apply @JsonIgnore or whatever you need prevent the error but probably, as you already have a DTO, it will be no longer required.
Of course, if you do not need the relationship to be bidirectional, one possible solution is to remove one side of the relation. The reason why the filters
field is not providing you any result is because you again interchanged the values of the joinColumns
and inverseJoinColumns
. Instead of this:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {@JoinColumn(name = "filter_id")},
inverseJoinColumns = {@JoinColumn(name = "disposition_id")})
private List<DispositionFilterEntity> filters;
You need:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {@JoinColumn(name = "disposition_id")},
inverseJoinColumns = {@JoinColumn(name = "filter_id")})
private List<DispositionFilterEntity> filters;
Please, always remember that the inverseJoinColumns
references the column to join with the entity applicable in the other side of the collection.