Java 保存后刷新并获取实体(JPA/Spring Data/Hibernate)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45491551/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 02:03:53  来源:igfitidea点击:

Refresh and fetch an entity after save (JPA/Spring Data/Hibernate)

javahibernatejpaspring-data-jpapersistence

提问by Andrea Bevilacqua

I've these two simple entities Somethingand Property. The Somethingentity has a many-to-one relationship to Property, so when I create a new Somethingrow, I assign an existing Property.

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

Something:

某物:

@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() +
            "}";
    }

Property:

财产:

@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() + "'" +
            "}";
    }
}

This is the SomethingRepository(Spring):

这是SomethingRepository(春天):

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

}

Through a REST controller and a JSON, I want to create a new Something:

通过 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;
    }
}

This is the JSON in input (the propertyid1 is an existing row in the database):

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

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

}

}

The problem is: after the method .save(something), the variable resultcontains the persisted entity, but without the fields of field property, validated (they are null):

问题是:在 method 之后.save(something),变量result包含持久化实体,但没有 field 字段property,已验证(它们是null):

Output JSON:

输出 JSON:

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

I expect that they are validated/returned after the save operation.

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

To workaround this, I have to inject/declare the EntityManagerin the REST controller, and call the method EntityManager.refresh(something)(or I have to call a .findOne(something.getId())method to have the complete persisted entity):

要解决此问题,我必须EntityManager在 REST 控制器中注入/声明,并调用该方法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;
    }
}

With this workaround, I've the expected saved entith (with a correct JSON):

通过这种解决方法,我得到了预期的保存内容(使用正确的 JSON):

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

Is there an automatic method/annotation, with JPA or Spring or Hibernate, in order to have the "complete" persisted entity?

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

I would like to avoid to declare the EntityManager in every REST or Service class, or I want avoid to call the .findOne(Long) method everytime I want the new refreshed persisted entity.

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

Thanks a lot, Andrea

非常感谢,安德里亚

回答by Harish Barma

By the time you persist the entity it will be in managed state so if you just call something.getProperty();it loads from the database and fills the propertyvalue of the somethingentity

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

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

so normally when you have many-to-one relationship that should be fetched automatically. If not calling the getters of the objects in entity will fill them too by firing a new DB Find request.

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

回答by Vlad Mihalcea

That's not enough:

这还不够:

Something result = somethingRepository.save(something);

You need to manually merge the incoming entity:

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

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

somethingRepository.save(dbSomething);

Since the propertyattribute is using the default FetchType.EAGER, the entity should have the propertyattribute initialized.

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

But, that's strange to call the Repository twice from the REST controller. You should have a Service layer that does all that in a @Transactionalservice method. That way, you don't need to resave the entity since it's already managed.

但是,从 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;
}

Now, you need to carefully merge every property you sent. In your case, if you send nullfor propertyyou should decide whether you should nullify the @ManyToOnereference or not. So, it depends on your current application business logic requirements.

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

Update

更新

If you make sure you always send back the same entity you previously fetched, you could just use merge.

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

em.merge(result);

But your propertyattribute is just an id, and not an actual child entity, so you have to resolve that yourself in the Service layer.

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

回答by Sahil Chhabra

Instead of defining EntityManagerin each of your resource, you can define it once by creating a CustomJpaRepository. Reference

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

Then use the refreshof your EntityManagerin each of your repository directly.

然后使用refresh你的EntityManager直接在每个仓库的。

Refer the below example:

请参考以下示例:

CustomRepository Interface

自定义存储库接口

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 Implementation

自定义存储库实现

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);
  }
}

Enable Custom JPARepository in Spring Boot Application Class

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

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

Your Something Repository

你的东西存储库

public interface SomethingRepository extends CustomRepository<Something, Long> {

}

Use Refresh directly in SomethingResource(Assuming Something is an Entity)

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

@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;
    }
}

回答by Narasimha A

In Spring Boot JpaRepository:

在 Spring Boot JpaRepository 中:

If our modifying query changes entities contained in the persistence context, then this context becomes outdated.

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

In order to fetch the entities from the database with latest record.

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

Use @Modifying(clearAutomatically = true)

使用@Modifying(clearAutomatically = true)

@Modifying annotation has clearAutomatically attribute which defines whether it should clear the underlying persistence context after executing the modifying query.

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

Example:

例子:

@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);