英文:
QueryDSL intersection with Spring Boot Data JPA
问题
I am using QueryDSL within a Spring Boot, Spring Data JPA project.
I have the following schema for a table called test
:
| id | key | value |
|----|------|-------|
| 1 | test | hello |
| 1 | test | world |
| 2 | test | hello |
| 2 | foo | bar |
| 3 | test | hello |
| 3 | test | world |
Now I want to write the following SQL in QueryDSL:
select id from test where key = 'test' and value = 'hello'
INTERSECT
select id from test where key = 'test' and value = 'world'
Which would give me all ids where key is 'test' and values are 'hello' and 'world'.
I did not find any way of declaring this kind of SQL in QueryDSL yet. I am able to write the two select statements but then I am stuck at combining them with an INTERSECT
.
JPAQueryFactory queryFactory = new JPAQueryFactory(em); // em is an EntityManager
QTestEntity qTestEntity = QTestEntity.testEntity;
var q1 = queryFactory.query().from(qTestEntity).select(qTestEntity.id).where(qTestEntity.key("test").and(qTestEntity.value.eq("hello")));
var q2 = queryFactory.query().from(qTestEntity).select(qTestEntity.id).where(qTestEntity.key("test").and(qTestEntity.value.eq("world")));;
In the end I want to retrieve a list of ids which match the given query. In general the amount of intersects can be something around 20 or 30, depending on the number of key/value-pairs I want to search for.
Does anyone know a way how to do something like this with QueryDSL ?
EDIT:
Assume the following schema now, with two tables: test
and 'user':
test:
| userId | key | value |
|---------|------|-------|
| 1 | test | hello |
| 1 | test | world |
| 2 | test | hello |
| 2 | foo | bar |
| 3 | test | hello |
| 3 | test | world |
user:
| id | name |
|----|----------|
| 1 | John |
| 2 | Anna |
| 3 | Felicita |
The correspond java classes look like this. TestEntity
has a composite key consisting of all of its properties.
@Entity
public class TestEntity {
@Id
@Column(name = "userId", nullable = false)
private String pubmedId;
@Id
@Column(name = "value", nullable = false)
private String value;
@Id
@Column(name = "key", nullable = false)
private String key;
}
@Entity
class User {
@Id
private int id;
private String name;
@ElementCollection
private Set<TestEntity> keyValues;
}
How can I map the test
table to the keyValues
properties within the User
class?
英文:
I am using QueryDSL within a Spring Boot, Spring Data JPA project.
I have the following schema for a table called test
:
| id | key | value |
|----|------|-------|
| 1 | test | hello |
| 1 | test | world |
| 2 | test | hello |
| 2 | foo | bar |
| 3 | test | hello |
| 3 | test | world |
Now I want to write the following SQL in QueryDSL:
select id from test where key = 'test' and value = 'hello'
INTERSECT
select id from test where key = 'test' and value = 'world'
Which would give me all ids where key is 'test' and values are 'hello' and 'world'.
I did not find any way of declaring this kind of SQL in QueryDSL yet. I am able to write the two select statements but then I am stuck at combining them with an INTERSECT
.
JPAQueryFactory queryFactory = new JPAQueryFactory(em); // em is an EntityManager
QTestEntity qTestEntity = QTestEntity.testEntity;
var q1 = queryFactory.query().from(qTestEntity).select(qTestEntity.id).where(qTestEntity.key("test").and(qTestEntity.value.eq("hello")));
var q2 = queryFactory.query().from(qTestEntity).select(qTestEntity.id).where(qTestEntity.key("test").and(qTestEntity.value.eq("world")));;
In the end I want to retrieve a list of ids which match the given query. In general the amount of intersects can be something around 20 or 30, depending on the number of key/value-pairs I want to search for.
Does anyone know a way how to do something like this with QueryDSL ?
EDIT:
Assume the following schema now, with two tables: test
and 'user':
test:
| userId | key | value |
|---------|------|-------|
| 1 | test | hello |
| 1 | test | world |
| 2 | test | hello |
| 2 | foo | bar |
| 3 | test | hello |
| 3 | test | world |
user:
| id | name |
|----|----------|
| 1 | John |
| 2 | Anna |
| 3 | Felicita |
The correspond java classes look like this. TestEntity
has a composite key consisting of all of its properties.
@Entity
public class TestEntity {
@Id
@Column(name = "userId", nullable = false)
private String pubmedId;
@Id
@Column(name = "value", nullable = false)
private String value;
@Id
@Column(name = "key", nullable = false)
private String key;
}
@Entity
class User {
@Id
private int id;
private String name;
@ElementCollection
private Set<TestEntity> keyValues;
}
How can I map the test
table to the keyValues
properties within the User
class?
答案1
得分: 1
在这种情况下,我可能会只使用一个 OR
表达式:
queryFactory
.query()
.from(qTestEntity)
.select(qTestEntity.id)
.where(qTestEntity.key("test").and(
qTestEntity.value.eq("hello")
.or(qTestEntity.value.eq("world")))
);
然而,你特别提到想要使用一个集合操作。我顺便提一下,我认为你想执行一个 UNION
操作,而不是一个 INTERSECT
操作,因为后者在给定的示例中会为空。
JPA 不支持类似 ANSI SQL 中定义的集合操作。然而,Blaze-Persistence 是一个扩展,可以与大多数 JPA 实现集成,并在 JPQL 中扩展了集合操作。我最近为 Blaze-Persistence 编写了一个 QueryDSL 扩展。使用该扩展,你可以这样做:
List<Document> documents = new BlazeJPAQuery<Document>(entityManager, cbf)
.union(
select(document).from(document).where(document.id.eq(41L)),
select(document).from(document).where(document.id.eq(42L))
)
.fetch();
有关集成及如何设置的更多信息,可以在文档中查阅:https://persistence.blazebit.com/documentation/1.5/core/manual/en_US/index.html#querydsl-integration
英文:
In this case I'd probably just use an OR
expression:
queryFactory
.query()
.from(qTestEntity) .select(qTestEntity.id)
.where(qTestEntity.key("test").and(
qTestEntity.value.eq("hello")
.or(qTestEntity.value.eq("world")));
However, you specifically mention wanting to use a set operation. I by the way think you want to perform an UNION
operation instead of an INSERSECT
operation, because the latter one would be empty with the example given.
JPA doesn't support set operations such as defined in ANSI SQL. However, Blaze-Persistence is an extension that integrates with most JPA implementations and does extend JPQL with set operations. I have recently written a QueryDSL extension for Blaze-Persistence. Using that extension, you can do:
List<Document> documents = new BlazeJPAQuery<Document>(entityManager, cbf)
.union(
select(document).from(document).where(document.id.eq(41L)),
select(document).from(document).where(document.id.eq(42L))
).fetch();
For more information about the integration and how to set it up, the documentation is available at https://persistence.blazebit.com/documentation/1.5/core/manual/en_US/index.html#querydsl-integration
答案2
得分: 0
你的TestEntity并不真正是一个实体(Entity),因为它的id不是一个主键,而是指向用户表的外键。
如果它只能通过使用所有属性来进行识别,那么它是一个@Embeddable,不应具有任何@Id属性。
你可以将一组Embeddables映射为另一个具有id作为主键的实体的@ElementCollection的一部分。在你的情况下,id列不是Embeddable的属性,它只是指向主表的外键,因此你将其映射为一个@JoinColumn:
@Embeddable
public class TestEmbeddable {
@Column(name = "value", nullable = false)
private String value;
@Column(name = "key", nullable = false)
private String key;
}
@Entity
class User {
@Id
private int id;
@ElementCollection
@CollectionTable(
name="test",
joinColumns=@JoinColumn(name="id")
)
private Set<TestEmbeddable> keyValues;
}
在这种情况下,QueryDSL的查询可能如下(不确切的API调用):
user.keyValues.any().in(new TestEmbeddable("test", "hello"))
.and(user.keyValues.keyValues.any().in(new TestEmbeddable("test", "world")))
英文:
Your TestEntity is not really an Entity, since it's id is not a primary key, it's the foreign key to the user table.
If it's only identifiable by using all its properties, it's an @Embeddable, and doesn't have any @Id properties.
You can map a collection of Embeddables as an @ElementCollection part of another entity which has the id as primary key. The id column in your case is not a property of the Embeddable, it's just the foreign key to the main table, so you map it as a @JoinColumn:
@Embeddable
public class TestEmbeddable {
@Column(name = "value", nullable = false)
private String value;
@Column(name = "key", nullable = false)
private String key;
}
@Entity
class User {
@Id
private int id;
@ElementCollection
@CollectionTable(
name="test",
joinColumns=@JoinColumn(name="id")
)
private Set<TestEmbeddable> keyValues;
}
In this case, the QueryDSL becomes something like this (don't know the exact api):
user.keyValues.any().in(new TestEmbeddable("test", "hello"))
.and(user.keyValues.keyValues.any().in(new TestEmbeddable("test", "world"))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论