[Java Performance] 数据库性能最佳实践 - JPA缓存
JPA缓存(JPA Caching)
JPA有两种类型的缓存:
EntityManager自身就是一种缓存。事务中从数据库获取的和写入到数据库的数据会被缓存(什么样的数据会被缓存。在后面有介绍)。在一个程序中或许会有非常多个不同的EntityManager实例。每个实例执行着不同的事务,拥有着它们自己的缓存。
当EntityManager提交一个事务后,它缓存的全部数据就会被合并到一个全局的缓存中。
全部的EntityManager都可以訪问这个全局的缓存。
全局缓存被称为二级缓存(Level 2 Cache)。而EntityManager拥有的本地缓存被称为一级缓存(Level 1 Cache)。全部的JPA实现都拥有一级缓存,而且对它没有什么能够调优的。
而二级缓存就不同了:大多数JPA实现都提供了二级缓存,可是有些并没有把启用它作为默认选项,比方Hibernate。一旦启用了二级缓存。它的设置会对性能产生较大的影响。
仅仅有当使用实体的主键进行訪问时,JPA的缓存才会工作。这意味着。以下的两种获取方式会将获取的结果放入到JPA的缓存中:
- 调用find()方法,由于它须要接受实体类的主键作为參数
- 调用实体类型的getter方法来得到关联的实体类型。本质上。获取关联的实体对象也是通过关联对象的主键得到,由于在数据库的表结构中。存放的是该关联对象的外键信息。
那么当EntityManager须要通过主键或者关联关系获取一个实体对象时。它首先会去二级缓存中寻找。
假设找到了,那么它就不须要对数据库进行訪问了。
通过查询(JPQL)方式得到的实体对象是不会被放到二级缓存中的。
然而在一些JPA实现中也会将查询得到的结果放入到缓存中。可是仅仅有当同样的查询再次被运行时,这些缓存才会起作用。所以即使JPA的实现支持查询缓存,查询返回的实体也不会被存储在二级缓存中。因此也就不能被诸如find()等方法利用了。
通过以下的一段代码对二级缓存和查询进行性能測试:
EntityManager em = emf.createEntityManager();
Query q = em.createNamedQuery(queryName);
List<StockPrice> l = q.getResultList(); // SQL Call 1
for (StockPrice sp : l) {
// ... process sp ...
if (processOptions) {
Collection<? extends StockOptionPrice> options = sp.getOptions(); // SQL Call 2
for (StockOptionPrice sop : options) {
// ... process sop ...
}
}
}
em.close();
以上代码通过一个命名查询来得到StockPrice实体对象。 布尔变量processOptions用来控制是否遍历关联的StockOptionPrice实体对象。
缓存和懒载入
@NamedQuery(name="findAll", query="SELECT s FROM StockPriceImpl s ORDER BY s.id.symbol") @OneToMany(mappedBy="stock")
private Collection<StockOptionPrice> optionsPrices;
在默认情况下,对于StockPrice关联的StockOptionPrice,因为是一对多的关联方式,后者的载入类型是懒载入。执行
| 測试用例 | 首次运行 | 兴许运行 |
|---|---|---|
| 默认缓存策略 + 懒载入 | 61.9s (33,409 SQL调用) | 3.2s (1 SQL 调用) |
| 默认缓存策略 + 懒载入 + 不遍历关联对象 | 5.6s (1 SQL 调用) | 2.8s (1 SQL 调用) |
当须要遍历关联对象时。在首次运行时产生了大量SQL调用。这是由于对于每一个StockPrice实例。都须要遍历其StockOptionPrice集合,因此产生了:128 * 261 = 33408次SQL调用。
再加上获取StockPrice的一次命名查询,所以一共是33409次。可是在兴许运行时,仅仅会发生一次命名查询导致的SQL调用,这是由于StockOptionPrice此时所有都已经被存储到二级缓存中(由关联关系和find方法得到的实体对象会被保存到二级缓存中,而查询结果则不会被保存),不须要再对数据库进行訪问。
当不须要遍历关联对象时,每次运行都仅仅会产生一次SQL调用。
同一时候注意到对于此測试用例,首次运行仍然比兴许运行要慢整整一倍,这是由于编译器的“热身”也会在首次运行期间进行(关于JIT编译器的性质。请查看相关章节)。
缓存和马上载入
当StockOptionPrice的载入方式切换成马上载入后,得到的測试数据例如以下:
| 測试用例 | 首次运行 | 兴许运行 |
|---|---|---|
| 默认缓存策略 + 马上载入 | 60.2s (33,409 SQL调用) | 3.1s (1 SQL 调用) |
| 默认缓存策略 + 马上载入 + 不遍历关联对象 | 60.2s (33,409 SQL 调用) | 2.8s (1 SQL 调用) |
此时,不管是否选择遍历关联对象。都会发生33409次SQL调用。
由于在运行命名查询得到每一个StockPrice对象后,就会顺便调用StockOptionPrice的getter方法来得到关联对象。此时得到的StockOptionPrice对象会被存储到二级缓存中。因此在兴许运行中不会再触发SQL调用。
JOIN FETCH和缓存
假设在命名查询中使用JOIN FETCH:
@NamedQuery(name="findAll", query="SELECT s FROM StockPriceEagerLazyImpl s " + "JOIN FETCH s.optionsPrices ORDER BY s.id.symbol")
| 測试用例 | 首次运行 | 兴许运行 |
|---|---|---|
| 默认配置 | 61.9s (33,409 SQL调用) | 3.2s (1 SQL 调用) |
| JOIN FETCH | 17.9s (1 SQL 调用) | 11.4s (1 SQL 调用) |
| JOIN FETCH + 查询缓存 | 17.9s (1 SQL 调用) | 1.1s (0 SQL 调用) |
当使用了JOIN FETCH后,性能得到了很大的提升。尽管查询的数据量是相同的。可是发生的SQL调用剧减到了1,这也是性能得以大幅提升的首要原因。可是。由于缺少查询缓存。在兴许调用的时候仍然须要较长的时间(相同地,运行时间从17.9s -> 11.4s是由于首次运行期间JIT编译器须要“热身”)。
所以在最后一个測试用例,当开启了查询缓存后,兴许运行的时间大幅缩短到1.1s。同一时候没有发生SQL调用。这是一个使用查询缓存的典型样例。可是须要注意仅仅有当查询使用的參数全然同样时,查询缓存才会起作用。
避免查询
依据二级缓存的特点,假设不使用查询,那么得到的全部对象都会被保存到二级缓存中。那么当程序执行一段时间后。随着对象都被缓存,须要执行的SQL语句就越来越少。程序的执行速度也就越来越快了:
EntityManager em = emf.createEntityManager();
ArrayList<String> allSymbols = ... all valid symbols ...;
ArrayList<Date> allDates = ... all valid dates...;
for (String symbol : allSymbols) {
for (Date date = allDates) {
StockPrice sp = em.find(StockPriceImpl.class, new StockPricePK(symbol, date);
// ... process sp ...
if (processOptions) {
Collection<? extends StockOptionPrice> options = sp.getOptions();
// ... process options ...
}
}
}
測试结果例如以下所看到的:
| 測试用例 | 首次运行 | 兴许运行 |
|---|---|---|
| 默认配置 | 61.9s (33,409 SQL调用) | 3.2s (1 SQL 调用) |
| 无查询 | 100.5s (66,816 SQL 调用) | 1.19s (0 SQL 调用) |
首次运行会产生66816次SQL调用。当中33408次是调用find方法时产生的。另外33408次时调用getOptions方法时产生的。在此之后。全部的对象都会被保存到二级缓存中,因此兴许运行时,没有SQL被运行。
所以,当使用无查询的策略是。首次运行的时间一般会比較长,这个过程能够被看成是一个“热身”的过程。在“热身”结束之后。程序的性能会提高一个档次。
另外须要注意的一个问题是,即使使用getOptions方法得到的是一个集合对象,这个集合对象的全部元素也会被存储到二级缓存中,不要将它和查询混淆。所以,当希望缓存一个实体对象关联的一组实体对象时,仅仅须要调用对应的getter方法就可以。甚至不须要对该集合进行遍历。
设置JPA缓存的空间
当JPA缓存占用的内存过多时,它会给GC加入不小的压力。
所以JPA缓存的空间须要被细致设置。可是,JPA规范并没有规定怎样设置JPA缓存。所以须要查看相应JPA实现的相关文档。
TODO:和堆相关
总结
- JPA的二级缓存会自己主动地为应用缓存对象。
- 二级缓存不会保存查询(JPQL)的返回对象。所以当须要缓存对象时,不要使用查询。
(或者开启查询缓存)
- 慎重使用结合了JOIN FETCH的查询。除非使用的JPA实现支持查询缓存。由于默认情况下。查询会跳过二级缓存。
JPA仅仅读实体(JPA
Read-Only Entities)
虽然JPA规范并没有介绍仅仅读实体。可是在非常多JPA实现中,都会这样的实体作出对应的优化。
对仅仅读实体的操作在性能上一般都会优于读写实体(Read-Write Entities)。由于对于仅仅读实体,不须要保存它的状态,不须要将它放在事务中。也不须要对它进行加锁。
在Java EE容器中。不管使用的什么JPA实现,仅仅读实体一般都会被支持。应用server会保证对这些实体的获取是通过一个特殊的非事务性的JDBC连接来完毕。
这样做通常都有更好的性能。
[Java Performance] 数据库性能最佳实践 - JPA缓存的更多相关文章
- [Java Performance] 数据库性能最佳实践 - JPA和读写优化
数据库性能最佳实践 当应用须要连接数据库时.那么应用的性能就可能收到数据库性能的影响. 比方当数据库的I/O能力存在限制,或者因缺失了索引而导致运行的SQL语句须要对整张表进行遍历.对于这些问题.只相 ...
- paip.提升性能--多核编程中的java .net php c++最佳实践 v2.0 cah
paip.提升性能--多核编程中的java .net php c++最佳实践 v2.0 cah 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax ...
- (转)Amazon Aurora MySQL 数据库配置最佳实践
转自:https://zhuanlan.zhihu.com/p/165047153 Amazon Aurora MySQL 数据库配置最佳实践 AWS云计算 已认证的官方帐号 1 人赞同了该文章 ...
- paip.java gui swt/jface 最佳实践
paip.java gui swt/jface 最佳实践 1. 工具:Eclipse +jigloo4 1 2. 安装插件: 1 1. IMPORT swt lib 2 2. 新建立窗体 2 3. 运 ...
- SQL Server系统数据库备份最佳实践
原文:SQL Server系统数据库备份最佳实践 首先了解主要的系统数据库: 系统数据库 master 包含登录信息和其他数据库的核心信息 msdb 存储作业.操作员.警报.备份还原历史.数据库邮件信 ...
- atitit.Atitit. Gui控件and面板-----服务端控件 java struts的实现最佳实践
atitit.Atitit. Gui控件and面板-----服务端控件 java struts的实现最佳实践 1. 服务器控件的类别 1 1.1. 数据控件:该类控件可细分为两种类型:数据源控件和数 ...
- (转)调优 DB2 UDB v8.1 及其数据库的最佳实践
原文:https://www.ibm.com/developerworks/cn/data/library/techarticles/dm-0404mcarthur/index.html 简介 性能是 ...
- Java异常处理 10 个最佳实践
异常处理是Java 开发中的一个重要部分.它是关乎每个应用的一个非功能性需求,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch 和 ...
- http网页性能最佳实践
你愿意为打开一个网页等待多长时间?我一秒也不愿意等.但是事实上大多数网站在响应速度方面都让人失望.现在越来越多的人开始建立自己的网站,博客,你的网页响应速度如何呢?在这篇文章中我们来介绍一下提高网页性 ...
随机推荐
- ThinkPHP - 自定义标签库 - 标签驱动
ThinkPHP 官方文档:http://document.thinkphp.cn/manual_3_2/taglib_driver.html 创建一个类,继承自TagLib类: <?php / ...
- iOS 8 强制横屏
最近用到视频播放功能:(Vitamio, 注:在Build Setting 里面的 Other Link Flag 添加-all_load) iOS 8的屏幕旋转比较坑, 使用以下代码可以强制旋转 - ...
- php下正则表达式整理
一.正则表达式的历史背景 1,内容深厚的正则表达式 ^.+@.+\\..+$ 形式 字符串搜索与匹配的工具 2,应用范围 手机输入法 Windows文件搜索 linux 列出文件命令 网站用户注册,如 ...
- USACO Palindromic Squares 【STL__string_的应用】
这里有个讲解 string 用法非常详细的博文:https://www.byvoid.com/zhs/blog/cpp-string 题目意思很简单啦,就是找回文 使用string可以高速A过 Sou ...
- 【转】Visual Stdio VS 错误 error : 0xC00000FD: Stack overflow. 更改堆栈空间解决栈溢出问题
原文见:http://www.cnblogs.com/xiangwengao/archive/2012/03/16/2399888.html 问题 给一个程序添加小功能,在debug下能正常运行,在r ...
- Hibernate执行sql语句
Hibernate执行sql语句:BasicServiceImpl basicServiceImpl = new BasicServiceImpl();String hql = "selec ...
- JavaScript 的数组操作--删除元素
在JavaScript中,可以很方便的删除指定位置的元素,这个是用到 splice方法, 该方法用于删除或替换数组中的部分数据. 其语法定义是 : splice(start , count [,new ...
- OnPaint()函数的作用原理
WM_PAINT是窗口每次重绘都会产生的一个消息. OnPaint是对这个消息的反应函数 mfc 的 CWnd::OnPaint 没做什么,只是丢给系统处理. 一 : 先执行OnEraseBkgnd, ...
- PL/SQL连64位Oracle11g R2 win7 64旗舰环境
说明:使用的软件版本是PL/SQL Developer 7.0.1,Oracle服务器端安装在Windows7旗舰版上. 以下是我的步骤 1,先到Oracle网站下载客户端:http://www.or ...
- Python3 正则表达式特殊符号及用法(详细列表) http://bbs.fishc.com/thread-57691-1-1.html (出处: 鱼C论坛)
http://bbs.fishc.com/thread-57691-1-1.html 留待查询用