How to map master detail on hibernate? - java

I can not change the db schema and this is what I got so far:
public class User{
#Id
private String userId;
#OneToMany
#JoinTable(
name = "user_invoice",
joinColumns = #JoinColumn(name="user_id"),
inverseJoinColumns = #JoinColumn(name = "invoice_id")
)
private List<InvoiceItem> invoiceItems;
}
public class InvoiceItem{
#Id
private String invoiceId;
private String invoiceItemId;
}
This configuration does not allow invoice_id to be duplicated on invoice_item table(it should since I can have multiple items on a given invoice)
If I make invoice_item_id composite pk I would need to add an extra column on user_invoice table which I can not.
How can I map this?

You could split up the many-to-many association into two one-to-many associations and an entity for the join table. You can map it like this:
public class User{
#Id
private String userId;
#OneToMany(mappedBy = "user")
private List<Invoice> invoices;
}
#Table(name = "user_invoice")
public class Invoice{
#Id
#ManyToOne(fetch = LAZY)
#JoinColumn(name="user_id")
private User user;
#Id
private String invoiceId;
#OneToMany(mappedBy = "invoice")
private List<InvoiceItem> invoiceItems;
}
public class InvoiceItem{
#Id
#ManyToOne(fetch = LAZY)
private Invoice invoice;
#Id
private String invoiceItemId;
}

Related

How to audit a #JoinTable with #ManyToMany

I'm working on a Spring-Boot project with a H2 database. I have two entities Portfolio and Report, and there is a many-to-many association between the two.
I want those entities to be audited, so I followed this tutorial to audit through an AuditorAware interface with custom fields.
The two entities are well audited, the columns are created in the database. However, the join table portfolio_reports is not audited. How can I audit the join table as well ?
Portfolio.java
#Entity
#Table(name = "portfolio")
public class Portfolio extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
#Unique
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "portfolio_report", joinColumns = #JoinColumn(name = "portfolio_id"), inverseJoinColumns = #JoinColumn(name = "report_id"))
private List<Report> reports;
// Getters and setters
}
Report.java
#Entity
#Table(name = "report")
public class Report extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "axioma_id")
private Long axiomaId;
#Column(name = "name")
private String name;
#AuditJoinTable
#ManyToMany(mappedBy = "reports", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
private List<Portfolio> portfolios;
// Getters and setters
}
Auditable.java
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {
#Version
#Column(name = "version_no")
protected Long versionNo;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date")
protected Date createdDate;
#LastModifiedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "modified_date")
protected Date modifiedDate;
}
AuditorAwareImpl.java
public class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Admin");
}
}
PersistenceConfiguration.java
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class PersistenceConfiguration {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditorAwareImpl();
}
}
Problem:
Clearly here Auditable should add some column to your intermediate table that maintains relation between Portfolio and Report and that table is created behind the scene and you don't have access to that table in your program. Only hibernate can use that table to maintain relation between your entities and do join operation.
Solution:
Here you should make Join table that maintain Many to Many relation between Portfolio and Report explicit so that you can have entity like PortfolioReport in your program that can extends from Auditable. Please read the following post to see how to do that: The best way to map a many-to-many association with extra columns when using JPA and Hibernate

Java JPA how relate an entity instance with all instances of another entity?

I work with an embedded H2 database in which I use the #OneToMany relationship to relate an entity instance (product) to multiple instances of the other entities (suppliers); it's useful when I have specific suppliers for a particular product.
However now, I want to associate all the suppliers with every single product; I don't want to generate in my supplier table different supplier records for each product, instead I want to have only 5 records (5 suppliers) in my supplier table which are associated to every single product, it few words I want to achieve something like "one to all", is it possible to do it using JPA annotations?
Product entity
#Entity
public class Product {
#Id
private String productCode;
#OneToMany
#JoinColumn(name = "supplier_id", referencedColumnName = "productCode")
private List<Supplier> suppliers;
}
Supplier entity
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
private String name;
}
Unidirectional #OneToMany association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) // according to your need
private List<Supplier> suppliers;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
...
}
#ManyToOne association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "product_id", foreignKey = #ForeignKey(name = "PRODUCT_ID_FK"))
private Product product;
private String name;
...
}

JPA annotation for parent composite key to be part of child composite primary key

I am trying to map a race day that has multiple races, and can't figure out the annotations.
What I want to end up with is something like (this is a simplified example):
TABLE: race_day
date (PK)
venue (PK)
description
TABLE: race
date (PK but also FK on race_day table)
venue (PK but also FK on race_day table)
race_number (PK)
description
So far I've come up with the following POJO + annotations:
public class RaceDayPK implements Serializable {
#Column(name="race_date")
private String raceDate;
#Column(name="venue")
private String venue;
...
}
public class RaceDay {
#EmbeddedId
private RaceDayPK raceDayPk;
#OneToMany(mappedBy = "raceDay", orphanRemoval = true, cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
private final List<Race> races = new ArrayList<>();
...
}
public class Race {
#ManyToOne
#JoinColumns({
#JoinColumn(name = "race_date", referencedColumnName = "race_date"),
#JoinColumn(name = "venue", referencedColumnName = "venue")
})
private RaceDay raceDay;
private int raceNumber;
private String raceTitle;
...
}
How do I make an EmbeddedId for Race (I'm guessing that's what I need) with both the raceDay join column AND raceNumber in it? Any help appreciated. I have seen dozens of JoinColumn examples and EmbeddedId examples here on StackOverflow but none that seem to combine the two in the way that I need.
After some hacking around, I think I got it working like this. Note the nested "racePk.raceMeeting" in the mappedBy parameter for the OneToMany annotation:
public class RaceDayPK implements Serializable {
#Column(name="race_date")
private String raceDate;
#Column(name="venue")
private String venue;
...
}
public class RaceDay {
#EmbeddedId
private RaceDayPK raceDayPk;
#OneToMany(mappedBy = "racePk.raceDay", orphanRemoval = true, cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
private final List<Race> races = new ArrayList<>();
...
}
public class RacePk implements Serializable {
#ManyToOne
#JoinColumns({
#JoinColumn(name = "race_date", referencedColumnName = "race_date"),
#JoinColumn(name = "venue", referencedColumnName = "venue")
})
private RaceDay raceDay;
#Column(name = "race_number")
private int raceNumber;
...
}
public class Race {
#EmbeddedId
private final RacePk racePk = new RacePk();
private String raceTitle;
...
}
No idea yet whether this will fall over at some point but the MySQL tables appear to have been auto-generated correctly.
TABLE: race_day
id(PK)
date (PK)
venue (PK)
description
TABLE: race
id(PK but also FK on race_day table)
date (PK but also FK on race_day table)
venue (PK but also FK on race_day table)
race_number (PK)
description
For the above given tables, I was able to generate the sequence and get the data persisted in the tables following the constraints using the below code.
public class RaceDayPK implements Serializable {
private Long id;
private String raceDate;
private String venue;
...
}
#Entity
#IdClass(RaceDayPK.class)
public class RaceDay {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator = "R_SEQ")
#SequenceGenerator(sequenceName = "RD_SEQ", allocationSize = 1, name = "R_SEQ")
private Long id;
#Id
#Column(name="race_date")
private String raceDate;
#Id
#Column(name="venue")
private String venue;
#OneToMany(mappedBy = "raceDay", cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
private Set<Race> races;
...
}
public class RacePK implements Serializable {
private RaceDay raceDay;
private int raceNumber;
...
}
#Entity
#IdClass(RacePK.class)
public class Race {
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "race_date", referencedColumnName = "race_date"),
#JoinColumn(name = "venue", referencedColumnName = "venue"),
#JoinColumn(name = "id", referencedColumnName = "id")
})
#JsonIgnore
private RaceDay raceDay;
#Id
private int raceNumber;
private String raceTitle;
...
}
the dao code needs to be changed a bit as to below.
public RaceDay saveRaceDay(RaceDay raceDay){
if(raceDay.getRaces()!=null){
raceDay.getRaces().forEach(race ->{
race.setRaceDay(raceDay);
});
}
}
Not sure if it works but try this approach:
public class RacePK implements Serializable {
private RaceDayPK raceDay;
private String raceNumber;
...
}
#Entity
#IdClass(RacePK.class)
public class Race {
#Id
#ManyToOne...
private RaceDay raceDay;
#Id
private int raceNumber;
private String raceTitle;
...
}
Note that PK field names should be exactly the same as #id-annotaed field names.
Google for Derived identifiers to learn more.

#OneToOne relationship with additional constraint

Suppose, we have two entities, first one:
#Entity
#Table(name = "entitya")
public class EntityA {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private Long name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<EntityB> childEntities;
}
and the second:
#Entity
#Table(name = "entityb")
public class EntityB {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "master")
private Boolean master;
#ManyToOne
#JoinColumn(name = "parent")
private EntityA parent;
}
So far, so good. However underlying database tables and constrains enforce that for any entityA there can be only one EntityB with boolean field master set to true. I can extract it by adding following method to entityA:
public entityB getMasterChild() {
for(entityB ent : childEntities) {
if(ent.isMaster()) {
return ent;
}
}
}
The question is, can I create #OneToOne relationship in EntityA that can express that rule, so that entityA can have additional masterChild member of type entityB?
If I understood you correctly you want to create/define a relationship between two entities based on a value of some entity's property. The think is that relationship between entities is defined on entities count (how many entities can has the other entity) and not on some entity's property value.
However
If you really want to use #OneToOne mapping for masterChild I would recommend creating a separate table/entity for it. Once this is done, you can include this new MasterChild entity into EntityA and annotate it with #OneToOne.
Here is new MasterChild entity
#Entity
public class MasterChild extends EntityB{
#Id
#Column(name = "id")
private Long id;
}
Note that I have removed 'master' from EntityB as it is no longer needed
#Entity
#Table(name = "entityb")
public class EntityB {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "parent")
private EntityA parent;
}
And here is modified EntityA
#Entity
#Table(name = "entitya")
public class EntityA {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private Long name;
#OneToOne
private MasterChild master;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<EntityB> childEntities;
}

Hibernate creating relation on #JoinTable

I have two tables which have Many-to-Many relations which have a JoinTable USER_SERVICES as below.
#Entity
public class User implements Serializable {
#NotNull
#Column(unique=true)
private String username;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "USER_SERVICES",
joinColumns = {#JoinColumn(name = "id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "", referencedColumnName = "name")})
private Set<Services> services;
// Getters and Setters
}
#Entity
public class Services implements Serializable {
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long serviceId;
#NotNull
#Column(unique=true)
private String name;
//Getters and Setters
}
The above code creates a table USER_SERVICES, but I also want to have a Many-to-Many relation on the table USER_SERVICES with another table RATINGS which would result in another table USER_SERVICES_RATINGS. how can I define this relation with Hibernate/JPA annotations?
Bi-Directional Many to Many using user managed join table object (Relatively common)
Commonly used when you want to store extra information on the join object such as the date the relationship was created.
public class Foo{
private UUID fooId;
#OneToMany(mappedBy = "bar", cascade=CascadeType.ALL)
private List<FooBar> bars;
}
public class Bar{
private UUID barId;
#OneToMany(mappedBy = "foo", cascade=CascadeType.ALL)
private List<FooBar> foos;
}
#Entity
#Table(name="FOO_BAR")
public class FooBar{
private UUID fooBarId;
#ManyToOne
#JoinColumn(name = "fooId")
private Foo foo;
#ManyToOne
#JoinColumn(name = "barId")
private Bar bar;
//You can store other objects/fields on this table here.
}
You need to create an explicit UserServices entity and setup the relationship to the Ratings entity per your needs.
Remember that in hibernate you model relationships between entities (i.e. your java objects), not db tables.

Categories

Resources