简体   繁体   中英

spring data jpa: after update @many side table, can't get correct changes by findAll @one side table in the same method

I have a procedure entity, and a procedure contains a list of cards entity. Any card can be moved between procedures. So now if I do a move (actually setProcedure of a card to another procedure), and immediately findAll() at the next line, it returns [] for both procedures.

tried using saveAndFlush() instead of save() before findAll() but doesn't work too.

entities:

@Entity
@Table(name = "procedures")
public class Procedure {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @OneToMany(mappedBy = "procedure", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JsonManagedReference
    private List<Card> cards = new ArrayList<>();
//...getter setter constructor
}
@Entity
@Table(name = "cards")
public class Card {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @ManyToOne
    @JsonBackReference
    private Procedure procedure;
//...getter setter constructor
}

controller

    @GetMapping("/card/move/{cid}/{nextPid}")
    @Transactional
    public List<Procedure> moveCard(@PathVariable Long cid, @PathVariable Long nextPid) {
        return cardService.moveCard(cid, nextPid);
    }

inside service

    List<Procedure> moveCard(Long cid, Long nextPid) {
        Card card = cardRepository.findById(cid).get();
        Procedure toProcedure = procedureService.findById(nextPid);

        card.setIndex(toProcedure.getCards().size());
        card.setProcedure(toProcedure);

        cardRepository.saveAndFlush(card);

        return procedureService.findAll();
    }

eg if I have 1 card in procedure 1, after I call this api, it returns the following:

[
    {
        "id": 1,
        "cards": [],
    },
    {
        "id": 2,
        "cards": [],
    }
]

but I expect:

[
    {
        "id": 1,
        "cards": [],
    },
    {
        "id": 2,
        "cards": [{id: 123}],
    }
]

You have to maintain the other side of the relationship too.

Your code must look like:

List<Procedure> moveCard(Long cid, Long nextPid) {
    Card card = cardRepository.findById(cid).get();

    // Remove the card from the procedure
    card.getProcedure().getCards().remove(card);

    Procedure toProcedure = procedureService.findById(nextPid);
    // Add it to the new procedure
    toProcedure.getCards().add(card);

    card.setIndex(toProcedure.getCards().size());
    card.setProcedure(toProcedure);

    cardRepository.saveAndFlush(card);

    return procedureService.findAll();
}

As hinted at elsewhere where a relationship is bidirectional then it is up to you to maintain both sides.

You should so this by encapsulating the operations to modify the collection in order to guarantee that the in-memory model is always consistent.

public class Procedure{

    public Set<Card> cards; // or list

    public void addCard(Card Card){
        card.getProcedure().removeCard(card);
        this.cards.add(card);
        card.setProcedure(this);
    {

    public void removeCard(Card card){
        cards.remove(card);
        card.setProedure(null);
    }

    public Set<Card> getCards(){
        //force client code to use add/remove operations
        //to ensure in-memory model **always** consistent
        return Collections.unmodifiableSet(cards); // or list
    }
}

Think in terms of aggregate roots ( https://www.baeldung.com/spring-persisting-ddd-aggregates ) and work with Procedure:

void moveCard(Long cid, Long nextPid) {
    Card card = cardRepository.findById(cid).get();
    Procedure fromProcedure = card.getProcedure();
    Procedure toProcedure = procedureService.findById(nextPid);

    formProcedure.removeCard(card);
    toProcedure.addcard(card);

    card.setIndex(toProcedure.getCards().size()); ??

    //if method is executed in transaction then no need to call saveAndFlush
    //the following 2 lines can be removed.
    cardRepository.saveAndFlush(fromProcedure);
    cardRepository.saveAndFlush(toProcedure);
}

This is what worked for me, the only change was removing and adding in the procedures from/to and you don't need to flush the changes straight as well:

  @Transactional
  List<Procedure> moveCard(Long cid, Long nextPid) {
    Card card = cardRepository.findById(cid).get();
    Procedure toProcedure = procedureService.findById(nextPid);
    Procedure fromProcedure = card.getProcedure();

    //this keeps the object in memory updated and returned by findAll after
    fromProcedure.getCards().remove(card);
    toProcedure.getCards().add(card);

    card.setIndex(toProcedure.getCards().size());
    card.setProcedure(toProcedure);

    cardRepository.save(card);

    return procedureService.findAll();
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM