【问题标题】:Refresh and fetch an entity after save (JPA/Spring Data/Hibernate)保存后刷新并获取实体(JPA/Spring Data/Hibernate)
【发布时间】:2018-01-11 11:48:38
【问题描述】:

我有这两个简单的实体SomethingPropertySomething 实体与Property 具有多对一的关系,因此当我创建一个新的Something 行时,我会分配一个现有的Property

某事:

@Entity
@Table(name = "something")
public class Something implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "owner")
    private String owner;

    @ManyToOne
    private Property property;

    // getters and setters

    @Override
    public String toString() {
        return "Something{" +
            "id=" + getId() +
            ", name='" + getName() + "'" +
            ", owner='" + getOwner() + "'" +
            ", property=" + getProperty() +
            "}";
    }

属性:

@Entity
@Table(name = "property")
public class Property implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "shape")
    private String shape;

    @Column(name = "color")
    private String color;

    @Column(name = "dimension")
    private Integer dimension;

    // getters and setters

    @Override
    public String toString() {
        return "Property{" +
            "id=" + getId() +
            ", shape='" + getShape() + "'" +
            ", color='" + getColor() + "'" +
            ", dimension='" + getDimension() + "'" +
            "}";
    }
}

这是SomethingRepository(春天):

@SuppressWarnings("unused")
@Repository
public interface SomethingRepository extends JpaRepository<Something,Long> {
    
}

通过一个 REST 控制器和一个 JSON,我想创建一个新的Something

@RestController
@RequestMapping("/api")
public class SomethingResource {

    private final SomethingRepository somethingRepository;

    public SomethingResource(SomethingRepository somethingRepository) {
        this.somethingRepository = somethingRepository;
    }

    @PostMapping("/somethings")
    public Something createSomething(@RequestBody Something something) throws URISyntaxException {
        Something result = somethingRepository.save(something);
        return result;
    }
}

这是输入中的 JSON(propertyid1 是数据库中的现有行):

{
  "name": "MyName",
  "owner": "MySelf",
  "property": {
    "id": 1
  }

}

问题是:在方法.save(something)之后,变量result包含了持久化的实体,但是没有字段property的字段,经过验证(它们是null):

输出 JSON:

{
  "id": 1,
  "name": "MyName",
  "owner": "MySelf",
  "property": {
    "id": 1,
    "shape": null,
    "color": null,
    "dimension": null
  }
}

我希望它们在保存操作后得到验证/返回。

要解决这个问题,我必须在 REST 控制器中注入/声明 EntityManager,并调用方法 EntityManager.refresh(something)(或者我必须调用 .findOne(something.getId()) 方法来获得完整的持久化实体):

@RestController
@RequestMapping("/api")
@Transactional
public class SomethingResource {

    private final SomethingRepository somethingRepository;
    
    private final EntityManager em;

    public SomethingResource(SomethingRepository somethingRepository, EntityManager em) {
        this.somethingRepository = somethingRepository;
        this.em = em;
    }

    @PostMapping("/somethings")
    public Something createSomething(@RequestBody Something something) throws URISyntaxException {
        Something result = somethingRepository.save(something);
        em.refresh(result);
        return result;
    }
}

使用此解决方法,我已经保存了预期的 entith(使用正确的 JSON):

{
  "id": 4,
  "name": "MyName",
  "owner": "MySelf",
  "property": {
    "id": 1,
    "shape": "Rectangle",
    "color": "Red",
    "dimension": 50
  }
}

是否有使用 JPA 或 Spring 或 Hibernate 的自动方法/注释,以便拥有“完整”的持久实体?

我想避免在每个 REST 或 Service 类中声明 EntityManager,或者我想避免每次我想要新的刷新的持久实体时调用 .findOne(Long) 方法。

【问题讨论】:

    标签: java hibernate jpa spring-data-jpa persistence


    【解决方案1】:

    您可以通过创建一个自定义 JpaRepository 来定义一次,而不是在每个资源 中定义EntityManagerReference

    然后在每个存储库中直接使用EntityManagerrefresh

    参考下面的例子:

    CustomRepository 接口

    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.repository.NoRepositoryBean;
    
    import java.io.Serializable;
    
    @NoRepositoryBean
    public interface CustomRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
      void refresh(T t);
    }
    

    CustomRepository 实现

    import org.springframework.data.jpa.repository.support.JpaEntityInformation;
    import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.persistence.EntityManager;
    import java.io.Serializable;
    
    public class CustomRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
        implements CustomRepository<T, ID> {
    
      private final EntityManager entityManager;
    
      public CustomRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
      }
    
      @Override
      @Transactional
      public void refresh(T t) {
        entityManager.refresh(t);
      }
    }
    

    在 Spring Boot 应用程序类中启用自定义 JPARepository

    @SpringBootApplication
    @EnableJpaRepositories (repositoryBaseClass = CustomRepositoryImpl.class)
    public class Application {
      public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
      }
    }
    

    你的东西存储库

    public interface SomethingRepository extends CustomRepository<Something, Long> {
    
    }
    

    直接在SomethingResource中使用Refresh(假设Something是一个实体)

    @RestController
    @RequestMapping("/api")
    @Transactional
    public class SomethingResource {
    
        private final SomethingRepository somethingRepository;
    
        public SomethingResource(SomethingRepository somethingRepository) {
            this.somethingRepository = somethingRepository;
        }
    
        @PostMapping("/somethings")
        public Something createSomething(@RequestBody Something something) throws URISyntaxException {
            Something result = somethingRepository.save(something);
            somethingRepository.refresh(result);
            return result;
        }
    }
    

    【讨论】:

    • 干净、清晰、可重复使用。这应该是公认的答案。
    • 我收到错误“interface required” for repository "public interface SomethingRepository extends CustomRepository{}"
    • @Shailesh 很难在没有看到您的代码的情况下判断出什么问题。你能分享你的代码吗?在没有看到代码的情况下我可以说的是验证 CustomRepository 是一个接口而不是你代码中的一个类。
    • 对我来说,它抛出异常:没有找到类型 Something 的属性刷新,你能解释一下为什么
    • @Khuynh Thành Chi Luyến ,您错过了 repositoryBaseClass = CustomRepositoryImpl.class 行。它解决了你的问题
    【解决方案2】:

    这还不够:

    Something result = somethingRepository.save(something);
    

    需要手动合并传入的实体:

    Something dbSomething = somethingRepository.findOne(
        Something.class, something.getId()
    );
    dbSomething.setName(something.getName());
    dbSomething.setOwner(something.getOwner());
    
    somethingRepository.save(dbSomething);
    

    由于property 属性使用默认的FetchType.EAGER,实体应该初始化property 属性。

    但是,从 REST 控制器调用存储库两次很奇怪。您应该有一个服务层,它可以在 @Transactional 服务方法中完成所有这些工作。这样,您不需要重新保存实体,因为它已经被管理了。

    @Transactional
    public Something mergeSomething(Something something) {
        Something dbSomething = somethingRepository.findOne(
            Something.class, something.getId()
        );
        dbSomething.setName(something.getName());
        dbSomething.setOwner(something.getOwner());
    
        return dbSomething;
    }
    

    现在,您需要仔细合并您发送的每个属性。在您的情况下,如果您为 property 发送 null,您应该决定是否应该取消 @ManyToOne 引用。因此,这取决于您当前的应用程序业务逻辑要求。

    更新

    如果您确保始终发回之前提取的同一实体,则可以使用 merge

    em.merge(result);
    

    但是您的 property 属性只是一个 id,而不是实际的子实体,因此您必须在 Service 层中自己解决。

    【讨论】:

    • 我在 REST 中使用存储库只是为了简单和简短。我不明白你为什么要手动合并传入的实体,我在输入和输出实体之间合并没有问题。我只有property 字段(及其字段)有问题。我试图在@Service 类和标记为@Transactional 的方法中移动保存操作,但没有任何改变。我不明白为什么 Spring 和 Hibernate 不检索也不将字段附加到 property 字段。只有id。在输入 JSON 中,我不需要也不想指定它们的值。我想从 DB 重新加载。
    • javax.transaction 还是 springframework.transaction 用于导入?
    【解决方案3】:

    在 Spring Boot JpaRepository 中:

    如果我们的修改查询更改了持久化上下文中包含的实体,那么这个上下文就会过时。

    为了从数据库中获取具有最新记录的实体。

    使用@Modifying(clearAutomatically = true)

    @Modifying 注解具有 clearAutomatically 属性,该属性定义在执行修改查询后是否应清除底层持久性上下文。

    例子:

    @Modifying(clearAutomatically = true)
    @Query("UPDATE NetworkEntity n SET n.network_status = :network_status WHERE n.network_id = :network_id")
            int expireNetwork(@Param("network_id") Integer network_id,  @Param("network_status") String network_status);
    

    【讨论】:

    • 那行不通。我有一个案例,在数据库触发器插入后修改了一些列。然而,保存后这些列在我的实体中似乎为空。我想扩展 EntityManager 并调用 refresh 方法是解决这个问题的唯一方法。
    • 这行得通,但您可能还想将它与flushAutomatically = true 一起使用。请参阅stackoverflow.com/a/57062549/71066 了解更多信息。
    【解决方案4】:

    当您持久化实体时,它将处于托管状态,因此如果您只调用 something.getProperty();,它会从数据库加载并填充 something 实体的 property

    public Something save(Something something) {
        em.persist(something);
        something.getProperty();
        return something;
    }
    

    通常当您有应该自动获取的多对一关系时。如果不调用实体中对象的 getter,也会通过触发新的 DB Find 请求来填充它们。

    【讨论】:

    • 试图在代码的各个点调用 .getProperty() 方法;我也在一些教程中看到了这一点,但就我而言,什么也没发生,它不起作用。很奇怪。我正在尝试使用 .getProperty() 找到一些解决方案(还使用一些注释,如 PostLoad、PostPersist、JsonGetter 和 LAZY/EAGER fetch)。
    • 你是不是用相同的persist方法做到的
    • 没有。我在 .save 方法之后在 REST 控制器中执行此操作。我无法访问 .save 方法,因为它是一个 Spring 方法,存在于存储库接口中(它扩展了 JpaRepository)
    • 没办法。至少在 JPA 中它可以工作,因为您在 JPA 中编写了 em.persist() 方法
    猜你喜欢
    • 2016-09-03
    • 2018-04-20
    • 2019-09-30
    • 2018-12-02
    • 1970-01-01
    • 2013-05-09
    • 1970-01-01
    • 2023-01-27
    • 1970-01-01
    相关资源
    最近更新 更多