Я твердо убежден, что проблема заключается в картировании ваших сущностей.
При неправильной модели вам будет сложно создать правильный запрос.
Давайте посмотрим, что генерируется ddl из исходного кода:
org.hibernate.DuplicateMappingException: Table [bar] contains physical column name [foo_id] referred to by multiple physical column names: [foo_id], [fooId]
Давайте попробуем исправить это:
@Column(name = "foo_id")
private Long fooId;
Теперь генерируется следующий ddl:
create table foo (foo_id bigint(20) generated by default as identity,
_deleted_ smallint,
name varchar(255),
primary key (foo_id))
create table bar (bar_id bigint(20) generated by default as identity,
_deleted_ smallint,
foo_id bigint,
key varchar(255),
value varchar(255),
bars_bar_id bigint(20) not null,
primary key (bar_id))
Проблема
bars_bar_id
является результатом вашей @JoinTable и будет проблематичным.
Запрос, предложенный в другом ответе, с использованием
Join<Foo, Bar> foobars = (Join<Foo, Bar>) fromFoo.fetch("fooId");
не удается с hibernate.jpa.criteria.BasicPathUsageException: Cannot join to attribute of basic type
См. подсказку, что вам нужно правильно сопоставить ассоциацию для объединения
Обратите внимание, что меняется только:
@Column(name = "foo_id")
private Long fooId;
до
@ManyToOne
@JoinColumn(name = "foo_id")
Foo foo;
недостаточно: любое a от foo до bar вызовет 2 соединения в SQL (как упоминалось ранее, FK для неожиданного поля bars_bar_id
):
final CriteriaBuilder builder = em.getCriteriaBuilder();
final CriteriaQuery<Foo> criteria = builder.createQuery(Foo.class);
Root<Foo> fromFoo = criteria.from(Foo.class);
Join<Foo, Bar> foobars = (Join) fromFoo.fetch("bars");
select
foo0_.foo_id as foo_id1_2_0_,
bar2_.bar_id as bar_id1_1_1_,
foo0_._deleted_ as _deleted2_2_0_,
foo0_.name as name3_2_0_,
bar2_._deleted_ as _deleted2_1_1_,
bar2_.foo_id as foo_id3_1_1_,
bar2_.key as key4_1_1_,
bar2_.value as value5_1_1_,
bars1_.foo_id as foo_id3_1_0__,
bars1_.bars_bar_id as bars_bar6_1_0__
from
foo foo0_
inner join
bar bars1_
on foo0_.foo_id=bars1_.foo_id
inner join
bar bar2_
on bars1_.bars_bar_id=bar2_.bar_id
Правильное отображение
Лучший способ отобразить отношения @OneToMany с JPA и Hibernate
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Entity(name = "foo")
public class Foo {
@Id
@Column(name = "foo_id", nullable = false, unique = true, columnDefinition = "bigint(20)")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long fooId;
private String name;
@Column(name = "_deleted_")
private Short deleted;
@OneToMany(mappedBy = "foo")
private List<Bar> bars;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Entity(name = "bar")
public class Bar {
@Id
@Column(name = "bar_id", nullable = false, unique = true, columnDefinition = "bigint(20)")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long barId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "foo_id")
Foo foo;
private String key;
private String value;
@Column(name = "_deleted_")
private Short deleted;
}
критерий запроса
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> criteria = builder.createQuery(Foo.class);
Root<Foo> fromFoo = criteria.from(Foo.class);
Join<Foo, Bar> foobars = (Join) fromFoo.fetch("bars");
List<Predicate> conditions = new ArrayList<>();
conditions.add(builder.equal(fromFoo.get("deleted"), 0));
conditions.add(builder.equal(foobars.get("deleted"), 0));
TypedQuery<Foo> typedQuery = entityManager.createQuery(
criteria.select(fromFoo)
.where(conditions.toArray(new Predicate[]{})));
сгенерированный SQL
select
foo0_.foo_id as foo_id1_2_0_,
bars1_.bar_id as bar_id1_1_1_,
foo0_._deleted_ as _deleted2_2_0_,
foo0_.name as name3_2_0_,
bars1_._deleted_ as _deleted2_1_1_,
bars1_.foo_id as foo_id5_1_1_,
bars1_.key as key3_1_1_,
bars1_.value as value4_1_1_,
bars1_.foo_id as foo_id5_1_0__,
bars1_.bar_id as bar_id1_1_0__
from
foo foo0_
inner join
bar bars1_
on foo0_.foo_id=bars1_.foo_id
where
foo0_._deleted_=0
and bars1_._deleted_=0