提高API加载速度的4种方法,并应用于Java Spring Boot

2023-12-24 16:48:01 浏览数 (1)

  1. 分页

对于返回数组的 API 响应以及在表格、列表、选项等中使用大量数据,查询语句必须使用分页,不得获取全部数据。

Java Spring Boot

使用 JPA 和 Hibernate

代码语言:java复制
List<Post> posts = entityManager.createQuery(
    "select p "  
    "from Post p "  
    "left join fetch p.comments "  
    "order by p.createdOn", Post.class)
.setFirstResult(10)
.setMaxResults(10)
.getResultList();

然而对于 Java Spring Boot,如果你有数千条记录并且使用了 join fetch,使用上述方式会出现如下警告:

HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

特别需要注意 Hibernate N 1 问题

:( 从一开始为什么不直接说呢!直到性能问题出现才提到

为什么会有 HHH000104 警告,以及它对查询速度和响应有何影响,答案肯定是有的。

实际上 Hibernate 会将其编译成本地 SQL 查询,如下所示:

代码语言:java复制
SELECT p.id AS id1_0_0_
  
c.id AS id1_1_1_,
  p.created_on AS created_2_0_0_,
   p.title AS title3_0_0_,

   c.post_id AS post_id4_1_1_,
   c.review AS review3_1_1_,
   c.post_id AS post_id4_1_0__,
   c.id AS id1_1_0__

FROM post p

LEFT OUTER JOIN post_comment c ON p.id=c.post_id

ORDER BY p.created_on

查询中没有任何与 limit、offset 或 row_number 相关的关键字,...

Hibernate 查询会获取所有数据,然后再进行实体/模型/DTO的反序列化,导致查询数据库时间很长

有两种解决这个问题的方法:

  • 方法1:使用两个查询语句

仅选择 post.id 以获取满足条件的 id 列表。

代码语言:java复制
List<Long> postIds = entityManager.createQuery("""
    select p.id
    from Post p
    where p.title like :titlePattern
    order by p.createdOn
    """, Long.class)
.setParameter(
    "titlePattern",
    "High-Performance Java Persistence %"
)
.setMaxResults(5)
.getResultList();

获取到 post id 列表后,再查询包含在该列表中的 post。

代码语言:java复制
List<Post> posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.comments
    where p.id in (:postIds)
    order by p.createdOn
    """, Post.class)
.setParameter("postIds", postIds)
.setHint(
    QueryHints.HINT_PASS_DISTINCT_THROUGH,
    false
)
.getResultList();

这种方式需要注意一下 postIds 数组的限制,因为它是有限制的。

  • 方法2:如果使用 Oracle Database,可以使用 DENSE_RANK 我还没有应用过这种方法,所以想要应用并了解更多细节,请参考此处。

除了 N 1 和分页,对于 Java Spring Boot Hibernate JPA,还有很多与性能相关的问题,比如 spring.jpa.open-in-view、Hikari:Connection is not available、request timeout after 30000ms、EntityGraph 等。如果你有兴趣,可以评论讨论哦 :D

  1. 异步日志记录

后端记录日志以监视错误、信息、调试是理所当然的,但如果日志不是异步记录,也会影响性能,这个问题在考虑性能时经常被忽略。

对于大型系统来说,请求量很大,如果日志不是异步记录,而是花费时间或者空间来解决逻辑和返回响应,则会降低 API 的延迟。

Java Spring Boot

如果使用 logback.xml,可以进行类似如下的配置:

代码语言:xml复制
<configuration>
  <property name="LOG_PATTERN"
    value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" />

  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>
        ${LOG_PATTERN}
      </pattern>
    </encoder>
  </appender>

  <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>
      ./logs/application.log
    </file>
    <encoder>
      <pattern>
        ${LOG_PATTERN}
      </pattern>
    </encoder>

    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <fileNamePattern>
        ./logs/archive/application-%d{yyyy-MM-dd}-%i.log.zip
      </fileNamePattern>
      <maxFileSize>10MB</maxFileSize>
      <maxHistory>100</maxHistory>
    </rollingPolicy>
  </appender>

  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="ROLLING_FILE" />
  </appender>

  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="CONSOLE" />
  </appender>

  <root level="INFO">
    <appender-ref ref="ROLLING_FILE" />
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>
  1. 缓存

这项技术极大地提升了加载速度,并且有多种应用方式,比如 Redis,或者如果使用 Java Spring Boot,该框架也已经提供了

支持。

基本的缓存机制是根据键将数据存储在内存中,并且有一个过期时间。

通常第一次调用时不会很快,因为缓存还不存在,所以会直接查询数据库,之后的调用才会变快。

因此,我经常编写调度程序/定时任务/定时器,每天清晨系统将会预先获取和缓存用于大量数据查询的 API,比如列表、图表、统计等。

  1. 负载压缩

简而言之,这将在客户端的反序列化和响应时优化数据量。

一些方法包括:

  • gzip 响应
  • 对于每个 API 使用 DTO 技术而不是使用实体或模型中的完整列
  • 对字段使用简短的命名(不建议这种方式,因为返回的字段难以理解含义)

对于 Java Spring Boot,你可以在这里了解更多。

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

0 人点赞