转载  关于Spring Data JPA 更新

分类:java 2019-12-14T15:15:13    52人阅读   

更新:

JpaRepository并没有提供专门的update方法,而是将更新操作放在save中完成了,下面是save方法的源码实现:

@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

我们看到调用save方法传入一个实例,首先会通过entityInformation.isNew(entity)来判断该实体是否是一个新的对象,具体的是先判断有无id,如果有就通过id在数据库中查找是否存在对应的数据,如果存在就是更新操作,会调用EntityManagermerge()方法执行更新,如果不存在就说明是插入操作,会调用EntityManagerpersist()方法执行插入。

EntityManager管理器
通过源码我们可以看到save方法实质上是调用的EntityManager的方法完成的数据库操作,所以这里有必要介绍下EntityManager接口,在此之前得了解jpa中实体对象拥有的四种状态:

  • 瞬时状态(new/transient):没有主键,不与持久化上下文关联,即 new 出的对象(但不能指定id的值,若指定则是游离态而非瞬时态)
  • 托管状态(persistent):使用EntityManager进行find或者persist操作返回的对象即处于托管状态,此时该对象已经处于持久化上下文中(被EntityManager监控),任何对该实体的修改都会在提交事务时同步到数据库中。
  • 游离状态(detached):有主键,但是没有跟持久化上下文关联的实体对象。
  • 删除状态 (deleted):当调用EntityManger对实体进行remove后,该实体对象就处于删除状态。其本质也就是一个瞬时状态的对象。

下面的图清晰的表示了各个状态间的转化关系:

下面介绍下EntityManager接口的几个常用方法:

  • persist():将临时状态(无主键)的对象转化为托管状态。由于涉及数据库增删改,执行该语句前需启用事务
entityManager.persist(modelObject);
  • merge():将游离状态(有主键)的对象转化为托管托管状态,不同于persist(),merger()对于操作的对象,如果对象存在于数据库则对对象进行修改,如果对象在数据库中不存在,则将该对象作为一条新记录插入数据库。
entityManager.merge(modelObject);
  • find()与getReference():从数据库中查找对象。不同点:当对象不存在时,find()会返回null,getReference()则会抛出javax.persistence.EntityNotFoundException异常。
// 参数一:实体类的class,参数二:实体主键值
entityManager.find(Class<T> ModelObject.class , int key);
  • remove():将托管状态的对象转化为删除状态。由于涉及数据库增删改,执行该语句前需启用事务
entityManager.remove(entityManager.getReference(ModelObject.class, key));
  • refresh(Object obj):重新从数据库中读取数据。可以保证当前的实例与数据库中的实例的内容一致。该方法用来操作托管状态的对象。
  • contains(Object obj):判断对象在持久化上下文(不是数据库)中是否存在,返回true/false。
  • flush():立即将对托管状态对象所做的修改(包括删除)写入数据库。
    从上面内容我们发现通过EntityManager对实体对象所做的操作实质是让对象在不同的状态间转换,而这些修改是在执行flush()后才会真正的写入数据库。正常情况下不需要手动执行flash(),在事务提交的时候,JPA会自动执行flush()一次性保存所有数据。
    如果要立即保存修改,可以手动执行flush()。
    同时我们可以通过setFlushModel()方法修改EntityManager的刷新模式。默认为AUTO,这种模式下,会在执行查询(指使用JPQL语句查询前,不包括find()和getReference()查询)前或事务提交时自动执行flush()。通过entityManager.setFlushMode(FlushModeType.COMMIT)设置为COMMIT模式,该模式下只有在事务提交时才会执行flush()。
  • clear():把实体管理器中所有的实体对象(托管状态)变成游离状态,clear()之后,对实体类所做的修改也会丢失。

现在我们再回到更新方法,为了方便查看,我们摘取出上面写好的更新方法实现

public Boolean update(User user) {
    Optional<User> u = userDao.findById(user.getId());
    if (u.isPresent()) {
        User oldUser = u.get();
        oldUser.setName(user.getName());
        oldUser.setRoles(user.getRoles());
        oldUser.setBirthday(user.getBirthday());
        oldUser.setEmail(user.getEmail());
        oldUser.setUpdateTime(new Date());
        userDao.save(oldUser);
        return Boolean.TRUE;
    }
    return Boolean.FALSE;
}

如果你已经理解了上文中的JPA的四种状态,那你应该就能看出这段代码存在的问题,oldUser是从数据库中查出来的,是托管状态对象,受EntityManager管理,我们后面对该对象所做的修改会在事务提交时自动调用flush()将修改写入数据库完成更新,所以并不需要再调用save()方法执行更新,这样显得多此一举。当然还要注意这样实现更新需要在方法上加@Transactional启动事务。

下面是修改后的代码实现:

@Transactional
public Boolean update(User user) {
    Optional<User> u = userDao.findById(user.getId());
    if (u.isPresent()) {
        User oldUser = u.get();
        oldUser.setName(user.getName());
        oldUser.setRoles(user.getRoles());
        oldUser.setBirthday(user.getBirthday());
        oldUser.setEmail(user.getEmail());
        oldUser.setUpdateTime(new Date());
        return Boolean.TRUE;
    }
    return Boolean.FALSE;
}


链接:https://www.jianshu.com/p/a2f98f6d6fbd
分享到: