JPA 101
If there’s one thing you have to understand to successfully use JPA (Java Persistence API) it’s the concept of a Cache. Almost everything boils down to the Cache at one point or another.
Here’s a quick cheat sheet of the JPA world:
- A Cache is a copy of data, copy meaning pulled from but living outside the database.
- Flushing a Cache is the act of putting modified data back into the database.
- A PersistenceContext is essentially a Cache. It also tends to have it’s own non-shared database connection.
- An EntityManager represents a PersistenceContext (and therefore a Cache)
- An EntityManagerFactory creates an EntityManager (and therefore a PersistenceContext/Cache)
本质上,ORM框架关注的是数据库里的数据和它们的缓存(内存对象)之间的交互。
文章里讲的cache主要指的是“一级缓存(first level cache)”。
JPA的Locking
主要是为了在OneFeed里测试一下<table>
。
Lock Mode | Description |
---|---|
OPTIMISTIC | Obtain an optimistic read lock for all entities with version attributes. |
OPTIMISTIC_FORCE_INCREMENT | Obtain an optimistic read lock for all entities with version attributes, and increment the version attribute value. |
PESSIMISTIC_READ | Immediately obtain a long-term read lock on the data to prevent the data from being modified or deleted. Other transactions may read the data while the lock is maintained, but may not modify or delete the data. The persistence provider is permitted to obtain a database write lock when a read lock was requested, but not vice versa. |
PESSIMISTIC_WRITE | Immediately obtain a long-term write lock on the data to prevent the data from being read, modified, or deleted. |
PESSIMISTIC_FORCE_INCREMENT | Immediately obtain a long-term lock on the data to prevent the data from being modified or deleted, and increment the version attribute of versioned entities. |
READ | 来自JPA 1.0,OPTIMISTIC的同义词。 |
WRITE | 来自JPA 1.0,OPTIMISTIC_FORCE_INCREMENT的同义词。 |
NONE | 不加应用任何锁机制 |
可以通过多种途径设置锁,
要不要使用ORM框架
到底应不应该在项目中使用ORM(Object-relational mapping)框架?
来自Martin Fowler的一篇文章,OrmHate。文章的观点是,
关系型数据库和内存对象的相互映射本来就是一个很难的问题: 因为两者属于两种不同的建模方式,而且还要考虑两者的数据一致性(特别是在并发访问/修改的情况下)。 一些开发者对ORM框架有着不实际的期待——他们只想处理(灵活的)内存对象,期待ORM框架能(完美)处理对数据库的操作。 实际上,如果使用了关系型数据库,那么就应该在开发中考虑关系型数据库带来的影响。 如果ORM框架能解决80%的问题,那么使用它就是值得的。 另外,如果不需要双向的映射(bi-directional mapping),或者NoSQL数据库适用于业务场景,可以考虑不用ORM框架。
另一篇文章,ORM Haters Don’t Get It,更细节一些。文章的观点是,
即使不使用现成的ORM框架,也会在项目中创造出一个功能类似ORM框架的东西。 使用ORM还有一些额外的好处,比如方便切换数据库、现成的缓存实现等。 需要防止错误地使用ORM框架(需要熟悉ORM框架,ORM不是一个傻瓜式框架)。 另外ORM框架的session管理一般比较复杂,想要配置好需要一定的经验。
一些想法,
- 考虑一下NoSQL是否能满足业务需求。
- 考虑一下轻量级的ORM框架,比如MyBatis,能否满足要求。
- 使用ORM框架时,考虑显式地使用native query(有时候ORM框架生成的SQL可能并不理想)
- 在开发过程中打印出ORM框架所执行的SQL语句,确认ORM框架的行为符合性能要求和预期。比如通过执行的SQL语句可以很容易地发现N+1问题。
使用ORM框架(比如Ruby on Rails的ActiveRecord)的另外一个好处是可以快速地进行原型开发。
Multi-Tenancy in JPA
EclipseLink
EclipseLink支持多种多租户(multi-tenancy)实现。 主要通过@Multitenant等相关的annotation来提供对multi-tenancy的支持。
public @interface Multitenant {
MultitenantType value() default MultitenantType.SINGLE_TABLE;
boolean includeCriteria() default true;
}
@Multitenant
支持SINGLE_TABLE
和TABLE_PER_TENANT
等方式(还有VPD
)。
SINGLE_TABLE
表示多个租户共用一张表;一般这种方式单租户的成本比较低。
TABLE_PER_TENANT
表示不同租户的表是隔离的。隔离的方式有不同的租户的表有不同的前缀/后缀或者放在不同的schema里。
配置好相关的annotation后,查询、更新或者删除时EclipseLink会自动帮你在query里拼接上tenant信息。 在某些情况下,自动拼出来的query也会有问题。比如EclipseLink 2.6.2在multi-tenancy下的left join就有问题。 EclipseLink会在left join的where子句里拼接上tenant相关的条件,
SELECT t0.ID, t1.ID FROM CURRENCY t0 LEFT OUTER JOIN EXCHANGERATE t1
ON (((t1.currencyId = t0.ID) AND (t1.RATEDATE >= ?)) AND (t1.RATEDATE <= ?))
WHERE (((t0.STATUS = ?) AND (t0.TENANT_ID = ?)) AND (t1.TENANT_ID = ?))
(t1.TENANT_ID = ?)
会使得left join“退化”成inner join。
对于这种问题可以用native query来避免EclipseLink对query的修改;
JPA性能调优简介
(主要参考Hibernate和EclipseLink的文档。)
Fetching strategies
对于entity的association到底应该lazy fetch还是eager fetch。 需要为不同的场景指定合适的fetch策略。 不该eager的时候eager fetch费内存;不该lazy的时候lazy fetch会增加和数据库间的通信次数。
The Hibernate recommendation is to statically mark all associations lazy and to use dynamic fetching strategies for eagerness. This is unfortunately at odds with the JPA specification which defines that all one-to-one and many-to-one associations should be eagerly fetched by default. Hibernate, as a JPA provider, honors that default.
Hibernate推荐把association的fetch策略先定义为lazy的(statically,比如@ManyToOne(fetch = FetchType.LAZY)
),
然后在需要eager fetch的时候再动态地指定fetch策略。
比如可以通过JPQL或者Criteria以Fetch Join的方式来eager fetch,
SELECT mag FROM Magazine mag LEFT JOIN FETCH mag.articles WHERE mag.id = 1
-- 如果不想返回重复结果,可以加上DISTINCT
builder.createQuery(Employee.class).from(Employee.class).fetch("projects", JoinType.LEFT)...
通过entity graph(@NamedEntityGraph
)或者Hibernate profile(@FetchProfile
)可以为不同的场景指定fetch的策略。
另外,在lazy fetch的情况下,还可以通过指定batch size来减少fetch的次数。
Pagination
如果结果集太大,则考虑通过setFirstResult()
和setMaxResults()
以“分页”的方式来返回结果集。