I have 2 entities that relate to one another. These 2 entities should map to each other in a Many-To-Many relationship, however, I need to also have a timestamp of their respective relationship (when it happened), so I am trying to map them using an intermediary table.
Initially, the relationship was One-To-Many, but I realized that I actually need a Many-To-Many as the business logic requires this. The structure is still the same, as in there is a Parent-Child relationship, but this time, a child should have multiple parents as well.
My BaseEntity is an abstract class that contains the fields present in all the other entities:
@Data
@MappedSuperclass
public abstract class BaseEntity {
@Id
@Min(100)
@Max(Integer.MAX_VALUE)
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
@CreationTimestamp
@Column(name = "Created_At", updatable = false)
protected ZonedDateTime createdDate;
@UpdateTimestamp
@Column(name = "Updated_At")
protected ZonedDateTime updatedDate;
@NotNull
@Column(name = "Is_Active")
protected Boolean active = true;
}
Then I have my 2 entities that should relate in a Many-To-Many style. This is my first entity and should be the parent:
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "User")
@EqualsAndHashCode(callSuper = true)
@TypeDefs( {
@TypeDef(name = "json", typeClass = JsonStringType.class),
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class UserEntity extends BaseEntity {
@NotBlank
@Column(name = "User_Name", columnDefinition = "varchar(255) default 'N/A'")
private String userName;
@Nullable
@JoinColumn(name = "User_Id")
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<UserRole> roleList = new ArrayList<>();
}
My second entity is considered the child entity:
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "Role")
@Where(clause = "is_active = true")
@EqualsAndHashCode(callSuper = true)
public class RoleEntity extends BaseEntity {
@NotBlank
@Column(name = "Name")
private String name;
@JsonIgnore
@JoinColumn(name = "Role_Id")
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<UserRole> userList = new ArrayList<>();
}
I also have my intermediary entity:
@Data
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Where(clause = "is_active = true")
@EqualsAndHashCode(callSuper = true)
@Table(name = "User_Role", uniqueConstraints= @UniqueConstraint(columnNames={"User_Id", "Role_Id"}))
public class UserRole extends BaseEntity {
// Adding @JsonIgnore here will only cause an error
@JoinColumn(name = "User_Id")
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, targetEntity = UserEntity.class)
private UserEntity user;
@JoinColumn(name = "Role_Id")
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, targetEntity = RoleEntity.class)
private RoleEntity role;
}
Problem now is that when I try to get my UserEntity, I get infinite recursion.
So far I've tried using @JsonIgnore, @JsonManagedReference, @JsonBackReference and it did not work or I simply don't know where or how to use them properly.
Recap:
- 2 entities mapped by Many-To-Many relationship;
- Many-To-Many implemented using an intermediary entity and One-To-Many + Many-To-One associations;
- Getting recursion when showing my UserEntity;
Update: I managed to get this fixed using a different approach described in my answer to this question.
I fixed this by implementing a Composite Key structure and just using the @JsonIgnore annotation:
This gets to be used in the intermediary entity, which now doesn't use my BaseEntity anymore.
Now, for my two entities, I have this definition:
UserEntity class (definition of the role):
RoleEntity class (definition of the user)
This seems to be working and no longer returns an infinite JSON recursion.