Problem Statement:
When fetching a Marklar
from the database, it is returned with duplicated Foo
s. Specifically, there are as many Foo
s as there are elements in the bars
HashMap. For example, if I am saving a Marklar
with one Foo
in its FooCollection
, and if bars
is:
{0, "data1", 1, "data2", 2, "data3"}
I will get three Foo
s in the FooContainer
when I read it back. What is going on?
Definitions:
I have the following class structure:
Foo<T>
^
|
| 1:M
FooCollection
^
|
| 1:1
<<Marklar>>
A Marklar
has a FooContainer
, which has several Foo
s. A Foo
is a generic class defined as:
@Getter
@Setter
@ToString
@Entity
public class Foo<T> {
@Id @GeneratedValue private Long id;
@ElementCollection(fetch = FetchType.EAGER)
private final Map<Integer, String> bars = new HashMap<>();
@Type(type = "java.lang.Class")
private final Class<T> clazz;
@Column(columnDefinition = "LONGTEXT")
@Convert(converter = PatternConverter.class)
private Pattern pattern;
public void addBar(Integer key, String bar) {
bars.put(key, bar);
}
public Foo(Class<T> clazz) {
this.clazz = clazz;
}
public Foo() {
this.clazz = null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Foo)) return false;
Foo<?> foo = (Foo<?>) o;
return Objects.equals(getId(), foo.getId())
&& Objects.equals(getBars(), foo.getBars())
&& Objects.equals(getClazz(), foo.getClazz())
&& Objects.equals(getPattern(), foo.getPattern());
}
@Override
public int hashCode() {
return Objects.hash(getId(), getBars(), getClazz(), getPattern());
}
}
FooContainer
:
@Getter
@Setter
@ToString
@Entity
public class FooContainer {
@Id @GeneratedValue private Long id;
@OneToMany(targetEntity = Foo.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private final List<Foo> foos = new ArrayList<>();
public void addFoo(Foo foo) {
foos.add(foo);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FooContainer)) return false;
FooContainer that = (FooContainer) o;
return Objects.equals(getId(), that.getId()) && Objects.equals(getFoos(), that.getFoos());
}
@Override
public int hashCode() {
return Objects.hash(getId(), getFoos());
}
}
Marklars
are fetched from the MarklarRepository
:
@Repository
public interface MarklarRepository extends JpaRepository<Marklar<?>, UUID> {}
A Marklar
is an abstract class:
@Getter
@Setter
@Entity
@ToString
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Marklar<T> {
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
private UUID id;
@Convert(converter = UriConverter.class)
private final URI baseUri;
@Type(type = "java.lang.Class")
private final Class<T> clazz;
private boolean enabled = true;
public Marklar() {
this.baseUri = null;
this.clazz = null;
}
public Marklar(@NotNull URI baseUri, @NotNull Class<T> clazz) {
this.baseUri = baseUri;
this.clazz = clazz;
}
}
Implemented as a DefaultMarklar
:
@Getter
@Setter
@ToString(callSuper = true)
@Entity
@RequiredArgsConstructor
public final class DefaultMarklar<T> extends Marklar<T> {
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private final FooContainer fooContainer;
public DefaultMarklar() {
super();
this.fooContainer = null;
}
public DefaultMarklar(
@NotNull URI baseUri, @NotNull Class<T> clazz, @NotNull FooContainer fooContainer) {
super(baseUri, clazz);
this.fooContainer = fooContainer;
}
}