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

And*_*qua 26 java persistence hibernate jpa spring-data-jpa

我有这两个简单的实体SomethingProperty.该Something实体与多对一关系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() +
            "}";
    }
Run Code Online (Sandbox Code Playgroud)

属性:

@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() + "'" +
            "}";
    }
}
Run Code Online (Sandbox Code Playgroud)

这是SomethingRepository(春天):

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

}
Run Code Online (Sandbox Code Playgroud)

通过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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

{
  "name": "MyName",
  "owner": "MySelf",
  "property": {
    "id": 1
  }
Run Code Online (Sandbox Code Playgroud)

}

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

输出JSON:

{
  "id": 1,
  "name": "MyName",
  "owner": "MySelf",
  "property": {
    "id": 1,
    "shape": null,
    "color": null,
    "dimension": null
  }
}
Run Code Online (Sandbox Code Playgroud)

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

要解决这个问题,我必须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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

{
  "id": 4,
  "name": "MyName",
  "owner": "MySelf",
  "property": {
    "id": 1,
    "shape": "Rectangle",
    "color": "Red",
    "dimension": 50
  }
}
Run Code Online (Sandbox Code Playgroud)

是否有自动方法/注释,使用JPA或Spring或Hibernate,以获得"完整"持久化实体?

我想,以避免申报的EntityManager在每一个REST或服务类,或者我想避免调用.findOne(long)方法,每次我希望新的刷新坚持实体.

非常感谢,安德里亚

Sah*_*bra 22

您可以通过创建Custom JpaRepository 来定义它,而不是EntityManager在每个资源中定义.参考

然后直接在refreshEntityManager的每个存储库中使用您的.

请参考以下示例:

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);
}
Run Code Online (Sandbox Code Playgroud)

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

在Spring Boot Application Class中启用自定义JPARepository

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

你的东西存储库

public interface SomethingRepository extends CustomRepository<Something, Long> {

}
Run Code Online (Sandbox Code Playgroud)

直接在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 干净,清晰,可重复使用。这应该是公认的答案。 (5认同)
  • 对我来说,它抛出异常:没有找到类型“Something”的属性刷新,你能解释一下为什么吗 (2认同)

Vla*_*cea 9

这还不够:

Something result = somethingRepository.save(something);
Run Code Online (Sandbox Code Playgroud)

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

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

somethingRepository.save(dbSomething);
Run Code Online (Sandbox Code Playgroud)

由于property属性使用的是 default 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;
}
Run Code Online (Sandbox Code Playgroud)

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

更新

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

em.merge(result);
Run Code Online (Sandbox Code Playgroud)

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


Nar*_*a A 6

在 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);
Run Code Online (Sandbox Code Playgroud)

  • 那没有用。我遇到的情况是,数据库触发器插入后某些列被修改。然而,保存后这些列在我的实体中似乎为空。我想扩展 EntityManager 并调用刷新方法是解决这个问题的唯一方法。 (4认同)

Har*_*rma -1

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

public Something save(Something something) {
    em.persist(something);
    something.getProperty();
    return something;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 尝试在代码的各个点调用 .getProperty() 方法;我也在一些教程中看到过这一点,但就我而言,没有任何反应,它不起作用。很奇怪。我正在尝试使用 .getProperty() 找到一些解决方案(还使用一些注释,如 PostLoad、PostPersist、JsonGetter 和 LAZY/EAGER fetch)。 (2认同)