【问题标题】:How to select distinct results in Spring Data如何在 Spring Data 中选择不同的结果
【发布时间】:2014-03-27 20:42:29
【问题描述】:

我在使用简单的 Spring Data 查询或 @Query 或 QueryDSL 在 Spring Data 中构建查询时遇到问题。

如何选择三列(Study、Country、Login)不同的行并作为查询的结果将是用户的对象类型列表?

表:

------------------------------------- | User | ------------------------------------- | Id | Study | Country | Site | Login | ------------------------------------- | 1 | S1 | US | 11 | user1 | | 2 | S1 | US | 22 | user1 | | 3 | S1 | US | 33 | user1 | | .. | .. | .. | .. | .. | -------------------------------------

我需要一个仅基于 Study 的查询,它将仅返回每个 LoginCountry 的唯一用户,而不考虑 Site 列。

方法签名如下:

List<User> findByStudyIgnoreCase(String study);

现在正在返回表用户中的所有行。因此,我在 Study 和 Country 中重复了有关用户分配的行,因为我在不需要 Site 的其他表中有 UI 演示。

所以,我需要类似的东西:

select distinct Study, Country, Login from User

但返回对象必须是用户对象,就像方法签名所说的一样(例如匹配结果的第一个)。

怎么做?

  1. 是否有可能以这种方式或类似方式?如何让它正确?

    @Query("SELECT DISTINCT s.study, s.country, s.login FROM user s where s.study = ?1 ") List<User> findByStudyIgnoreCase(String study);

  2. 是否可以使用 QueryDSL?

---- 编辑----

我尝试像 TimoWestkämper 建议的那样通过 QueryDSL 编写查询,但我遇到了问题。

    public List<User> findByStudyIgnoreCase(String study) {
        QUser $ = QUser.user;
        BooleanExpression studyExists = $.study.equalsIgnoreCase(study);

        List<Users> usersList = from($)
            .where(studyExists)
            .distinct()
            .list(Projections.bean(User.class, $.study, $.country, $.id.login));

        return usersList;
    }

调用上述查询后发生异常:

org.springframework.dao.InvalidDataAccessApiUsageException: The bean of type: com.domain.app.model.User has no property called: study; nested exception is java.lang.IllegalArgumentException: The bean of type: com.domain.app.model.User has no property called: study

为什么会这样?

---- 编辑 2 ----

我的User 班级:

@Entity
@Table(name="USER")
@Immutable
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter @EqualsAndHashCode @ToString
@FieldDefaults(level=AccessLevel.PRIVATE)
public class User {

    @EmbeddedId
    UserId id;

    @Column(name="CONTACT_UNIQUE_ID")
    String contactUniqueId;

    String country;

    @Column(name="COUNTRY_CODE")
    String countryCode;

    @Column(name="STUDY")
    String study;

    String firstname;

    String lastname;

    String email;

    String role;
}

可嵌入UserId类:

@Embeddable
@Getter @EqualsAndHashCode @ToString
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level=AccessLevel.PRIVATE)
public class UserId implements Serializable {

    private static final long serialVersionUID = 1L;

    String site;
    String login;

}

生成的 QUser 类:

@Generated("com.mysema.query.codegen.EntitySerializer")
public class QUser extends EntityPathBase<User> {

    private static final long serialVersionUID = 1646288729;

    private static final PathInits INITS = PathInits.DIRECT;

    public static final QUser user = new User("user");

    public final StringPath contactUniqueId = createString("contactUniqueId");

    public final StringPath country = createString("country");

    public final StringPath countryCode = createString("countryCode");

    public final StringPath study = createString("study");

    public final StringPath email = createString("email");

    public final StringPath firstname = createString("firstname");

    public final QUser id;

    public final StringPath lastname = createString("lastname");

    public final StringPath role = createString("role");

    public QUser(String variable) {
        this(User.class, forVariable(variable), INITS);
    }

    @SuppressWarnings("all")
    public QUser(Path<? extends User> path) {
        this((Class)path.getType(), path.getMetadata(), path.getMetadata().isRoot() ? INITS : PathInits.DEFAULT);
    }

    public QUser(PathMetadata<?> metadata) {
        this(metadata, metadata.isRoot() ? INITS : PathInits.DEFAULT);
    }

    public QUser(PathMetadata<?> metadata, PathInits inits) {
        this(User.class, metadata, inits);
    }

    public QUser(Class<? extends User> type, PathMetadata<?> metadata, PathInits inits) {
        super(type, metadata, inits);
        this.id = inits.isInitialized("id") ? new QUser(forProperty("id")) : null;
    }

}

【问题讨论】:

  • 这样可以吗?你为什么不试试呢?
  • @JBNizet 我试过了,但我得到的对象不是用户,而是只有研究、国家和登录值,所以这不是我所期望的......
  • 这是意料之中的事,因为您的查询选择了研究、国家和登录名。
  • 所以您想要用户实例,但根据不同的研究、国家和登录属性进行过滤?
  • @TimoWestkämper 是的。您在回答中的建议很好,但对我不起作用,我更新了我的回答内容

标签: java sql spring-data querydsl


【解决方案1】:

我可以回答 Querydsl 部分。它通过

工作
List<User> users = query.from(user)
  .where(user.study.eq(arg))
  .distinct()
  .list(Projections.fields(User.class, user.study, user.country, user.login));

您将获得包含填充研究、国家和登录字段的用户实例。 User 实例不是托管的 JPA 实体,而是填充的 bean。

您也可以像这样查询元组实例

List<Tuple> tuples = query.from(user)
  .where(user.study.eq(arg))
  .distinct()
  .list(user.study, user.country, user.login);

但是当您使用 Spring Data 时,您可能希望返回用户。

【讨论】:

  • 感谢您的回答,我尝试了您的第一个建议,但我收到了 org.springframework.dao.InvalidDataAccessApiUsageException: The bean of type: com.domain.app.model.User has no property called: study ;与其他 2 个属性相同。我已经生成了谓词(其他查询有效)但是为什么这里看不到属性?请在更新的答案内容中查看我的 dslquery。
  • 是的。我向您展示我如何以其他(不优雅)的方式解决我的问题:1 查询所有行,然后以其他方法缩小结果。但我相信应该可以只写一个正确的查询。可以看看吗?
  • 能否提供User的来源?
  • 是的,我已经用 User 类的来源和其他可嵌入的 UserId 更新了我的问题并生成了 QUser
  • 我相应地更新了我的答案。 Projections.bean 适用于 bean,而 Projections.fields 使用字段。
【解决方案2】:

我解决了对所有行调用通用查询的问题,然后用其他方法缩小结果。这不是我喜欢的解决方案,但我想向您展示我的需求。也许那时你会找到更好的解决方案,只需要查询。是否甚至可以将其仅包含在一个查询中?

public List<User> findDistinctUsersByStudyNumIgnoreCase(String study) {
    QUser $ = QUser.user;
    BooleanExpression studyExists = $.study.equalsIgnoreCase(study);

    List<User> usersList = from($)
        .where(study)
        .listDistinct($);

    // narrowing results (this what I do not know how to use in QueryDsl query)
    usersList = removeDuplicates(usersList); 

    return usersList;
}

/**
 * <p>Remove duplicated user assignments for presentation User table purpose only</p>
 * <p><b>NOTE:</b> Duplicated assignment is when more than one entry in USER 
 * has the same <i>study</i>, <i>country</i>, <i>login</i> and differ with <i>site</i> only. 
 * For presentation User Assignments table purpose the <i>site</i> context is not needed, so we need
 * only info about user existence in <i>study</i> and <i>country</i> context only.</p>
 * @param usersList user assignments list from USER with study, country and site context (with duplicates)
 * @return distinct user assignments list narrowed to study and country context only and without site context (without duplicates)
 */
private List<User> removeDuplicates(List<User> usersList) {
    List<User> result = new ArrayList<User>();

    MultiKeyMap studyCountryLoginMap = new MultiKeyMap();
    for (User u : usersList) {
        if (!studyCountryLoginMap.containsKey(u.getStudy(), u.getCountry(), u.getId().getLogin())) {
            studyCountryLoginMap.put(u.getStudy(), u.getCountry(), u.getId().getLogin(), u);
        }
    }

    result.addAll(studyCountryLoginMap.values());
    return result;
}

【讨论】:

    猜你喜欢
    • 2014-12-29
    • 2022-08-10
    • 2014-08-10
    • 2017-08-29
    • 2023-02-03
    • 2018-07-22
    • 2016-05-14
    • 2014-07-08
    • 2019-09-28
    相关资源
    最近更新 更多