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

v6ylcynt  于 2022-11-14  发布在  Spring
关注(0)|答案(5)|浏览(178)

我有两个简单的实体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(Spring):

@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(propertyid 1是数据库中的现有行):

{
  "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;
    }
}

使用此解决方案,我得到了预期的保存实体(使用正确的JSON):

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

是否有JPA、Spring或Hibernate的自动方法/注解,以便拥有“完整”的持久化实体?
我希望避免在每个REST或Service类中声明EntityManager,或者希望避免在每次需要新的刷新持久化实体时调用.findOne(Long)方法。

fwzugrvs

fwzugrvs1#

您可以通过创建一个CustomJpaRepository一次性定义EntityManager,而不是在每个resource中定义EntityManager。参考
然后直接在每个存储库中使用EntityManagerrefresh
请参考以下示例:

自定义存储库接口

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

自定义存储库实施

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

在Sping 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;
    }
}
c9qzyr3d

c9qzyr3d2#

这还不够:

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控制器调用Repository两次是很奇怪的。你应该有一个服务层,它在@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;
}

现在,您需要小心地合并发送的每个属性。在您的示例中,如果您发送null来替换property,则应该决定是否应该使@ManyToOne引用无效。因此,这取决于您当前的应用程序业务逻辑需求。

更新

如果您确保总是返回先前获取的相同实体,则可以使用merge

em.merge(result);

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

cwdobuhd

cwdobuhd3#

在Sping Boot JpaRepository中:
如果我们的修改查询更改了持久性上下文中包含的实体,则此上下文将过时。
以便从数据库中提取具有最新记录的实体。
修改时使用(自动清除= 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);
beq87vna

beq87vna4#

我将用使用session.load生成的代理替换该属性

something.setProperty(session.load(Property.class, something.getProperty().getId()))
        Something result = somethingRepository.save(something);
        return result;

现在,结果将从数据库中加载整个属性对象

hfyxw5xn

hfyxw5xn5#

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

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

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

相关问题