简体   繁体   中英

How to join a table with one column of a view in that table's entity class

Scenario:

  • I have a products table with these fields: id, code, description, photo.
  • I have a product_stock view from other schema with fields: prod_code, stok_tot

Misson: What I need to do in Spring Boot is to join the products with their total stock.

Rules:

  • I can't use @Query on this task, I need to join them at the Product Entity Class , so that stock became some kind of Transient field of product.
  • I can't change the fact that product's ID is a Long and ProductStock's ID is a String , but I could use product's code field instead right? (how?)

So far... I tryed to use @OneToOne and @JoinColumn to do the job, but my REST gives me the stock field as NULL .

"Estoque.java"

@Entity
@Table(name = "VW_ESTOQUE", schema = "ASICAT")
public class Estoque {

    @Id
    @Column(name = "CD_BEM_SERVICO", unique = true, nullable = false)
    private String codigo;

    @Column(name = "ESTOQUE")
    private Long estoque;

    // getters and setters hidden by me
}

"Produto.java"

@Entity
@NamedEntityGraph(name = "Produto.detail", attributeNodes = @NamedAttributeNode("categorias"))
public class Produto implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    private Long id;

    private String codigo;

    private String descricao;

    // here is where I get the null values
    @Transient
    @OneToOne(fetch = FetchType.LAZY)
    @JoinTable(name = "VW_ESTOQUE", joinColumns = @JoinColumn(name = "CODIGO", referencedColumnName = "CODIGO"), inverseJoinColumns = @JoinColumn(name = "CD_BEM_SERVICO", referencedColumnName = "CODIGO"))
    private Estoque estoque;

    private String hash;

    @ManyToMany(mappedBy = "produtos", fetch = FetchType.EAGER)
    @BatchSize(size = 10)
    private List<Categoria> categorias = new ArrayList<>();

    // getters and setters hidden by me

}

In my product repository I call FindAll()

You have annotated Produto.estoque as @Transient , which means that it is not part of the persistent state of the entity. Such a field will be neither written nor read when instances of that entity are managed. That's not going to serve your purpose.

There are two things I can imagine you might have been trying to achieve:

  1. That every time an Estoque is accessed via a Produto , it should be loaded from the DB to ensure its freshness. JPA does not provide for that, though you might want to annotate Estoque with @Cacheable(value = false) , and specify the lazy fetch strategy on the Produto side of the relationship.

  2. You want to avoid the persistence provider attempting to persist any changes to an Estoque , since it is backed by a view, not an updatable table. This we can address.

My first suggestion would be to map ASICAT.VW_ESTOQUE as a secondary table instead of an entirely separate entity. That might look something like this:

@Entity
@SecondaryTable(name = "VW_ESTOQUE", schema = "ASICAT"
        pkJoinColumns = {
            @PrimaryKeyJoinColumn(name = "CD_BEM_SERVICO",
                    referencedColumnName = "CODIGO") })
public class Produto implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    private Long id;

    private String codigo;

    private String descricao;

    @Column(name = "ESTOQUE", table = "VW_ESTOQUE", nullable = true,
            insertable = false, updatable = false)
    private Long estoque;

    // ...
}

You might furthermore avoid providing a setter for the estoque property.

But the SecondaryTable approach might not work well if you cannot rely on the ESTOQUE view always to provide a row for every row of PRODUTO , as there will very likely be an inner join involved in retrievals. Moreover, you don't get lazy fetches this way. The main alternative is more or less what you present in your question: to set up a separate Estoque entity.

If you do set up a separate Estoque , however, then I would approach it a bit differently. Specifically,

  • I would make the relationship bidirectional, so that I could
  • make the Estoque entity the relationship owner.

Something like this, then:

@Entity
public class Produto implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    private Long id;

    private String codigo;

    private String descricao;

    // must not be @Transient:
    @OneToOne(fetch = FetchType.LAZY, mappedBy = "produto", cascade = {
            CascadeType.REFRESH
        })
    private Estoque estoque;

    // ...    
}

@Entity
@Table(name = "VW_ESTOQUE", schema = "ASICAT")
@Cacheable(value = false)
public class Estoque {

    @Id
    @Column(name = "CD_BEM_SERVICO", nullable = false,
            insertable = false, updatable = false)
    private String codigo;

    @Column(name = "ESTOQUE", insertable = false, updatable = false)
    private Long estoque;

    @OneToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "CD_BEM_SERVICO", referencedColumnName = "CODIGO",
        nullable = false, insertable = false, updatable = false, unique = true)
    Produto produto;

    // getters and setters hidden by me
}

In this case, I would avoid providing setter methods for any of the properties of Estoque , and avoid providing any constructor that allows initial property values to be set. Thus, to a first approximation, the only way an instance's properties will take non-null values is if they are set by the persistence provider.

Additionally, since you mention Oracle, if you are using TopLink as your persistence provider then you might consider applying its @ReadOnly extension attribute to the Estoque entity, in place of or even in addition to some of these protections against trying to insert into or update the view.

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