Use Hibernate Formula annotation for reference loading - java

I have 3 entities - Storage, Item and Relation. Storage has several Item entities and items are bound by Relation entities. And relations can bind items from different storage. For simplification let say I want to load relation by query and want to do it fast. So now I have 3 query - loading for Storage, loading for all Items under storage and load of relation (List<Relation> relations field).
Now I want to tell hibernate how to load Collection<Relation> extRelation field. I tried #Formula, #CalculatedColumn and different combination of #ManyToMany and #JoinFormula. But generated query is wrong (or ignores my query). Also I cannot use #OneToMany because of https://hibernate.atlassian.net/browse/HHH-9897 bug.
Latest Exception is:
Caused by: org.h2.jdbc.JdbcSQLException: Table
"TEST_STORAGES_TEST_RELATIONS" not found; SQL statement:
SELECT extrelatio0_.test_storages_storage_id AS test_sto1_2_0_
,extrelatio0_.extRelation_from_item_id AS extRelat2_3_0_
,extrelatio0_.extRelation_to_item_id AS extRelat3_3_0_
,relation1_.from_item_id AS from_ite1_1_1_
,relation1_.to_item_id AS to_item_2_1_1_
,relation1_.relation_id AS relation3_1_1_
,relation1_.storage_id AS storage_4_1_1_
FROM test_storages_test_relations extrelatio0_
INNER JOIN test_relations relation1_ ON extrelatio0_.extRelation_from_item_id = relation1_.from_item_id
AND extrelatio0_.extRelation_to_item_id = relation1_.to_item_id
WHERE extrelatio0_.test_storages_storage_id = ?
My entities:
#Entity
#Table(name = "test_storages")
public class Storage {
#Id
#Column(name = "storage_id")
private BigInteger storageId;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, targetEntity = Item.class)
#JoinColumn(name = "storage_id", updatable = false)
#MapKey
private List<Item> items;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, targetEntity = Relation.class)
#JoinColumn(name = "storage_id", updatable = false)
#Fetch(org.hibernate.annotations.FetchMode.SELECT)
private List<Relation> relations;
#ManyToMany()
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula =
#JoinFormula(
value = "(select dep.from_item_id, dep.to_item_id from test_relations dep where dep.storage_id = ?)"
)
)
})
private Collection<Relation> extRelation;
}
#Entity
#Table(name = "test_items")
public class Item {
#Id
#Column(name = "item_id")
private BigInteger itemId;
private String name;
#Column(name = "storage_id")
private BigInteger storageId;
}
#Entity
#Table(name = "test_relations")
public class Relation {
#Column(name = "relation_id")
private BigInteger relationId;
#Column(name = "storage_id")
private BigInteger storageId;
#EmbeddedId
private RelationPK pk;
}
#Embeddable
public class RelationPK implements Serializable {
#Column(name = "from_item_id")
private BigInteger fromItemId;
#Column(name = "to_item_id")
private BigInteger toItemId;
}
All sources available on https://github.com/ainlolcat/test_hibernate_formula

Related

Hibernate not respecting nullable=true in #JoinColumn annotation

I have two entities
#Entity
#Table(name = "Documents")
public class Document extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "asset_id")
private Asset asset;
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "debt__id")
private Debt debt;
}
and
#Entity
#Table(name = "debts")
public class Debt extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "debt", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Document> documents;
}
This mapping:
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "debt_id")
private Debt debt;
was recently added.
On running the app, the app is crashing with an error saying: Error executing DDL "alter table documents add debt__id bigint not null" via JDBC Statement
I checked the sql query hibernate sent to the database and it was: alter table documents add debt_id bigint not null
This query fails because there are records already on the documents table so a non-nullable column without default value could not be added.
So why is the nullable=true in the #JoinColumn annotation ignored by hibernate.
I couldn't find an answer anywhere. This is a spring-boot app, if it helps.
Just remove the optional = false from the #ManyToOne. That is the culprit. Also, you don't need to mention nullable=true in the #JoinColumn, that is by default true
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY)
#JoinColumn(name = "debt__id")
private Debt debt;

hibernate many-to-many association with extra columns in join table example

I have 3 tables as #Entity, and 2 join tables in my spring + hibernate app.
In one of join table i have extra column. I want to take info from this info column when i take info from my main table.
Main table code:
#Entity
#Table(name = "items")
public class Items {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "crafts"
,joinColumns = #JoinColumn(name = "item_id")
,inverseJoinColumns = #JoinColumn(name = "plot_id"))
private Set<Plots> plotInfo = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "item_materials"
,joinColumns = #JoinColumn(name = "item_id")
,inverseJoinColumns = #JoinColumn(name = "material_id"))
private Set<Materials> materialsInfo = new HashSet<>();
Table item_materials have this columns "id, item_id(fkey), material_id(fkey), expense" and one of this which names as "expense" i need to have in my final result.
How can i code my class to have "expense" in my result?
I read about #embeddable but still dont understand how to use in my project.
Don't use a #ManyToMany association. Map the join table as entity and model it similar to this:
#Entity
#Table(name = "items")
public class Items {
#OneToMany(mappedBy = "item")
private Set<Crafts> plotInfo = new HashSet<>();
}
#Entity
#Table(name = "plots")
public class Plots {
#OneToMany(mappedBy = "plot")
private Set<Crafts> items = new HashSet<>();
}
#Entity
#Table(name = "crafts")
public class Crafts {
#EmbeddedId
private CraftsId id;
#ManyToOne
#JoinColumn(name = "item_id", insertable = false, updatable = false)
private Items item;
#ManyToOne
#JoinColumn(name = "plot_id", insertable = false, updatable = false)
private Plots plot;
}
#Embeddable
public class CraftsId {
#Column(name = "item_id")
private Integer itemId;
#Column(name = "plot_id")
private Integer plotId;
// equals + hashCode
}

Automatically delete child entity using orphanRemoval Spring JPA

Currently I have the following 2 entities with a one to many relationship -
#Data
#Entity
#Table(name = "invoice_line")
#IdClass(InvoiceLinePK.class)
public class InvoiceLineEntity {
#Id
#Column(name = "line_id")
private String lineId;
#Id
#Column(name = "client_id")
private Integer clientId;
#Id
#Column(name = "invoice_id")
private String invoiceId;
#Column(name = "item_id")
private String itemId;
#Column(name = "amount")
private BigDecimal amount;
#ManyToOne
private InvoiceEntity invoice;
}
#Entity
#Table(name = "invoice")
#IdClass(InvoicePK.class)
#Data
public class InvoiceEntity {
#Id
#Column(name = "client_id")
private Integer clientId;
#Id
#Column(name = "invoice_id")
private String invoiceId;
#Column(name = "description")
private String description;
#Column(name = "txn_total_amount")
private BigDecimal txnTotalAmount;
#Column(name = "created_time", updatable = false)
#CreationTimestamp
private Date createdTime;
#Column(name = "updated_time")
#UpdateTimestamp
private Date updatedTime;
#OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "invoice")
private List<InvoiceLineEntity> invoiceLines;
}
In a case wherein let's say, one of my existing invoice has 3 lines and I receive a request that this particular invoice has been updated and it now has only 1 line instead of the previous 3 (so the other 2 have to be deleted), I would like to create a new Invoice object with this 1 InvoiceLineEntity and then do a invoiceRepository.save(invoice)
I am expecting that the other 2 InvoiceLine records would be automatically deleted because the orphanRemoval flag is enabled.
Can someone tell me how I can achieve this relationship by tweaking the entity relationship structure of the above 2 entities?
Your child entity must be the owner of the relationship, so that the orphans are allowed to be deleted
If you change and add mappedBy to that relation
#OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "bill")
private List<BillLine> billLines;
Then the BillLine must also hold a reference
public class BillLine {
#Id
#Column(name = "line_id")
private String lineId;
#Id
#Column(name = "company_id")
private Integer companyId;
#Id
#Column(name = "bill_id")
private String billId;
#Column(name = "item_id")
private String itemId;
#Column(name = "amount")
private BigDecimal amount;
#ManyToOne
private Bill bill;
}
Now it will remove the orphans
Also since you have multiple #Id on each entity. Do you know that you have to either declare a composite class or an embeddable class? Without one of those the multiple Ids are not valid.
Edit:
1) My bad mappedBy should be placed inside #OneToMany and not #JoinColumn. I have corrected it in my answer
2) Remove #JoinColumn. It is wrong in your configuration. By default #OneToMany inserts a column in the side of the #ManyToOne which holds the references to the primary table. You can override those default configurations and create a separate table for mappings but then you need the #JoinTable and I don't see any reason for that here.
This here
#JoinColumns(value = { #JoinColumn(name = "company_id", referencedColumnName = "company_id"),
#JoinColumn(name = "bill_id", referencedColumnName = "bill_id") })
definitely does not belong on #OneToMany
The following can be applied to #OneToMany but as said before I don't see any reason to do that and complicate a simple mapping which does not require a separate table.
#JoinTable(joinColumns = #JoinColumn(name = "company_id", referencedColumnName = "company_id"),
inverseJoinColumns = #JoinColumn(name = "bill_id", referencedColumnName = "bill_id") )
Check here for more information Jpa primary key

Getting old-entity with JPA (Hibernate)

I am getting old entity-data with JPA displayed in my JSF-Page, even if I disable the second-level-cache of hibernate.
There are three entities: MessageEntities contain DataEntities and those contain SubDataEntities.
SubDataEntities are extended by two different types.
I temporary delete an instance of SubDataEntity by setting its data-attribute to null and remove the instance from the collection in parent-entity (DataEntity).
After ajax-request, the old data is still displayed in JSF-Page, also if I really execute a successfull delete-operation on DB.
MessageEntity:
#Entity(name = "Message")
#Table(name = "message")
public class MessageEntity {
#Column(name = "version")
private String version;
#Column(name = "variant")
private String variant;
#OneToMany(mappedBy = "message", fetch = FetchType.EAGER, orphanRemoval = true, cascade = CascadeType.ALL)
private List<DataEntity> data;
DataEntity:
#Entity(name = "Data")
#Table(name = "data")
public class DataEntity {
#OneToOne
#JoinColumn(name = "messageId")
private MessageEntity message;
#OneToMany(mappedBy = "data", fetch = FetchType.EAGER, orphanRemoval = true, cascade = CascadeType.ALL)
private List<SubDataEntity> subData;
SubDataEntity:
#Entity(name = "SubData")
#Table(name = "subData")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class SubDataEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, name = "sd_id")
private Integer id;
#OneToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "dataId")
private DataEntity data;
#Column(name = "value")
private String value;
The failure was caused be cause the attribute "immediate" was set to true in the component, that was displaying and editing the value for.
After switching on process="#this" and process="#form".
Probably it was due to caching of first level..
Try evicting the cache when you are loading the data..
getEntityManager().getEntityManagerFactory().getCache().evictAll();

Delete both parent and child in #OneToMany relationship

I have an entity called itineraryTraveller, and every itineraryTraveller can have many flightEntity. When I try to delete an itineraryTraveller (parent), from the database, I get this error message:
a foreign key constraint fails (`pquino01db`.`ITINERARYTRAVELLER_FLIGHTENTITY`, CONSTRAINT `FK_ITINERARYTRAVELLER_FLIGHTENTITY_flights_ID` FOREIGN KEY (`flights_ID`) REFERENCES `FLIGHTENTITY` (`ID`))"
Here is my itineraryTraveller entity:
#Entity
public class itineraryTraveller implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<flightEntity> flights;
#Temporal(javax.persistence.TemporalType.DATE)
private Date departureDate;
private String departureLocation;
private String arrivalLocation;
private double cost;
private char status;
private ArrayList<String> stops;
private String stopPrint;
private String userName;
private int iden;
// ...
}
And the flightEntity looks like this:
#Entity
public class flightEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Temporal(javax.persistence.TemporalType.DATE)
private Date departureDate;
private String airlineCode;
private String flightNumber;
private String departureLocation;
private String arrivalLocation;
private double businessCost;
private double economyCost;
private int numBusinessSeats;
private int numEconomySeats;
// ...
}
Can someone see the problem? I think my #OneToMany annotation might be missing something, but I'm not sure what. I want to delete both the parent and child at the same time.
Your relationship between the two entities is unidirectional as there is no mapping from flightEntity back to itineraryTraveller entity as you do not have a #JoinColumn on your flightEntity. There can be one of the following solutions for your problem:
Add a #ManyToOne annotation on the flightEntity as follows:
#Entity
public class flightEntity implements Serializable {
// ....
#ManyToOne
#JoinColumn(name="<name_of_foreignkey_column>")
private itineraryTraveller traveller;
// ...
}
And you have to add a mappedBy attribute to your #OneToMany annotation:
#OneToMany(mappedBy="traveller", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
Thereby making the relationship between the entities bidirectional.
This one can solve the problem if you already have tables in the database with a foreign key relationship.
Use #JoinTable annotation on the #OneToMany annotation:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name="<join_table_name>", joinColumns=#JoinColumn("TRAVELLER_ID"), inverseJoinColumns=#JoinColumn("FLIGHT_ID"))
private List<flightEntity> flights;
(The names of the columns are considered to be examples, and can be changed.)
This last mapping is useful if you don't have tables in the database with foreign key column defined, and it will create a new table as an association between the tables; which is normally the case in a many-to-many relationships.
If it is possible use #ManyToOne annotation on the flights entity. This is normal way of mapping a one-to-many relationships.
Lastly, there are conventions in Java that state class names should begin with a capital letter. So I would rename the entity names to Flight and ItineraryTraveller.
Note that in some cases the #JoinColumn on the child object must have insertable = false and updatable = false like this:
#JoinColumn(name = "user_id", insertable = false, updatable = false)
public class User {
private List<UserRole> roles;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "user")
public List<UserRole> getRoles() {
return this.roles;
}
public void setRoles(List<UserRole> roles) {
this.roles = roles;
}
}
public class UserRole {
#ManyToOne
#JoinColumn(name = "user_id", insertable = false, updatable = false)
private User user;
}

Categories

Resources