如何使用 Spring Boot 带来一个大实体并将其转换为 dto

How can I bring a large entity and convert the same to dto using Spring Boot

我正在尝试创建一个基于数据库的数据库 API 我有大量数据,但每次我尝试使用可分页 属性 来获取数据时,我都会收到此错误:

{
    "timestamp": "2022-01-12T01:34:01.851+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "trace": "org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: br.com.leomanzini.space.flight.news.model.Article.launches, could not initialize proxy - no Session\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:612)\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:591)\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)\r\n\tat org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:387)\r\n\tat java.base/java.lang.Iterable.forEach(Iterable.java:74)\r\n\tat br.com.leomanzini.space.flight.news.dto.ArticlesDTO.<init>(ArticlesDTO.java:65)\r\n\tat br.com.leomanzini.space.flight.news.service.ArticleService.lambda$findAll[=11=](ArticleService.java:30)\r\n\tat java.base/java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:195)\r\n\tat java.base/java.util.ArrayList$Itr.forEachRemaining(ArrayList.java:1032)\r\n\tat java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)\r\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)\r\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)\r\n\tat java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)\r\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)\r\n\tat java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)\r\n\tat org.springframework.data.domain.Chunk.getConvertedContent(Chunk.java:173)\r\n\tat org.springframework.data.domain.PageImpl.map(PageImpl.java:106)\r\n\tat br.com.leomanzini.space.flight.news.service.ArticleService.findAll(ArticleService.java:30)\r\n\tat br.com.leomanzini.space.flight.news.controller.SpaceFlightsApiController.findAll(SpaceFlightsApiController.java:28)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:655)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:834)\r\n",
    "message": "failed to lazily initialize a collection of role: br.com.leomanzini.space.flight.news.model.Article.launches, could not initialize proxy - no Session",
    "path": "/articles"
}

在数据库中我有 11795 篇文章以及它们各自与其他表的关系,有没有办法优化这个负载或者让它起作用?

文章实体:

import lombok.*;

import javax.persistence.*;
import java.util.List;

@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "article")
public class Article {

    @Id
    @EqualsAndHashCode.Include
    private Long id;

    @Column(nullable = false)
    private Boolean featured;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String url;

    @Column(name = "image_url", nullable = false)
    private String imageUrl;

    @Column(name = "news_site", nullable = false)
    private String newsSite;

    @Column(nullable = false)
    private String summary;

    @Column(name = "published_at", nullable = false)
    private String publishedAt;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Launches> launches;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Events> events;

    @Column(name = "inserted_by_human")
    private Boolean insertedByHuman = false;
}

事件和发布实体:

import lombok.*;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Events {

    @Id
    @EqualsAndHashCode.Include
    private Long id;

    private String provider;
}

import lombok.*;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Launches {

    @Id
    @EqualsAndHashCode.Include
    private String id;

    private String provider;
}

存储库只是与 JpaRepository 和文章作为实体的接口。

服务class:

import br.com.leomanzini.space.flight.news.dto.ArticlesDTO;
import br.com.leomanzini.space.flight.news.model.Article;
import br.com.leomanzini.space.flight.news.repository.ArticleRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
@AllArgsConstructor
public class ArticleService {

    @Autowired
    private final ArticleRepository articleRepository;

    public Page<ArticlesDTO> findAll(Pageable pageable) {
        Page<Article> articleList = articleRepository.findAll(pageable);
        return articleList.map(article -> new ArticlesDTO(article));
    }
}

如果我只是将我的实体用作 return,这些方法可以工作,但是当我将其转换为 dto 时,应用程序会中断。 dto class 如下:

import br.com.leomanzini.space.flight.news.model.Article;
import lombok.*;

import javax.management.ConstructorParameters;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ArticlesDTO implements Serializable {

    @NotEmpty
    @EqualsAndHashCode.Include
    private Long id;

    @NotEmpty
    private String title;

    @NotEmpty
    private String url;

    @NotEmpty
    private String imageUrl;

    @NotEmpty
    private String newsSite;

    @NotEmpty
    private String summary;

    @NotEmpty
    private String publishedAt;

    @NotEmpty
    private String updatedAt;

    @NotEmpty
    private Boolean featured;

    @Valid
    private List<LaunchesDTO> launches;

    @Valid
    private List<EventsDTO> events;

    public ArticlesDTO (Article article) {
        id = article.getId();
        title = article.getTitle();
        url = article.getUrl();
        imageUrl = article.getImageUrl();
        newsSite = article.getNewsSite();
        summary = article.getSummary();
        publishedAt = article.getPublishedAt();
        updatedAt = article.getPublishedAt();
        featured = article.getFeatured();
        launches = new ArrayList<>();
        article.getLaunches().forEach(launch -> {
            LaunchesDTO launchesDTO = new LaunchesDTO(launch.getId(), launch.getProvider());
            launches.add(launchesDTO);
        });
        events = new ArrayList<>();
        article.getEvents().forEach(event -> {
            EventsDTO eventsDTO = new EventsDTO(event.getId(), event.getProvider());
            events.add(eventsDTO);
        });
    }
}

有什么方法可以修复错误并使其正常工作?可以加速数据库加载还是其他?

建议的答案是在服务class中启用了@Transactional(readOnly = true)属性,可以很好地解决错误

failed to lazyly initialize a collection of role: br.com . leomanzini.space.flight.news.model.Article.launches, could not initialize proxy.

服务中的方法 class 如下所示:

import br.com.leomanzini.space.flight.news.dto.ArticlesDTO;
import br.com.leomanzini.space.flight.news.model.Article;
import br.com.leomanzini.space.flight.news.repository.ArticleRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@AllArgsConstructor
public class ArticleService {

    @Autowired
    private final ArticleRepository articleRepository;

    @Transactional(readOnly = true)
    public Page<ArticlesDTO> findAll(Pageable pageable) {
        Page<Article> articleList = articleRepository.findAll(pageable);
        return articleList.map(article -> new ArticlesDTO(article));
    }
}

申请工作正常!

使用FetchType.LAZY,用于检索这些字段的查询将仅在第一次访问时执行。为了做到这一点,Hibernate 为我们的实体 类 创建并配置了代理 类 (这就是为什么我们也不应该声明我们的实体 类 为 final 如果我们想使用延迟加载功能)。

当 Hibernate 初始化代理时,它需要 Session 才能完成任务。这就是为什么当您不向服务方法添加 @Transactional 注释时,它会抛出一个 LazyInitializationException...no Session.

除了上面的解决方案,如果不想添加@Transactional注解,可以将hibernate.enable_lazy_load_no_trans属性设置为true

<property
name="hibernate.enable_lazy_load_no_trans"
value="true"/>

但是这个解决方案被认为是一种反模式。每次您尝试延迟加载时,它都会创建一个新的 Session。您可以在这里阅读更多内容。
The hibernate.enable_lazy_load_no_trans Anti-Pattern