Spring-Data-JPA注解中的setMaxResults是什么意思?

160

我正在尝试将Spring-Data-JPA集成到我的项目中。

有一件事让我困惑,就是如何通过注解实现setMaxResults(n)?

例如,我的代码:

public interface UserRepository extends CrudRepository<User , Long>
{
  @Query(value="From User u where u.otherObj = ?1 ")
  public User findByOtherObj(OtherObj otherObj);
}

我只需要从otherObj返回一个(仅一个)用户,但是我找不到一种方法来注释maxResults。谁能给我一些提示?

(MySQL报错:

com.mysql.jdbc.JDBC4PreparedStatement@5add5415: select user0_.id as id100_, user0_.created as created100_ from User user0_ where user0_.id=2 limit ** NOT SPECIFIED **
WARN  util.JDBCExceptionReporter - SQL Error: 0, SQLState: 07001
ERROR util.JDBCExceptionReporter - No value specified for parameter 2

)

我发现了一个链接:https://jira.springsource.org/browse/DATAJPA-147,但我尝试了却失败了。现在好像不可能实现吗?为什么这样一个重要的功能没有内置到Spring-Data中呢?如果我手动实现这个功能:

public class UserRepositoryImpl implements UserRepository

我必须在CrudRepository中实现大量预定义的方法,这会很糟糕。

环境: spring-3.1, spring-data-jpa-1.0.3.RELEASE.jar, spring-data-commons-core-1.1.0.RELEASE.jar

10个回答

211

从Spring Data JPA 1.7.0版本(Evans release train)开始。

你可以使用新引入的TopFirst关键字来定义查询方法,如下所示:

findTop10ByLastnameOrderByFirstnameAsc(String lastname);

Spring Data会自动将结果限制为您定义的数量(如果未指定,则默认为1)。请注意,在这里,结果的排序变得相关(可以通过OrderBy子句(如示例中所示)或将Sort参数传递给方法来实现)。有关详细信息,请参阅涵盖Spring Data Evans版本中新功能文档中的内容。

对于以前的版本

为了仅检索数据片段,Spring Data使用分页抽象,在请求方面提供了Pageable接口,在结果方面提供了Page抽象。因此,您可以从以下内容开始:

public interface UserRepository extends Repository<User, Long> {

  List<User> findByUsername(String username, Pageable pageable);
}

然后像这样使用:

Pageable topTen = new PageRequest(0, 10);
List<User> result = repository.findByUsername("Matthews", topTen);
如果你需要了解结果的上下文(它实际上是哪一页?是第一页吗?总共有多少页?),请使用 Page 作为返回类型:
public interface UserRepository extends Repository<User, Long> {

  Page<User> findByUsername(String username, Pageable pageable);
}
客户端代码可以像这样执行:
Pageable topTen = new PageRequest(0, 10);
Page<User> result = repository.findByUsername("Matthews", topTen);
Assert.assertThat(result.isFirstPage(), is(true));

如果您使用Page作为返回类型,我们不会触发实际查询的计数投影,因为我们需要找出总共有多少元素来计算元数据。除此之外,请确保您实际上已经给PageRequest配备了排序信息以获得稳定的结果。否则,即使在数据没有改变的情况下,您可能会触发两次查询并获得不同的结果。


20
谢谢,但我仍然希望有一个基于注解的解决方案。因为在这种情况下,“Page / Pageable”过于臃肿。而且客户端必须创建一个Pageable/Page来检索“一个而仅有的”结果...这非常不友好。看起来你负责Spring-Data,我希望你能进一步研究这个问题:https://dev59.com/lWDVa4cB1Zd3GeqPdGl6。你能确认这是否是Spring-Data的bug吗? - smallufo
2
这个可以用,谢谢。但在新版本中,基于注释的解决方案会更好。 - azerafati
2
这里触发了一个计数查询,如果我们只想要一个有限的查询,那么这是完全不必要的。 - azerafati
1
如果您将List作为该方法的返回类型,那么这就不是问题。 - Oliver Drotbohm
6
我认为,“Top”和“First”关键字不适用于使用“@Query”注释的方法,只适用于使用基于方法名称生成查询的方法。 - Ruslan Stelmachenko
显示剩余3条评论

120

如果你正在使用Java 8和Spring Data 1.7.0,你可以使用默认方法来将@Query注释与设置最大结果结合起来:

public interface UserRepository extends PagingAndSortingRepository<User,Long> {
  @Query("from User u where ...")
  List<User> findAllUsersWhereFoo(@Param("foo") Foo foo, Pageable pageable);

  default List<User> findTop10UsersWhereFoo(Foo foo) {
    return findAllUsersWhereFoo(foo, new PageRequest(0,10));
  }

}

4
可能有用的一个单一结果Stream<User> ... {...},默认情况下使用Optional<User> ... { ...findAny() }来查找任意用户。 - xenoterracide

31

有一种方法可以通过注释提供类似于“ setMaxResults(n)”的等效功能,如下所示:

public interface ISomething extends JpaRepository<XYZ, Long>
{
    @Query("FROM XYZ a WHERE a.eventDateTime < :before ORDER BY a.eventDateTime DESC")
    List<XYZ> findXYZRecords(@Param("before") Date before, Pageable pageable);
}

如果将分页作为参数发送,则应该这样设置。例如,要获取前10条记录,您需要将可分页设置为此值:

new PageRequest(0, 10)

如果您正在使用Pagable,则返回的是Page<XYZ>而不是List<XYZ>。 - Arundev
1
@Arundev 错了 - List 完美地工作了(并且避免了一个不必要的 count 查询,正如之前的评论中已经提到的那样) - Janaka Bandara

28

使用Spring Data Evans (1.7.0版本)

Spring Data JPA的新版本与其他一些模块一起发布,称为Evans,具备使用关键字Top20First来限制查询结果的功能。

因此,您现在可以编写以下代码:

List<User> findTop20ByLastname(String lastname, Sort sort);
或者
List<User> findTop20ByLastnameOrderByIdDesc(String lastname);

或者对于单个结果

List<User> findFirstByLastnameOrderByIdDesc(String lastname);

6
按照lastname降序排列,查找第一个用户并返回其Optional包装类对象。 - krmanish007
在获取了前20个结果后,当我使用分页功能进入下一页时,如何获取接下来的20个结果? - kittu
@kittu,当然这是另一个问题,但你需要传递一个Pageable对象。请查看Spring文档。 - azerafati
为什么他们在首先指定了返回类型的情况下还要让方法返回一个该死的列表? - Vasile Surdu
最好将findFirstByLastnameOrderByIdDesc方法的返回类型写为Optional<User>,甚至只写成User - Ilya Serbis

15

对我来说最好的选择是原生查询:

@Query(value="SELECT * FROM users WHERE other_obj = ?1 LIMIT 1", nativeQuery = true)
User findByOhterObj(OtherObj otherObj);

不确定Hibernate是否支持limit:https://forum.hibernate.org/viewtopic.php?f=9&t=939314 - Witold Kaczurba
4
不要打破整个概念!最好使用EntityManager代替! - Pwnstar
@Code.IT 我传播KISS概念。 除此之外,JPA的nativeQuery注释参数不是为了忽略而添加的。 - Grigory Kislin
@GKislin,那么您需要将方法重命名为findLimitOneByOtherObj。此外,您忘记添加OrderBy,如果other_obj不唯一,则会导致错误的结果。否则,在唯一列上的where语句应该足够而无需限制一个。 - Pwnstar
LIMIT 关键字不是 Hibernate,而是 Postgres/MySQL。 - Acewin
不错,对我有用,将“?1”替换为“:param”。 - Mike Murphy

11

new PageRequest(0,10)在较新的Spring版本(我使用的是2.2.1.RELEASE)中不起作用。基本上,构造函数增加了一个额外的参数作为Sort类型。此外,构造函数是protected,因此您必须使用其子类之一或调用其of静态方法:

PageRequest.of(0, 10, Sort.sort(User.class).by(User::getFirstName).ascending()))

你也可以省略使用Sort参数,隐式地使用默认排序方式(按pk排序等):

PageRequest.of(0, 10)

你的函数声明应该像这样:

List<User> findByUsername(String username, Pageable pageable)

这个函数将会:

userRepository.findByUsername("Abbas", PageRequest.of(0,10, Sort.sort(User.class).by(User::getLastName).ascending());

6

也可以使用@QueryHints。下面的示例使用org.eclipse.persistence.config.QueryHints#JDBC_MAX_ROWS

@Query("SELECT u FROM User u WHERE .....")
@QueryHints(@QueryHint(name = JDBC_MAX_ROWS, value = "1"))
Voter findUser();

我已经测试了这段代码,但它没有工作。没有异常,但也没有正确的结果。此外,关于QueryHints的文档非常薄弱。请参见下面链接: http://docs.spring.io/spring-data/jpa/docs/1.7.1.RELEASE/reference/html/#jpa.query-hints - bogdan.rusu
据我所知,没有任何保证查询提示将被遵循。它只是向实现传递“友好建议”的一种方式。 - Priidu Neemre
我在Hibernate中尝试使用@QueryHints({@QueryHint(name = org.hibernate.annotations.FETCH_SIZE, value = "1")}),但是别名:(不起作用。 - Grigory Kislin
2
org.hibernate.annotations.FETCH_SIZE是它的获取大小,不是限制,也不是最大结果。Hiber无法。 - Zhuravskiy Vitaliy

5
如果你的类使用了@Repository并继承了JpaRepository,你可以使用下面的示例代码。
int limited = 100;
Pageable pageable = new PageRequest(0,limited);
Page<Transaction> transactionsPage = transactionRepository.findAll(specification, pageable);
return transactionsPage.getContent();

getContent 返回一个 List<Transaction>


如果我的查询有两个where条件呢,比如transactionRepository.findByFirstNameAndLastName(firstname, lastname)?这样可以吗?我遇到了这个异常org.springframework.data.mapping.PropertyReferenceException: No property create found for type Board. - Shailesh

0
我简直不敢相信,经过12年了,Sping-Data-JPA仍然没有一个实际的解决方案。 最后我不得不自己编写一个自定义的Repository实现。
public class CustomJpaRepository <Entity, Id> extends JpaRepositoryImplementation<Entity, Id> {
  Optional<Entity> findFirst(String query, Map<String, Object> parameters, String entityGraph);
}

public class CustomExtendedJpaRepository<E, I> extends SimpleJpaRepository<E, I> implements ExtendedJpaRepository<E, I>{

    private final EntityManager entityManager;
    private final Class<E> domainClass;

    public CustomExtendedJpaRepository(JpaEntityInformation<E, I> entityInformation, EntityManager em) {
        super(entityInformation, em);
        this.entityManager = em;
        this.domainClass = entityInformation.getJavaType();
    }

    public CustomExtendedJpaRepository(Class<E> domainClass, EntityManager em) {
        super(domainClass, em);
        this.entityManager = em;
        this.domainClass = domainClass;
    }
    
    @Override
    public Optional<E> findFirst(String query, Map<String, Object> parameters, String entityGraph) {
        var tq = entityManager.createQuery(query, domainClass);
        for (var p : parameters.entrySet()) {
            tq.setParameter(p.getKey(), p.getValue());
        }
        if (entityGraph != null) {
            tq.setHint(AvailableHints.HINT_SPEC_FETCH_GRAPH, entityManager.getEntityGraph(entityGraph));
        }
        tq.setMaxResults(1);
        return Optional.ofNullable(tq.getSingleResult());
    }
}


为了实现自定义实现
@EnableJpaRepositories(
        basePackages = "your.company.persistence",
        repositoryBaseClass = CustomExtendedJpaRepository.class
)

-1

使用

Pageable pageable = PageRequest.of(0,1);
Page<Transaction> transactionsPage = transactionRepository.findAll(specification, pageable);
return transactionsPage.getContent();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接