I will split my problem into 3 sections:
@Entity
@Table(name = "foo")
public class Foo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, unique = true)
private Long id;
// ... other fields
@OneToMany(mappedBy = "fooer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Bar> barInfo = new ArrayList<>();
@OneToMany(mappedBy = "fooing", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Bar> baringInfo = new ArrayList<>();
}
@Entity
@Table(name = "bar")
public class Bar {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, unique = true)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fooer_id", referencedColumnName = "id", nullable = false,
foreignKey = @ForeignKey(name = "bar_fooer_id_fkey"))
private Foo fooer;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fooing_id", referencedColumnName = "id", nullable = false,
foreignKey = @ForeignKey(name = "bar_fooing_id_fkey"))
private Foo fooing;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "baz_id", referencedColumnName = "id", nullable = false,
foreignKey = @ForeignKey(name = "bar_baz_id_fkey"))
private Baz baz;
}
@Entity
@Table(name = "baz")
public class Baz {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, unique = true)
private Long id;
// ... other fields
}
CREATE TABLE some_schema.bar
(
id int8 GENERATED BY DEFAULT AS IDENTITY ( INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1 NO CYCLE) NOT NULL,
fooer_id int8 NOT NULL,
fooing_id int8 NOT NULL,
baz_id int8 NOT NULL,
CONSTRAINT bar_id_key UNIQUE (id),
CONSTRAINT bar_fooer_id_fkey FOREIGN KEY (fooer_id) REFERENCES some_schema.foo (id),
CONSTRAINT bar_fooing_id_fkey FOREIGN KEY (fooing_id) REFERENCES some_schema.foo (id),
CONSTRAINT bar_baz_id_fkey FOREIGN KEY (baz_id) REFERENCES some_schema.baz (id)
);
Let's say we have two entities of type Foo
(John[id=111] and Alice[id=222]) and one entity of type Baz
(Paperwork[id=999]). John wants to request help from Alice that relates to Paperwork. My current implementation of adding request explained above looks like this:
final var john = getById(contextId); // fooing
final var alice = getById(id); // fooer
final var paperwork = bazService.getBazById(bazId);
john.getBarInfo()
.add(Bar.builder()
.fooer(alice)
.fooing(john)
.baz(paperwork)
.build());
fooRepository.save(john); // <-- Here john have barInfo with values set above and baringInfo empty which is what we want
Data in table bar
looks like this:
id | fooer_id | fooing_id | baz_id |
---|---|---|---|
1 | 222 | 111 | 999 |
Which from the perspective of current implementation and requirement looks okay. But the problem lies in retrieving entity and entities of type Foo
.
Expected result by retrieving either by ID or whole list:
{
"John": {
"barInfo": {
"fooers": [
{
"foo_id": 222,
"baz_id": 999
}
],
"fooing": []
}
},
"Alice": {
"barInfo": {
"fooers": [],
"fooing": [
{
"foo_id": 111,
"baz_id": 999
}
]
}
}
}
Actual result:
{
"John": {
"barInfo": {
"fooers": [],
"fooing": [
{
"foo_id": 111,
"baz_id": 999
}
]
}
},
"Alice": {
"barInfo": {
"fooers": [
{
"foo_id": 222,
"baz_id": 999
}
],
"fooing": []
}
}
}
The data displayed above is pseudo-representation of entity
Foo
retrieved from Spring repository (not wrapped to other object)
The fooing
/fooers
seems to be reversed (I assume there is something wrong with my associations between entities) and I'm wondering if there is some flaw in my approach. Any suggestions will be much appreciated.
Versions used in project:
Figured it out!
All I had to do was reverse mappedBy
parameters for Foo
:
@Entity
@Table(name = "foo")
public class Foo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, unique = true)
private Long id;
// ... other fields
@OneToMany(mappedBy = "fooing", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
~~~~~~~~
private List<Bar> barInfo = new ArrayList<>();
@OneToMany(mappedBy = "fooer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
~~~~~~~
private List<Bar> baringInfo = new ArrayList<>();
}
And in service related to adding request instead of adding to list of barInfo
or baringInfo
, we should have just saved new Bar
and it will be associated with respective entities:
final var john = getById(contextId); // fooing
final var alice = getById(id); // fooer
final var paperwork = bazService.getBazById(bazId);
final var bar = Bar.builder()
.fooer(alice)
.fooing(john)
.baz(paperwork)
.build();
barRepository.save(bar);
Retrieving information about Foo
gives expected result mentioned in my question.