关注公众号: 锅外的大佬

每日推送国外优秀的技术翻译文章,励志帮助国内的开发者更好地成长!

JPAHibernate允许你在JPQLCriteria查询中使用DTOEntity作为映射。当我在我的在线培训或研讨会上讨论Hibernate性能时,我经常被问到,选择使用适当的映射是否是重要的? 答案是:是的!为你的用例选择正确的映射会对性能产生巨大影响。我只选择你需要的数据。很明显,选择不必要的信息不会为你带来任何性能优势。

1.DTO与Entity之间的主要区别

EntityDTO之间常被忽略的区别是——Entity被持久上下文(persistence context)所管理。当你想要更新Entity时,只需要调用setter方法设置新值。Hibernate将处理所需的SQL语句并将更改写入数据库。

天下没有免费的午餐。Hibernate必须对所有托管实体(managed entities)执行脏检查(dirty checks),以确定是否需要在数据库中保存变更。这很耗时,当你只想向客户端发送少量信息时,这完全没有必要。

你还需要记住,Hibernate和任何其他JPA实现都将所有托管实体存储在一级缓存中。这似乎是一件好事。它可以防止执行重复查询,这是Hibernate写入优化所必需的。但是,需要时间来管理一级缓存,如果查询数百或数千个实体,甚至可能发生问题。

使用Entity会产生开销,而你可以在使用DTO时避免这种开销。但这是否意味着不应该使用Entity?显然不是。

2.写操作投影

实体投影(Entity Projections)适用于所有写操作。Hibernate以及其他JPA实现管理实体的状态,并创建所需的SQL语句以在数据库中保存更改。这使得大多数创建,更新和删除操作的实现变得非常简单和有效。

EntityManager em = emf.createEntityManager();
em.getTransaction().begin(); Author a = em.find(Author.class, 1L);
a.setFirstName("Thorben"); em.getTransaction().commit();
em.close();

3.读操作投影

但是只读(read-only)操作要用不同方式处理。如果想从数据库中读取数据,那么Hibernate就不会管理状态或执行脏检查。 因此,从理论上说,对于读取数据,DTO投影是更好的选择。但真的有什么不同吗?我做了一个小的性能测试来回答这个问题。

3.1.测试设置

我使用以下领域模型进行测试。它由AuthorBook实体组成,使用多对一关联(many-to-one)。所以,每本书都是由一位作者撰写。

@Entity
public class Author {     @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;     @Version
    private int version;     private String firstName;     private String lastName;     @OneToMany(mappedBy = "author")
    private List bookList = new ArrayList();     ...
}

要确保Hibernate不获取任何额外的数据,我设置了@ManyToOneFetchTypeLAZH。你可以阅读Introduction to JPA FetchTypes获取不同FetchType及其效果的更多信息。

@Entity
public class Book {     @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;     @Version
    private int version;     private String title;     @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "fk_author")
    private Author author;     ...
}

我用10个作者创建了一个测试数据库,他们每人写了10 本书,所以数据库总共包含100 本书。在每个测试中,我将使用不同的投影来查询100 本书并测量执行查询和事务所需的时间。为了减少任何副作用的影响,我这样做1000次并测量平均时间。 OK,让我们开始吧。

3.2.查询实体

在大多数应用程序中,实体投影(Entity Projection)是最受欢迎的。有了EntityJPA可以很容易地将它们用作投影。 运行这个小测试用例并测量检索100个Book实体所需的时间。

long timeTx = 0;
long timeQuery = 0;
long iterations = 1000;
// Perform 1000 iterations
for (int i = 0; i < iterations; i++) {
    EntityManager em = emf.createEntityManager();     long startTx = System.currentTimeMillis();
    em.getTransaction().begin();     // Execute Query
    long startQuery = System.currentTimeMillis();
    List<Book> books = em.createQuery("SELECT b FROM Book b").getResultList();
    long endQuery = System.currentTimeMillis();
    timeQuery += endQuery - startQuery;     em.getTransaction().commit();
    long endTx = System.currentTimeMillis();     em.close();
    timeTx += endTx - startTx;
}
System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations);
System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations);

平均而言,执行查询、检索结果并将其映射到100个Book实体需要2ms。如果包含事务处理,则为2.89ms。对于小型且不那么新的笔记本电脑来说也不错。

Transaction: total 2890 per iteration 2.89
Query: total 2000 per iteration 2.0

3.3.默认FetchType对To-One关联的影响

当我向你展示Book实体时,我指出我将FetchType设置为LAZY以避免其他查询。默认情况下,To-one关联的FetchtTypeEAGER,它告诉Hibernate立即初始化关联。

这需要额外的查询,如果你的查询选择多个实体,则会产生巨大的性能影响。让我们更改Book实体以使用默认的FetchType并执行相同的测试。

@Entity
public class Book {     @ManyToOne
    @JoinColumn(name = "fk_author")
    private Author author;     ...
}

这个小小的变化使测试用例的执行时间增加了两倍多。现在花了7.797ms执行查询并映射结果,而不是2毫秒。每笔交易的时间上升到8.681毫秒而不是2.89毫秒。

Transaction: total 8681 per iteration 8.681
Query: total 7797 per iteration 7.797

因此,最好确保To-one关联设置FetchTypeLAZY

3.4.选择@Immutable实体

Joao Charnet在评论中告诉我要在测试中添加一个不可变的实体(Immutable Entity)。有趣的问题是:返回使用@Immutable注解的实体,查询性能会更好吗?

Hibernate不必对这些实体执行任何脏检查,因为它们是不可变的。这可能会带来更好的表现。所以,让我们试一试。

我在测试中添加了以下ImmutableBook实体。

@Entity
@Table(name = "book")
@Immutable
public class ImmutableBook {     @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;     @Version
    private int version;     private String title;     @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "fk_author")
    private Author author;     ...
}

它是Book实体的副本,带有2个附加注解。@Immutable注解告诉Hibernate,这个实体是不可变得。并且@Table(name =“book”)将实体映射到book表。因此,我们可以使用与以前相同的数据运行相同的测试。

long timeTx = 0;
long timeQuery = 0;
long iterations = 1000;
// Perform 1000 iterations
for (int i = 0; i < iterations; i++) {
    EntityManager em = emf.createEntityManager();     long startTx = System.currentTimeMillis();
    em.getTransaction().begin();     // Execute Query
    long startQuery = System.currentTimeMillis();
    List<Book> books = em.createQuery("SELECT b FROM ImmutableBook b")
            .getResultList();
    long endQuery = System.currentTimeMillis();
    timeQuery += endQuery - startQuery;     em.getTransaction().commit();
    long endTx = System.currentTimeMillis();     em.close();
    timeTx += endTx - startTx;
}
System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations);
System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations);

有趣的是,实体是否是不可变的,对查询没有任何区别。测量的事务和查询的平均执行时间几乎与先前的测试相同。

Transaction: total 2879 per iteration 2.879
Query: total 2047 per iteration 2.047

3.5.使用QueryHints.HINT_READONLY查询Entity

Andrew Bourgeois建议在测试中包含只读查询。所以,请看这里。

此测试使用我在文章开头向你展示的Book实体。但它需要测试用例进行修改。

JPAHibernate支持一组查询提示(hits),允许你提供有关查询及其执行方式的其他信息。查询提示QueryHints.HINT_READONLY告诉Hibernate以只读模式查询实体。因此,Hibernate不需要对它们执行任何脏检查,也可以应用其他优化。

你可以通过在Query接口上调用setHint方法来设置此提示。

long timeTx = 0;
long timeQuery = 0;
long iterations = 1000;
// Perform 1000 iterations
for (int i = 0; i < iterations; i++) {
    EntityManager em = emf.createEntityManager();     long startTx = System.currentTimeMillis();
    em.getTransaction().begin();     // Execute Query
    long startQuery = System.currentTimeMillis();
    Query query = em.createQuery("SELECT b FROM Book b");
    query.setHint(QueryHints.HINT_READONLY, true);
    query.getResultList();
    long endQuery = System.currentTimeMillis();
    timeQuery += endQuery - startQuery;     em.getTransaction().commit();
    long endTx = System.currentTimeMillis();     em.close();
    timeTx += endTx - startTx;
}
System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations);
System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations);

你可能希望将查询设置为只读来让性能显著的提升——Hibernate执行了更少的工作,因此应该更快。

但正如你在下面看到的,执行时间几乎与之前的测试相同。至少在此测试场景中,将QueryHints.HINT_READONLY设置为true不会提高性能。

Transaction: total 2842 per iteration 2.842
Query: total 2006 per iteration 2.006

3.6.查询DTO

加载100 本书实体大约需要2ms。让我们看看在JPQL查询中使用构造函数表达式获取相同的数据是否表现更好。

当然,你也可以在Criteria查询中使用构造函数表达式。

long timeTx = 0;
long timeQuery = 0;
long iterations = 1000;
// Perform 1000 iterations
for (int i = 0; i < iterations; i++) {
    EntityManager em = emf.createEntityManager();     long startTx = System.currentTimeMillis();
    em.getTransaction().begin();     // Execute the query
    long startQuery = System.currentTimeMillis();
    List<BookValue> books = em.createQuery("SELECT new org.thoughts.on.java.model.BookValue(b.id, b.title) FROM Book b").getResultList();
    long endQuery = System.currentTimeMillis();
    timeQuery += endQuery - startQuery;     em.getTransaction().commit();
    long endTx = System.currentTimeMillis();     em.close();     timeTx += endTx - startTx;
}
System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations);
System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations);

正如所料,DTO投影比实体(Entity)投影表现更好。

Transaction: total 1678 per iteration 1.678
Query: total 1143 per iteration 1.143

平均而言,执行查询需要1.143ms,执行事务需要1.678ms。查询的性能提升43%,事务的性能提高约42%。

对于一个花费一分钟实现的小改动而言,这已经很不错了。

在大多数项目中,DTO投影的性能提升将更高。它允许你选择用例所需的数据,而不仅仅是实体映射的所有属性。选择较少的数据几乎总能带来更好的性能。

4.摘要

为你的用例选择正确的投影比你想象的更容易也更重要。

如果要实现写入操作,则应使用实体(Entity)作为投影。Hibernate将管理其状态,你只需在业务逻辑中更新其属性。然后Hibernate会处理剩下的事情。

你已经看到了我的小型性能测试的结果。我的笔记本电脑可能不是运行这些测试的最佳环境,它肯定比生产环境慢。但是性能的提升是如此之大,很明显你应该使用哪种投影。

使用DTO投影的查询比选择实体的查询快约40%。因此,最好花费额外的精力为你的只读操作创建DTO并将其用作投影。

此外,还应确保对所有关联使用FetchType.LAZY。正如在测试中看到的那样,即使是一个热切获取to-one的关联操作,也可能会将查询的执行时间增加两倍。因此,最好使用FetchType.LAZY并初始化你的用例所需的关系

原文链接:thoughts-on-java.org/entities-dt…

作者: Thorben Janssen

译者:Yunooa

何时使用Entity或DTO的更多相关文章

  1. 分享公司Entity与DTO之间数据拷贝的方法

    主题 最早以前自学java web的时候,数据库查询出来一个Entity对象(CMP对象).就直接传给前台展示了.并没有用到DTO对象,开始并没有觉得有什么不好...后来发现还是需要一些DTO对象来专 ...

  2. SpringBoot -生成Entity和Dto互转的双向枚举类 -使用注解@Mapper(componentModel = "spring")

    1.导入pom文件 ,版本号自定 <!--mapStruct依赖--> <dependency> <groupId>org.mapstruct</groupI ...

  3. Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合

    在使用Entity Framework 实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好.但 ...

  4. springboot~ObjectMapper~dto到entity的自动赋值

    实体与Dto自动赋值 在开发的过程中,实体之间相互赋值是很正常的事,但是我们一般的方法都通过set和get方法来进行的,如果要赋值的字段少那还行,但是需要赋值的字段超过10个,那就是个灾难,你会看到整 ...

  5. java项目中VO和DTO以及Entity,各自是在什么情况下应用

    1.entity里的每一个字段,与数据库相对应, 2.dto里的每一个字段,是和你前台页面相对应, 3.VO,这是用来转换从entity到dto,或者从dto到entity的中间的东西.   举个例子 ...

  6. java项目中VO和DTO以及Entity,各自是在什么情况下应用的

    j2ee中,经常提到几种对象(object),理解他们的含义有助于我们更好的理解面向对象的设计思维.     POJO(plain old java object):普通的java对象,有别于特殊的j ...

  7. java架构之项目结构(entity / DTO / VO)

    定义类的讲究 关系示例 定义类的讲究 ejb Enterprise JavaBean(EJB),企业javaBean.是java的核心代码,分别是会话Bean(Session Bean).实体Bean ...

  8. java项目中VO、DTO以及Entity,各自是在什么情况下应用的

    按照标准来说: entity里的每一个字段,与数据库相对应 vo里的每一个字段,是和你前台页面相对应 dto,这是用来转换从entity到dto,或者从dto到entity的中间的东西 举个例子: h ...

  9. ABP源码分析十六:DTO的设计

    IDTO:空接口,用于标注Dto对象. ComboboxItemDto:用于combobox/list中Item的DTO NameValueDto<T>/NameValueDto:用于na ...

随机推荐

  1. 二进制<2>

    位运算简介及实用技巧(二):进阶篇(1) =====   真正强的东西来了!   ===== 二进制中的1有奇数个还是偶数个    我们可以用下面的代码来计算一个32位整数的二进制中1的个数的奇偶性, ...

  2. CSS3的writing-mode属性

    writing-mode这个CSS属性以前是IE的独有属性,IE5.5浏览器就已经支持了.在很长一段时间里,FireFox, Chrome这些现代浏览器都不支持writing-mode,各大现代浏览器 ...

  3. BZOJ3993 [SDOI2015]星际战争 【二分 + 网络流】

    题目 3333年,在银河系的某星球上,X军团和Y军团正在激烈地作战.在战斗的某一阶段,Y军团一共派遣了N个巨型机器人进攻X军团的阵地,其中第i个巨型机器人的装甲值为Ai.当一个巨型机器人的装甲值减少到 ...

  4. (转)解决fasterxml中string字符串转对象json格式错误问题(无引号 单引号问题)

    原文地址:解决fasterxml中string字符串转对象json格式错误问题 com.fasterxml.jackson.databind.ObjectMapper mapper = new com ...

  5. 通过rabbitmqadmin管理rabbitmq

    # 安装rabbitmq $ sudo apt install rabbitmq-server # 下载rabbitmqadmin管理工具 sudo rabbitmq-plugins enable r ...

  6. 学习javascript设计模式之发布-订阅(观察者)模式

    1.发布-订阅模式又叫观察者模式,它定义对象之间一种一对多的依赖关系. 2.如何实现发布-订阅模式 2-1.首先指定好发布者 2-2.给发布者添加一个缓冲列表,用户存放回调函数以便通知订阅者 2-3. ...

  7. sync fsync fdatasync

    传统的UNIX实现在内核中设有缓冲区高速缓存或页面高速 缓存,大多数磁盘I/O都通过缓冲进行.当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队 ...

  8. LeetCode OJ--Unique Paths *

    https://oj.leetcode.com/problems/unique-paths/ 首先,转换成一个排列组合问题,计算组合数C(m+n-2) (m-1),请自动想象成上下标. class S ...

  9. React项目的打包与部署到腾讯云

    腾讯云送了30天的免费试用,于是有了把react项目部署到上面的想法.项目是默认生成的,只是一个页面,但是这个过程中也遇到了不少麻烦与问题.下面来具体梳理下: create-react-app 来自F ...

  10. ABP开发框架前后端开发系列---(2)框架的初步介绍

    在前面随笔<ABP开发框架前后端开发系列---(1)框架的总体介绍>大概介绍了这个ABP框架的主要特点,以及介绍了我对这框架的Web API应用优先的一些看法,本篇继续探讨ABP框架的初步 ...