JPA实体状态学习-(持久态:Persistent)

时间:2022-06-01 16:41:02

今天我们继续学习JPA的实体状态转化,老规矩贴上实体转化的转化图: 

JPA实体状态学习-(持久态:Persistent)

上一节我们学习了:

entityManager调用persist()方法后,实体的状态从transient到persistent,这个时候对persistent状态的实体改变也会同步持久化到数据库,执行tx.commit()之后实体进入detached状态,此时对实体进行改变后不会同步到数据库。

今天我们来看看transient状态的实体调用merge会有什么效果呢?

Persistent(持久态 JPA managed)

代码举例

我们先看第一种情况:

public void mergeTeacher() {
        Teacher teacher = new Teacher("new_email@gmail.com");
        logger.info("teacher: {} is transient state",teacher);
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction tx = entityManager.getTransaction();
        tx.begin();
        entityManager.merge(teacher);
        logger.info("teacher: {} is persistent state",teacher);
        teacher.setEmail("updated_email@gmail.com");
        Long persistedId = teacher.getId();
        tx.commit();
        entityManager.close();
        logger.info("teacher: {} is detached state",teacher);
        logger.info("persistedId:{}",persistedId);
//        entityManager = entityManagerFactory.createEntityManager();
//        tx = entityManager.getTransaction();
//        tx.begin();
//        teacher = entityManager.find(Teacher.class, persistedId);
//        tx.commit();
//        entityManager.close();
//        logger.info("Persisted teacher: {}", teacher);
}
teacher: Student{id=null, email='new_email@gmail.com'} is transient state
Hibernate: 
   call next value for hibernate_sequence
teacher: Student{id=null, email='new_email@gmail.com'} is persistent state
Hibernate: 
   insert 
   into
       teachers
       (email, id) 
   values
       (?, ?)
teacher: Student{id=null, email='updated_email@gmail.com'} is detached state
persistedId:null

这里需要注意的是我们调用entity.merge()方法后,的确值执行了一条insert的SQL,但是merge方法的参数对象没有主键id,而在上一节中entity.persist()方法调用后,实体对象是可以获得主键id的。因此,这里有一个疑问,persist和merge方法有啥区别?

public interface EntityManager {

    public void persist(Object entity);
    
    public <T> T merge(T entity);
}

大家注意这两个方法的区别,persist方法没有返回值,因此受持久化上下文管理的就是参数对象,而merge方法有返回值,有上面的例子可以看出,参数对象是不受持久化上下文管理的,可以猜想可能返回对象是被持久化上下文管理的。参考下面链接的内容:

https://*.com/questions/1069992/jpa-entitymanager-why-use-persist-over-merge

Either way will add an entity to a PersistenceContext, the difference is in what you do with the entity afterwards.

Persist takes an entity instance, adds it to the context and makes that instance managed (ie future updates to the entity will be tracked).

Merge returns the managed instance that the state was merged to. It does return something what exists in PersistenceContext or creates a new instance of your entity. In any case, it will copy the state from the supplied entity, and return managed copy. The instance you pass in will not be managed (any changes you make will not be part of the transaction - unless you call merge again). Though you can use the returned instance (managed one).

解释一下:

两个方法都能将实体添加到一个持久化上下文,区别是你后续需要对实体做些什么操作。

persist直接将参数纳入到持久化上下文进行管理,对这个参数对象的修改会被持久化上下文追踪,执行tx.commit()或者entity.flush()后会同步到数据库

而merge方法不会将参数对象纳入持久化上下文进行管理,而是将方法的返回对象纳入上下文进行管理,对参数对象状态的修改无效,但是对返回对象的修改是会同步到数据库的。我们用代码来验证一下:

public void mergeTeacher() {
    Teacher teacher = new Teacher("new_email@gmail.com");
    logger.info("teacher: {} is transient state",teacher);
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction tx = entityManager.getTransaction();
    tx.begin();
    Teacher newTeacher = entityManager.merge(teacher);
    logger.info("teacher: {} is persistent state",teacher);
    newTeacher.setEmail("updated_email@gmail.com");
    Long persistedId = newTeacher.getId();
    tx.commit();
    entityManager.close();
    logger.info("teacher: {} is detached state",teacher);
    logger.info("newTeacher: {} is detached state",newTeacher);
    logger.info("persistedId:{}",persistedId);
    entityManager = entityManagerFactory.createEntityManager();
    tx = entityManager.getTransaction();
    tx.begin();
    teacher = entityManager.find(Teacher.class, persistedId);
    tx.commit();
    entityManager.close();
    logger.info("Persisted teacher: {}", teacher);
}
teacher: Student{id=null, email='new_email@gmail.com'} is transient state
Hibernate: 
   call next value for hibernate_sequence
teacher: Student{id=null, email='new_email@gmail.com'} is persistent state
Hibernate: 
   insert 
   into
       teachers
       (email, id) 
   values
       (?, ?)
Hibernate: 
   update
       teachers 
   set
       email=? 
   where
       id=?
teacher: Student{id=null, email='new_email@gmail.com'} is detached state
newTeacher: Student{id=1, email='updated_email@gmail.com'} is detached state
persistedId:1
Hibernate: 
   select
       teacher0_.id as id1_0_0_,
       teacher0_.email as email2_0_0_ 
   from
       teachers teacher0_ 
   where
       teacher0_.id=?
Persisted teacher: Student{id=1, email='updated_email@gmail.com'}

通过代码和日志输出可以知道:

entity.merge()方法的返回值是被持久化上下文管理的。

 

这里有一句话说的是参数是不被管理的,除非再一次调用merge方法,我们再用代码来试试

public void mergeTeacher() {
    Teacher teacher = new Teacher("new_email@gmail.com");
    logger.info("teacher: {} is transient state",teacher);
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction tx = entityManager.getTransaction();
    tx.begin();
    Teacher newTeacher = entityManager.merge(teacher);
    logger.info("teacher: {} is persistent state",teacher);
    teacher.setEmail("updated_email@gmail.com");
    Long persistedId = newTeacher.getId();
    tx.commit();
    entityManager.close();
    logger.info("teacher: {} is detached state",teacher);
    logger.info("newTeacher: {} is detached state",newTeacher);
    logger.info("persistedId:{}",persistedId);

    entityManager = entityManagerFactory.createEntityManager();
    tx = entityManager.getTransaction();
    tx.begin();
    newTeacher = entityManager.merge(teacher);
    logger.info("teacher: {} is persistent state",teacher);
    newTeacher.setEmail("updated_email@gmail.com");
    persistedId = newTeacher.getId();
    tx.commit();
    entityManager.close();

    entityManager = entityManagerFactory.createEntityManager();
    tx = entityManager.getTransaction();
    tx.begin();
    teacher = entityManager.find(Teacher.class, persistedId);
    tx.commit();
    entityManager.close();
    logger.info("Persisted teacher: {}", teacher);
}

注意:这里我调用了一句:

newTeacher.setEmail("updated_email@gmail.com");

但是却没有发出update语句,为什么呢?因为呀,原来的对象属性email的值就是update_email@gmail.com,所以没有变化,没有变化自然就不会发出update语句,如果将这个值修改成一个变化的值:update_email1@gmail.com,,那么的确会发出一条update的sql语句,有兴趣的可以试试。

teacher: Student{id=null, email='new_email@gmail.com'} is transient state
Hibernate: 
   call next value for hibernate_sequence
teacher: Student{id=null, email='new_email@gmail.com'} is persistent state
Hibernate: 
   insert 
   into
       teachers
       (email, id) 
   values
       (?, ?)
teacher: Student{id=null, email='updated_email@gmail.com'} is detached state
newTeacher: Student{id=1, email='new_email@gmail.com'} is detached state
persistedId:1
Hibernate: 
   call next value for hibernate_sequence
teacher: Student{id=null, email='updated_email@gmail.com'} is persistent state
Hibernate: 
   insert 
   into
       teachers
       (email, id) 
   values
       (?, ?)
Hibernate: 
   select
       teacher0_.id as id1_0_0_,
       teacher0_.email as email2_0_0_ 
   from
       teachers teacher0_ 
   where
       teacher0_.id=?
Persisted teacher: Student{id=2, email='updated_email@gmail.com'}

注意:这里第二次调用entity.merge的方法的时候,如果参数对象是不受持久化上下文管理的话,实际上是发出了一条insert的SQL语句,那么此时我们会猜想,如果merge的是一个受持久化上下文管理的对象呢?

public void mergeTeacher() {
    Teacher teacher = new Teacher("new_email@gmail.com");
    logger.info("teacher: {} is transient state",teacher);
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction tx = entityManager.getTransaction();
    tx.begin();
    Teacher newTeacher = entityManager.merge(teacher);
    logger.info("teacher: {} is persistent state",teacher);
    teacher.setEmail("updated_email@gmail.com");
    Long persistedId = newTeacher.getId();
    tx.commit();
    entityManager.close();
    logger.info("teacher: {} is detached state",teacher);
    logger.info("newTeacher: {} is detached state",newTeacher);
    logger.info("persistedId:{}",persistedId);

    entityManager = entityManagerFactory.createEntityManager();
    tx = entityManager.getTransaction();
    tx.begin();
    entityManager.merge(newTeacher);
    logger.info("newTeacher: {} is persistent state",newTeacher);
    newTeacher.setEmail("updated_email@gmail.com");
    persistedId = newTeacher.getId();
    tx.commit();
    entityManager.close();

    entityManager = entityManagerFactory.createEntityManager();
    tx = entityManager.getTransaction();
    tx.begin();
    teacher = entityManager.find(Teacher.class, persistedId);
    tx.commit();
    entityManager.close();
    logger.info("Persisted teacher: {}", teacher);
}
teacher: Student{id=null, email='new_email@gmail.com'} is transient state
Hibernate: 
   call next value for hibernate_sequence
teacher: Student{id=null, email='new_email@gmail.com'} is persistent state
Hibernate: 
   insert 
   into
       teachers
       (email, id) 
   values
       (?, ?)
teacher: Student{id=null, email='updated_email@gmail.com'} is detached state
newTeacher: Student{id=1, email='new_email@gmail.com'} is detached state
persistedId:1
Hibernate: 
   select
       teacher0_.id as id1_0_0_,
       teacher0_.email as email2_0_0_ 
   from
       teachers teacher0_ 
   where
       teacher0_.id=?
newTeacher: Student{id=1, email='new_email@gmail.com'} is persistent state
Hibernate: 
   select
       teacher0_.id as id1_0_0_,
       teacher0_.email as email2_0_0_ 
   from
       teachers teacher0_ 
   where
       teacher0_.id=?
Persisted teacher: Student{id=1, email='new_email@gmail.com'}

这次,我们看到只有第一次merge的时候才发出了一条insert的SQL语句,为什么呢,为什么第二次merge方法后执行了下面的语句:

newTeacher.setEmail("updated_email@gmail.com");

为啥没有发出update语句呢,按照道理来说是应该发出SQL语句的呀,想想我们比较persist和merge的方法签名的区别:

将第二个merge方法后面的代码修改成

newTeacher = entityManager.merge(newTeacher);

此时日志信息如下:

teacher: Student{id=null, email='new_email@gmail.com'} is transient state
Hibernate: 
   call next value for hibernate_sequence
teacher: Student{id=null, email='new_email@gmail.com'} is persistent state
Hibernate: 
   insert 
   into
       teachers
       (email, id) 
   values
       (?, ?)
teacher: Student{id=null, email='updated_email@gmail.com'} is detached state
newTeacher: Student{id=1, email='new_email@gmail.com'} is detached state
persistedId:1
Hibernate: 
   select
       teacher0_.id as id1_0_0_,
       teacher0_.email as email2_0_0_ 
   from
       teachers teacher0_ 
   where
       teacher0_.id=?
newTeacher: Student{id=1, email='new_email@gmail.com'} is persistent state
Hibernate: 
   update
       teachers 
   set
       email=? 
   where
       id=?
Hibernate: 
   select
       teacher0_.id as id1_0_0_,
       teacher0_.email as email2_0_0_ 
   from
       teachers teacher0_ 
   where
       teacher0_.id=?
Persisted teacher: Student{id=1, email='updated_email@gmail.com'}

可以知道:merge方法需要对返回值进行修改才有效。从日志可以看出,当merge一个已经被持久化上下文管理的对象时,是先去查询出这个对象,然后对返回值进行更改的时候,才会发出update的SQL语句:

总结请参考链接:

https://*.com/questions/1069992/jpa-entitymanager-why-use-persist-over-merge