何时使用Entity或DTO
关注公众号: 锅外的大佬
每日推送国外优秀的技术翻译文章,励志帮助国内的开发者更好地成长!
JPA
和Hibernate
允许你在JPQL
和Criteria
查询中使用DTO
和Entity
作为映射。当我在我的在线培训或研讨会上讨论Hibernate
性能时,我经常被问到,选择使用适当的映射是否是重要的? 答案是:是的!为你的用例选择正确的映射会对性能产生巨大影响。我只选择你需要的数据。很明显,选择不必要的信息不会为你带来任何性能优势。
1.DTO与Entity之间的主要区别
Entity
和DTO
之间常被忽略的区别是——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.测试设置
我使用以下领域模型进行测试。它由Author
和Book
实体组成,使用多对一关联(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
不获取任何额外的数据,我设置了@ManyToOne
的FetchType
为LAZH
。你可以阅读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)是最受欢迎的。有了Entity
,JPA
可以很容易地将它们用作投影。 运行这个小测试用例并测量检索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
关联的FetchtType
是EAGER
,它告诉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
关联设置FetchType
为LAZY
。
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
实体。但它需要测试用例进行修改。
JPA
和Hibernate
支持一组查询提示(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
并初始化你的用例所需的关系。
作者: Thorben Janssen
译者:Yunooa
何时使用Entity或DTO的更多相关文章
- 分享公司Entity与DTO之间数据拷贝的方法
主题 最早以前自学java web的时候,数据库查询出来一个Entity对象(CMP对象).就直接传给前台展示了.并没有用到DTO对象,开始并没有觉得有什么不好...后来发现还是需要一些DTO对象来专 ...
- SpringBoot -生成Entity和Dto互转的双向枚举类 -使用注解@Mapper(componentModel = "spring")
1.导入pom文件 ,版本号自定 <!--mapStruct依赖--> <dependency> <groupId>org.mapstruct</groupI ...
- Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合
在使用Entity Framework 实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好.但 ...
- springboot~ObjectMapper~dto到entity的自动赋值
实体与Dto自动赋值 在开发的过程中,实体之间相互赋值是很正常的事,但是我们一般的方法都通过set和get方法来进行的,如果要赋值的字段少那还行,但是需要赋值的字段超过10个,那就是个灾难,你会看到整 ...
- java项目中VO和DTO以及Entity,各自是在什么情况下应用
1.entity里的每一个字段,与数据库相对应, 2.dto里的每一个字段,是和你前台页面相对应, 3.VO,这是用来转换从entity到dto,或者从dto到entity的中间的东西. 举个例子 ...
- java项目中VO和DTO以及Entity,各自是在什么情况下应用的
j2ee中,经常提到几种对象(object),理解他们的含义有助于我们更好的理解面向对象的设计思维. POJO(plain old java object):普通的java对象,有别于特殊的j ...
- java架构之项目结构(entity / DTO / VO)
定义类的讲究 关系示例 定义类的讲究 ejb Enterprise JavaBean(EJB),企业javaBean.是java的核心代码,分别是会话Bean(Session Bean).实体Bean ...
- java项目中VO、DTO以及Entity,各自是在什么情况下应用的
按照标准来说: entity里的每一个字段,与数据库相对应 vo里的每一个字段,是和你前台页面相对应 dto,这是用来转换从entity到dto,或者从dto到entity的中间的东西 举个例子: h ...
- ABP源码分析十六:DTO的设计
IDTO:空接口,用于标注Dto对象. ComboboxItemDto:用于combobox/list中Item的DTO NameValueDto<T>/NameValueDto:用于na ...
随机推荐
- 【bzoj2795】[Poi2012]A Horrible Poem Hash+分解质因数
题目描述 给出一个由小写英文字母组成的字符串S,再给出q个询问,要求回答S某个子串的最短循环节.如果字符串B是字符串A的循环节,那么A可以由B重复若干次得到. 输入 第一行一个正整数n (n<= ...
- ie,360浏览器出现无法打开网页(包括本地html)的解决方法
有一天,编写网页照例打开chrome,ie,360等浏览器,发现ie,360均无法打开本地网页,输入百度,也无法打开,从没遇到过这种情况,通过百度,找了几种方法,没解决, 后来,看到有一种原因可能是浏 ...
- http请求代理proxy-ajax
今天把项目中的反向代理脚本程序抽成了一个插件,通过配置文件配置代理的http请求,这样使用起来比较方便,每位开发成员都可以用自己配置的代理调试代码.也可以用来直接做http代理,你甚至都不用Charl ...
- Keepalived+NFS+SHELL脚本实现NFS-HA高可用
本来想做DRBD+HEARTBEAT,但是领导说再加硬盘浪费资源,没有必要,而且在已有硬盘上做风险较大,所以就只能用rsync来实现数据同步了,实验中发现很多的坑,都用脚本和计划任务给填上了,打算把这 ...
- css3 背景图动画一
一 实现背景图循环播放 @keyframes mlfly { 0%{ background-position:0 0; } 100%{ background-position:210px 0; } } ...
- 五、 java中数组
定义数组的两种方式 class myarray1 { public static void main(String[] args) { //1.如何定义一个数组 //1.1数组的声明 String[] ...
- AC日记——[HAOI2015]树上操作 洛谷 P3178
题目描述 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个操作,分为三种:操作 1 :把某个节点 x 的点权增加 a .操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 ...
- 神秘的FrontCache
用jmap -histo的时候,发现堆内存中有很多奇怪的对象,其class name为 java.util.HashMap$FrontCache 跳转到HashMap的源码中,直接搜索FrontCac ...
- Hystrix的介绍和简单使用
这周在看项目的相关代码时,接触到了Hystrix,因此查询了相关资料学习了下. 一.什么是Hystrix Hystrix是Netflix针对微服务分布式系统的熔断保护中间件,当我们的客户端连接远程的微 ...
- 微信小程序 压缩图片并上传
转自https://segmentfault.com/q/1010000012507519 wxml写入 <view bindtap='uploadImg'>上传</view> ...