如何使用JpaRepository有效地更新

时间:2019-02-11 05:52:37

标签: spring postgresql spring-data-jpa sql-update

我有一个家长,其中存储了孩子的清单。当我更新子项(添加/编辑/删除)时,是否有一种方法可以根据外键自动确定要删除或编辑的子项?还是我必须手动检查所有孩子以查看哪些是新的或修改的?

父母阶级

@Entity
@EntityListeners(PermitEntityListener.class)
public class Permit extends Identifiable {

    @OneToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL, mappedBy = "permit")
    private List<Coordinate> coordinates;
}

儿童班

@Entity
public class Coordinate extends Identifiable {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "permit_id", referencedColumnName = "id")
    private Permit permit;

    private double lat;

    private double lon;
}

父母的控制人

@PutMapping("")
public @ResponseBody ResponseEntity<?> update(@RequestBody Permit permit) {

    logger.debug("update() with body {} of id {}", permit, permit.getId());
    if (!repository.findById(permit.getId()).isPresent()) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
    }

    Permit returnedEntity = repository.save(permit);
    repository.flush();
    return ResponseEntity.ok(returnedEntity);
}

= EDIT =

创建控制器

@Override
    @PostMapping("")
    public @ResponseBody ResponseEntity<?> create(@RequestBody Permit permit) {

        logger.debug("create() with body {}", permit);
        if (permit == null || permit.getId() != null) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
        }

        List<Coordinate> coordinates = permit.getCoordinates();
        if (coordinates != null) {
            for (int x = 0; x < coordinates.size(); ++x) {
                Coordinate coordinate = coordinates.get(x);
                coordinate.setPermit(permit);
            }
        }

        Permit returnedEntity = repository.save(permit);
        repository.flush();
        return ResponseEntity.ok(returnedEntity);
    }

控制器更新

@PutMapping("")
public @ResponseBody ResponseEntity<?> update(@RequestBody Permit permit) {

    logger.debug("update() with body {} of id {}", permit, permit.getId());
    if (!repository.findById(permit.getId()).isPresent()) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
    }

    List<Coordinate> repoCoordinate = coordinateRepository.findByPermitId(permit.getId());
    List<Long> coordinateIds = new ArrayList<Long>();
    for (Coordinate coordinate : permit.getCoordinates()) {
        coordinate.setPermit(permit);
        //if existing coordinate, save the ID in coordinateIds
        if (coordinate.getId() != null) {
            coordinateIds.add(coordinate.getId());
        }
    }
    //loop through coordinate in repository to find which coordinate to remove
    for (Coordinate coordinate : repoCoordinate) {
        if (!(coordinateIds.contains(coordinate.getId()))) {
            coordinateRepository.deleteById(coordinate.getId());
        }
    }

    Permit returnedEntity = repository.save(permit);
    repository.flush();
    return ResponseEntity.ok(returnedEntity);
}

我已经测试过并且可以正常工作,没有简化的方法吗?

2 个答案:

答案 0 :(得分:2)

您已接近解决方案。您唯一缺少的是一对多映射上的orphanRemoval = true:

@Entity
@EntityListeners(PermitEntityListener.class)
public class Permit extends Identifiable {

    @OneToMany(mappedBy = "permit", cascade=CascadeType.ALL, orphanRemoval=true)
    private List<Coordinate> coordinates;

}

对映射进行标记以删除孤立对象将告诉基础ORM删除不再属于任何父实体的任何实体。由于已从列表中删除了子元素,因此在保存父元素时将删除该子元素。 创建新元素并更新旧元素是基于CascadeType的。由于具有CascadeType.ALL,因此在保存父实体时,列表中所有没有ID的元素都将保存到数据库中并分配新的ID,并且列表中已经存在并具有ID的所有元素都将被更新。 / p>

另一方面,您可能需要更新setter方法以使List坐标看起来像这样:

public void setCoordinates(List<Coordinates> coordinates) {
    this.coordinates = coordinates;
    this.coordinates.forEach(coordinate -> coordinates.setPermit(this));
}

或者,如果要使用JSON,则只需使用@JsonManagedReference和@JsonBackReference。

答案 1 :(得分:0)

  

我有一个家长,其中存储了孩子的清单。

让我们为其编写DDL。

TABLE parent (
  id integer pk
)
TABLE child(
  id integer pk
  parent_id integer FOREIGN KEY (parent.id)
)
  

当我更新子项(添加/编辑/删除)时,是否可以根据外键自动确定要删除或编辑的子项?

假设您有一个新的5号孩子绑定到2号父母,并且:

  1. DDL中的FK正确
  2. 实体知道FK
  3. 您使用的是相同的jpa上下文
  4. 交易正确执行

然后,每次对parent.getChilds()的调用都必须(!)返回在执行交易之前存在的所有实体 ,以及您刚刚提交给该实体的同一实体实例数据库。

然后,如果您删除父#2的子#5,并且交易成功执行parent.getChilds(),则必须返回所有实体没有子#5。

特殊情况:

如果删除父级#2,并且在DDL和Java代码中都具有级联删除,则所有子级必须从数据库中删除,而父级#2也必须从刚刚删除的数据库中删除。在这种情况下,父级2不再绑定到jpa上下文,父级2的所有子代也不再绑定到jpa上下文。

=编辑=

您可以使用merge。这将适用于以下结构:

POST {
  "coordinates": [{
    "lat":"51.33",
    "lon":"22.44"
  },{
    "lat":"50.22",
    "lon":"22.33"
  }]
}

它将在表“ permit”中创建一行,并在表“ coordinate”中创建两行,这两个坐标都绑定到许可行。结果将包括设置的ID。

但是:您将必须进行验证工作(检查id是否为null,检查坐标是否未引用其他许可,...)!

必须使用DELETE方法删除坐标:

DELETE /permit/972/coordinate/3826648305