Spring + JPA @OneToMany with orphanRemoval

2019-08-08 05:53发布

问题:

I'm having a little (big) problem with Spring list form binding, and orphanRemoval. This exception occurs only when updating some item - insert and delete does work.

"A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: me.gerenciar.model.entity.PedidoItem.filhos"

Well, I have a form, and in this form, there are some items (children) that is dynamically inserted/deleted/updated on front-end with javascript.

I've made two other forms like this one, and they works perfectly, the only difference is that in this one, we have 3 hierarchy level, and the others were just 1 hierarchy level.

I know all that stuff that we can not set new dictionary like this: "this.children = children;" but this is made by Spring by reflection when binding the form entity. And as I had said, it did work on 2 other cases.

Here are my entities (without getters and setters).

//BaseEntity is just a generic way to override equals, toString and hashCode

@Entity
@Table(name = "PEDIDO")
public class Pedido extends BaseEntity
{
    private static final long serialVersionUID = 1586104653460442257L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID_PEDIDO")
    private Integer pedidoId;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_PESSOA_ESTABELECIMENTO")
    private Estabelecimento estabelecimento;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_PESSOA_CLIENTE")
    private Cliente cliente;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumns({ @JoinColumn(name = "ID_MESA", referencedColumnName = "ID_MESA", insertable = false, updatable = false), @JoinColumn(name = "ID_PESSOA_ESTABELECIMENTO", referencedColumnName = "ID_PESSOA", insertable = false, updatable = false) })
    private Mesa mesa;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_TURNO")
    private Turno turno;

    @DateTimeFormat(iso = ISO.DATE_TIME)
    @Column(name = "DATA")
    private Date data;

    @Column(name = "DATA", updatable = false, insertable = false)
    private String rawData;

    @Column(name = "PRECO")
    private BigDecimal preco;

    @Column(name = "FINALIZADO")
    private Boolean finalizado;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "pedido", orphanRemoval = true)
    @OrderBy("ID_PEDIDO_ITEM_GRUPO DESC")
    private List<PedidoItemGrupo> pedidoItemGrupos;

    @Column(name = "DATA_ANO")
    private Integer dataAno;

    @Column(name = "DATA_MES")
    private Integer dataMes;

    @Column(name = "DATA_DIA")
    private Integer dataDia;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_CHEQUE")
    private Cheque cheque;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    @JoinTable(name = "PEDIDO_CARTAO", joinColumns = { @JoinColumn(name = "ID_PEDIDO", referencedColumnName = "ID_PEDIDO") }, inverseJoinColumns = { @JoinColumn(name = "ID_CARTAO", referencedColumnName = "ID_CARTAO") })
    private List<Cartao> cartoes;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_DINHEIRO")
    private Dinheiro dinheiro;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_BOLETO")
    private Boleto boleto;
}

@Entity
@Table(name = "PEDIDO_ITEM_GRUPO")
public class PedidoItemGrupo extends BaseEntity
{
    private static final long serialVersionUID = 7785627059444833691L;

    public static enum Tipo
    {
        DIVIDIDO, SOMADO
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID_PEDIDO_ITEM_GRUPO")
    private Integer pedidoItemGrupoId;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_PEDIDO")
    private Pedido pedido;

    @Column(name = "QUANTIDADE")
    private BigDecimal quantidade;

    @Column(name = "PRECO_UNITARIO")
    private BigDecimal precoUnitario;

    @Column(name = "PRECO")
    private BigDecimal preco;

    @Column(name = "DESCONTO")
    private BigDecimal desconto;

    @Column(name = "PRECO_FINAL")
    private BigDecimal precoFinal;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "pedidoItemGrupo", orphanRemoval = true)
    @Where(clause = "EXISTS (SELECT * FROM PEDIDO_ITEM WHERE ID_PEDIDO_ITEM_PAI IS NULL)")
    private List<PedidoItem> pedidoItens;
}

@Entity
@Table(name = "PEDIDO_ITEM")
public class PedidoItem extends BaseEntity
{
    private static final long serialVersionUID = 5296905009119022656L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID_PEDIDO_ITEM")
    private Integer pedidoItemId;

    @JsonIgnore
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_PEDIDO_ITEM_PAI")
    private PedidoItem pai;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "pai", fetch = FetchType.EAGER, orphanRemoval = true)
    private List<PedidoItem> filhos;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_PRODUTO", insertable = false, updatable = false)
    private Produto produto;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_PRODUTO_CATEGORIA")
    private ProdutoCategoria produtoCategoria;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumns({ @JoinColumn(name = "ID_PRODUTO", referencedColumnName = "ID_PRODUTO"), @JoinColumn(name = "TAMANHO", referencedColumnName = "TAMANHO") })
    private ProdutoTamanho produtoTamanho;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ID_PEDIDO_ITEM_GRUPO")
    private PedidoItemGrupo pedidoItemGrupo;

    @Column(name = "QUANTIDADE")
    private BigDecimal quantidade;

    @Column(name = "PRECO_UNITARIO")
    private BigDecimal precoUnitario;

    @Column(name = "PRECO")
    private BigDecimal preco;

    @Column(name = "DESCONTO")
    private BigDecimal desconto;

    @Column(name = "PRECO_TOTAL_UNITARIO")
    private BigDecimal precoTotalUnitario;

    @Column(name = "PRECO_TOTAL")
    private BigDecimal precoTotal;

    @Column(name = "PRECO_TOTAL_FINAL")
    private BigDecimal precoTotalFinal;
}

If you guys need more details I'll immediately post it. thank you so much!


I have tried this also: http://mcls.github.io/blog/2012/08/07/pojo-binding-and-jpas-orphanremoval-in-play/

no succeed =(, got null pointer exception when spring try to bind List filhos

回答1:

So, im gonna answer my own question.

For you guys, facing the same problem with POJO binding to hibernate entity.

The solution is to pre-instanciate all your collections, and replace your normal set method for this one

class Child {
    private List<Child> children = new ArrayList<>();

    public setChildren(List<Child> children)
    {
        this.children.clear();

        if (children != null) {
          this.children.addAll(children);
        }
    }
}

This way, you gonna kill all the other children... Of course this is a specific setter method, and you could do a generic way with Reflection, and just "update" the needed children, and "remove" the others.

So, my final version is like this:

class Child extends BaseEntity {
    private List<Child> children = new ArrayList<>();

    public setChildren(List<Child> children)
    {
        //this is the magic method, doing with Reflection on BaseEntity
        setList(this.children, children);
    }
}