Thymeleaf 从 Spring Data JPA 迭代 Java 8 流

Thymeleaf iterating a Java 8 Stream from Spring Data JPA

我的 Google-Fu 让我失望了,所以我问你...有没有一种方法可以用 Thymeleaf 迭代 Java 8 Stream,类似于迭代 List 的方式,同时仍然保持 Stream 的性能目的?

存储库

Stream<User> findAll()

型号

Stream<User> users = userRepository.findAll();
model.addAttribute("users", users);

查看

<div th:each="u: ${users}">
   <div th:text="${u.name}">

如果我尝试这个,我得到:

org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'name' cannot be found on object of type 'java.util.stream.ReferencePipeline$Head' - maybe not public?

如果我改用列表,它会按预期工作。

是否有正确的方法来处理我找不到的流?

据我所知并查看 Thymeleaf documentation 没有办法做你想做的事。

除了 Thymeleaf 不提供任何与流交互的方式,请考虑到 Stream 对象在您执行终端操作之前无法访问其包含的对象(例如 Collectors.toList() )

虽然 Thymeleaf 根据 documentation 不支持流,但它确实支持 Iterable,因此您可以执行以下操作:

型号:

Stream<User> users = userRepository.findAll();
model.addAttribute("users", (Iterable<User>) users::iterator);

您的视图将按照您编写的方式运行:

<div th:each="u: ${users}">
    <div th:text="${u.name}">

查看版本 3 documentation 它说它将支持任何实现 Iterator 的对象,因此也应该可以这样做:

型号:

Stream<User> users = userRepository.findAll();
model.addAttribute("users", users.iterator());

我没有使用那个版本,所以我无法让它工作。

这个post有点旧了,但是没看到更新的。可以用正确的秘方来完成。

在Java代码中,你必须做三件事:

  1. 使用@javax.transaction.Transactional注解
  2. 手动调用Thymeleaf处理模板
  3. 在模板处理中使用 try-with-resources 块以保证流关闭

在您的模板中,如果您传递 Stream 的迭代器,则无需执行任何不同的操作,因为 Thyemeleaf 已经理解迭代器。

从 Spring 数据返回流时需要 @Transactional 注释。关键是带注释的方法必须在它结束之前实际使用流 - 使用 Thyemleaf 不会发生这种情况 "normal" 方法只是 returns String 模板名称。

同时,流已关闭(使用流执行诸如将 List 转换为 Map 之类的操作时,您不必执行此操作)。通过自己控制模板生成过程,您可以确保流在您的@Transactional 方法内关闭和使用。

Java 代码如下所示(我使用的是 Spring 5 MVC):

@Controller
public class CustomerController {
    @Autowired
    SpringTemplateEngine templateEngine;

    @Autowired
    private CustomerRepository customerRepository;

    @RequestMapping("/customers")
    @Transactional
    public void customers(
        final String firstName,
        final HttpServletRequest request,
        final HttpServletResponse response
    ) throws IOException {
        final WebContext ctx = new WebContext(
            request,
            response,
            request.getServletContext(),
            request.getLocale()
        );

        try (
            final Stream<CustomerModelEntity> models = 
                (firstName == null) || firstName.isEmpty() ?
                customerRepository.findAll() :
                customerRepository.findByFirstNameIgnoringCaseOrderByLastNameAscFirstNameAsc(firstName)
        ) {
            ctx.setVariable(
                "customers",
                models.iterator()
            );

            response.setContentType("text/html");

            templateEngine.process(
                "customer-search",
                ctx,
                response.getWriter()
            );
        }
    }
}

Thymeleaf 模板如下所示(我使用的是解耦逻辑):

<?xml version="1.0"?>
<thlogic>
  <attr sel=".results" th:remove="all-but-first">
    <attr sel="/.row[0]" th:each="customer : ${customers}">
      <attr sel=".first-name" th:text="${customer.firstName}" />
      <attr sel=".middle-name" th:text="${customer.middleName}" />
      <attr sel=".last-name" th:text="${customer.lastName}" />
    </attr>
  </attr>
</thlogic>