一、实体的状态
① 新建状态 NEW
新创建的状态,尚未拥有持久性主键
实际上就是new了一个普通的JavaBean对象
② 持久化状态
已经拥有持久化主键并和持久化建立了上下文环境
当处在托管状态的实体Bean被管理器flush了,那么就在极短暂的时间进入了持久化状态,事务提交之后,立刻变为了游离状态。
您可以把持久化状态当做实实在在的数据库记录。
③ 游离状态
拥有持久化主键,但是没有与持久化建立上下文环境
游离状态就是提交到数据库后,事务commit后实体的状态,因为事务已经提交了,此时实体的属性任你如何改变,也不会同步到数据库,因为游离是没人管的孩子,不在持久化上下文中。
④ 删除状态 REMOVED
拥有持久化主键,已经和持久化建立了上下文环境,但是从数据库中删除
⑤ 托管状态
临时状态在调用persist()后,即可将一般的JavaBean做为了托管状态的Bean,该Bean的任何属性改动都会牵涉到数据库记录的改动。
一旦该记录flush到数据库之后,并且事务提交了,那么此对象不在持久化上下文中,即:变为了游离(没人管的孩子)状态了。
在游离状态的时候调用更新、刷新方法后,游离状态对象就变为了在持久化上下文的托管状态了。
通过管理器的find方法,将实体从数据库查询出来后,该实体也就变为了托管形态。
二、EntityManger && PersistenceContext
被EntityManager持久化到数据库中的对象,或者从数据库拉入内存中的对象,也会同时被一个持久化上下文(PersistenceContext)管理。
这些被管理的对象统称为受管对象(Managed Object),每个受管对象都有一个唯一的id。EntityManager和PersistenceContext之间的关系,一般可以是多对一的,即多个EntityManager可以同时指向一个PersistenceContext。
这其实很好理解,就是EntityManager虽然有多个实例,但是它们背后的持久化上下文却只有一个,这样就保证了多个EntityManager所管理的受管对象拥有的ID是唯一的。
受到容器托管的EntityManager可以通过注解@PersistenceContext
@PersistenceContext
private EntityManager em;
三、Persistence 和EntityManagerFactory
Persistence
Persistence 类是用于获取 EntityManagerFactory 实例。该类包含一个名为 createEntityManagerFactory 的 静态方法 。
createEntityManagerFactory 方法有如下两个重载版本。
带有一个参数的方法以 JPA 配置文件 persistence.xml 中的持久化单元名为参数
带有两个参数的方法:前一个参数含义相同,后一个参数 Map类型,用于设置 JPA 的相关属性,这时将忽略其它地方设置的属性。Map 对象的属性名必须是 JPA 实现库提供商的名字空间约定的属性名。
EntityManagerFactory
EntityManagerFactory 接口主要用来创建 EntityManager 实例。该接口约定了如下4个方法:
- createEntityManager():用于创建实体管理器对象实例。
- createEntityManager(Map map):用于创建实体管理器对象实例的重载方法,Map 参数用于提供 EntityManager 的属性。
- isOpen():检查 EntityManagerFactory 是否处于打开状态。实体管理器工厂创建后一直处于打开状态,除非调用close()方法将其关闭。
- close():关闭 EntityManagerFactory 。 EntityManagerFactory 关闭后将释放所有资源,isOpen()方法测试将返回 false,其它方法将不能调用,否则将导致IllegalStateException异常。
四、EntityManager 常用的API
find
find (Class<T> entityClass,Object primaryKey);
/*
第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值。
返回指定的 OID 对应的实体类对象,
如果这个实体存在于当前的持久化环境,则返回一个被缓存的对象;
否则会创建一个新的 Entity, 并加载数据库中相关信息;
若 OID 不存在于数据库中,则返回一个 null。
*/
getReference
getReference (Class<T> entityClass,Object primaryKey);
/*
第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值。
与find()方法类似
不同的是:如果缓存中不存在指定的 Entity, EntityManager 会创建一个 Entity 类的代理,但是不会立即加载数据库中的信息,只有第一次真正使用此 Entity 的属性才加载,
所以如果此 OID 在数据库不存在,getReference()不会返回null, 而是抛出EntityNotFoundException
*/
persist
persist (Object entity);
/*
参数为实体对象
用于将新创建的 Entity 纳入到 EntityManager 的管理。该方法执行后,传入 persist() 方法的 Entity 对象转换成持久化状态。
如果传入 persist() 方法的 Entity 对象已经处于持久化状态,则 persist() 方法什么都不做。
如果对删除状态的 Entity 进行 persist() 操作,会转换为持久化状态。
如果对游离状态的实体执行 persist() 操作,可能会在 persist() 方法抛出 EntityExistException(也有可能是在flush或事务提交后抛出)。
*/
remove
remove (Object entity);
/*
删除实例。如果实例是被管理的,即与数据库实体记录关联,则同时会删除关联的数据库记录。
*/
merge
merge (T entity);
// merge() 用于处理 Entity 的同步。即数据库的插入和更新操作
flush
flush();
//同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库中。
clear
clear()
/*
1.清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。
2.在处理大量实体的时候,如果你不把已经处理过的实体从EntityManager中分离出来,将会消耗你大量的内存。
3.调用EntityManager 的clear()方法后,所有正在被管理的实体将会从持久化内容中分离出来。
4.有一点需要说明下,在事务没有提交前(事务默认在调用堆栈的最后提交,如:方 法的返回),如果调用clear()方法,之前对实体所作的任何改变将会丢失
*/
persist和merge跟save的一些区别 & flush方法:
-
persist方法和save方法略微不同:如果对象有id,则persist方法不能执行insert操作,会抛出异常
-
如果传递进persist()方法的参数不是实体Bean,会引发IllegalArgumentException
-
merge方法:
-
传入的对象没有id
在这种情况下,调用merge方法,将返回一个新的对象(有id),并对这个新的对象执行insert操作。
-
传入的对象有id,entityManager的缓存中没有该对象,数据库中没有该记录
在这种情况下,调用merge方法,将返回一个新的对象,并对该对象执行insert操作。
新对象的id是数据库中这条记录的id(比如自增长的id),而不是我们自己传入的id。(其实和情况1的结果是一样的)
- 传入的对象有id,entityManager的缓存没有该对象,数据库中有该记录
在这种情况下,调用merge方法,将会从数据库中查询对应的记录,生成新的对象,然后将我们传入的对象复制到新的对象,最后执行update操作。简单来说,就是更新操作。
-
传入的对象有id,entityManager的缓存有该对象
在这种情况下,调用merge方法,JPA会把传入的对象赋值到entityManager的缓存中的对象,然后对entityManager缓存中的对象执行update操作。(和情况3的结果一样)
总结
执行merge时,如果实体ID为空,则进行insert操作, 如果有ID则进行update操作。
-
-
flush方法
- ORM框架执行的一些更新数据库的方法,其实质是在更新缓存,只有调用了flush()后才会将缓存同步到数据库,即真正执行SQL语句,但是这时并没有真正将数据保存进数据库,需要事务commit后才能全部保存。
- 一般flush后立刻就会进行事务的提交。
- 当EntityManager对象在一个sessionbean 中使用时,它是和服务器的事务上下文绑定的。
- 4.在一个session bean 中,服务器的事务默认地会在调用堆栈的最后提交(如:方法的返回)。
-
总而言之,若传入save的实体对象没有主键则进行insert操作,否则进行update操作
五、利用EntityManager批量插入
有的时候我们需要循环保存数据,当保存大量数据的时候,
如果到最后才提交所有数据,那么数据库的负载可能会比较大。
我们可以这样做,每500个记录就提交(flush)一次。
public class BatchDao {
@PersistenceContext
private EntityManager entityManager;
//每500个记录提交一次
private static final int BATCH_SIZE = 500;
/**
* 批量插入
*
* @param list 需要插入的list
* @param <T> 实体类型
*/
public <T> void batchInsert(List<T> list) {
if (!ObjectUtils.isEmpty(list)) {
for (int i = 0; i < list.size(); i++) {
//变成托管状态
entityManager.persist(list.get(i));
if (i % BATCH_SIZE == 0) {
entityManager.flush();
entityManager.clear();
}
}
//变成持久化状态
entityManager.flush();
//变成游离状态
entityManager.clear();
}
}
/**
* 批量更新
*
* @param list 要更新的list
* @param <T> 实体类型
*/
public <T> void batchUpdate(List<T> list) {
if (!ObjectUtils.isEmpty(list)) {
for (int i = 0; i < list.size(); i++) {
//变成托管状态
entityManager.merge(list.get(i));
if (i % BATCH_SIZE == 0) {
entityManager.flush();
entityManager.clear();
}
}
//变成持久化状态
entityManager.flush();
//变成游离状态
entityManager.clear();
}
}
}