QueryDSL与Spring Boot Data JPA的交集

huangapple go评论75阅读模式
英文:

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(&quot;test&quot;).and(
        qTestEntity.value.eq(&quot;hello&quot;)
       .or(qTestEntity.value.eq(&quot;world&quot;)));

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&lt;Document&gt; documents = new BlazeJPAQuery&lt;Document&gt;(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 = &quot;value&quot;, nullable = false)
    private String value;

    @Column(name = &quot;key&quot;, nullable = false)
    private String key;
}

@Entity
class User {
  @Id 
  private int id;
  @ElementCollection
  @CollectionTable(
     name=&quot;test&quot;,
     joinColumns=@JoinColumn(name=&quot;id&quot;)
  )
  private Set&lt;TestEmbeddable&gt; keyValues;
}

In this case, the QueryDSL becomes something like this (don't know the exact api):

user.keyValues.any().in(new TestEmbeddable(&quot;test&quot;, &quot;hello&quot;))
  .and(user.keyValues.keyValues.any().in(new TestEmbeddable(&quot;test&quot;, &quot;world&quot;))

huangapple
  • 本文由 发表于 2020年5月4日 16:23:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/61587941.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定