The strategy I'm taking to implementing a maker-checker scenario is through using multiple tables. Currently, I'm using Hibernate 4.2 (annotations). The following scenario is what I would like to achieve. However, I'm having problems with the multi-level inheritance.
The basic idea is that there are two tables (pending and approved). When an add() occurs, the entry is inserted into the pending table. When that entry is approved, it is removed from the pending table and inserted into the approved table.
Policy (the policy)
|
+ -- Pending (maker information)
|
+ -- Approved (checker information)
So, class Policy is the class that defines the necessary fields for a policy. To keep this post shorter, the fields are not be shown.
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // problem
public abstract class Policy { ... }
The Pending class is for the newly-added Policy that is awaiting approval and it has information on the maker/adder.
#Entity
#Table(name = "pending")
public class Pending extends Policy {
#Column(name = "adder", ...)
private String adder;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "time_added", ...)
private Date timeAdded;
}
The Approved class is for approved entities and it contains additional information on the approver in addition to the information from the Pending class.
#Entity
#Table(name = "approved")
public class Approved extends Pending {
#Column(name = "approver", ...)
private String approver;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "time_approved", ...)
private Date timeApproved;
}
My first thought was to try TABLE_PER_CLASS. However, it resulted in the following runtime error: org.hibernate.MappingException: Cannot use identity column key generation with <union-subclass> mapping for: .... The solution for this is to modify the base class #GeneratedValue(strategy = GenerationType.IDENTITY) to #GeneratedValue(strategy = GenerationType.TABLE). However, modifying that class is beyond my scope as it is shared across multiple projects.
Just for the heck of it, I tried the other two strategies. Obviously, SINGLE_TABLE resulted in one table, with an extra column DTYPE. Not what we wanted. JOINED resulted in two tables, but the approved table has a foreign key to the pending table. Since we wanted to remove an entry from the pending table and move it to the approved table, this would not work for us.
Currently, my solution is to as follows, which is basically copy and paste the code from the Pending class into the Approved class.
#Entity
#Table(name = "approved")
public class Approved extends Policy {
#Column(name = "adder", ...)
private String adder;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "time_added", ...)
private Date timeAdded;
#Column(name = "approver", ...)
private String approver;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "time_approved", ...)
private Date timeApproved;
}
This solution seems counter-intuitive as it duplicates code. Is there a solution that does not require code duplication and keeps the maker-checker process that way it currently works?
After experimenting with the suggested approach by #kostja, I arrived at the following solution.
The maker class encapsulates information pertaining to the maker, which is also an #Embeddable class.
#Embedabble
public class Maker {
#Column(name="maker_id", ...)
private String makerId;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="time_added", ...)
private Date timeAdded;
}
Similarly, the checker class also encapsulates information pertaining to the checker, which is also an #Embeddable class.
#Embedabble
public class Checker {
#Column(name="checker_id", ...)
private String makerId;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="time_approved", ...)
private Date timeApproved;
}
The payload is an #Embeddable class. By making the payload an #Embeddable class, the Maker and Checker can be reused for multiple payloads.
#Embeddable
public class Payload { ... }
For example, given two different payloads that requires maker/checker. One of the payload requires 2 checker.
#Embeddable
public class PayloadA { ... }
#Embeddable
public class PayloadB { ... }
Then we define the following two tables for PayloadA.
#Entity
#Table("a_pending")
public class PendingA {
#Embedded
private PayloadA payload;
#Embedded
private Maker maker;
}
#Entity
#Table("a_approved")
public class ApprovedA {
#Embedded
private PayloadA payload;
#Embedded
private Maker maker;
#Embedded
private Checker checker;
}
Similarly, for PayloadB define two tables. And PayloadB requires two checkers.
#Entity
#Table("b_pending")
public class PendingB {
#Embedded
private PayloadB payload;
#Embedded
private Maker maker;
}
#Entity
#Table("b_approved")
public class ApprovedB {
#Embedded
private PayloadB payload;
#Embedded
private Maker maker;
#Embedded
#AttributeOverrides(value = {
#AttributeOverride(name="checkerId",column="checker1_id"),
#AttributeOverride(name="timeApproved",column="checker1_time_approved"),
})
private Checker checker1;
#Embedded
#AttributeOverrides(value = {
#AttributeOverride(name="checkerId",column="checker2_id"),
#AttributeOverride(name="timeApproved",column="checker2_time_approved"),
})
private Checker checker2;
}
I hope this solution should be general and flexible enough.
I would use a different approach for this. I assume Policy is your entity - the one carrying the real payload. You would like to add some metadata to it. Inheritance does not look as a good fit for this to me. A Pending is not an Policy and an Approved is not a Pending.
Instead of inheritance, I would model the metadata as separate, unrelated entities and create 1-1 relationships to the payload entity. Or many-to-one, if you need multiple approvals.
This way, you have a better decoupled data model and a more normalized DB structure. This gives you more flexibility. You can have single or multiple approvals, change the Approval model without changing the payload entity, and have a better focused payload entity, unburdened by metadata.
The entities could look like this:
#Entity
public class Policy{
#OneToOne
private Creation creation;
#OneToMany(mappedBy="policy")
private List<Approval> approvals;
...
}
Creation:
#Entity
public class Creation{
#OneToOne
private Policy policy;
private String creator;
#Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
...
}
Approval:
#Entity
public class Approval {
#ManyToOne
private Policy policy;
private String approver;
#Temporal(TemporalType.TIMESTAMP)
private Date approvedAt;
..
}
Related
Animal.java
#Data
#Entity
public class Animal implements MyEntityInterface {
public enum Sex {MALE, FEMALE}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private Sex sex;
private boolean castrated;
#OneToMany
private List<Symptom> symptoms;
}
AnimalDTO.java
#Getter
#Setter
public class AnimalDTO implements Serializable {
private long id;
private String name;
private Animal.Sex sex;
private boolean castrated;
private List<Long> symptoms;
}
I wish for a list of Symptoms to be automatically mapped to a list of ID's. This could be achieved in many ways, such as creating a TypeMap, creating a Converter or even just by creating a method in AnimalDTO.java:
public void setSymptoms(List<Symptom> symptoms) {
if (symptoms != null)
this.symptoms = symptoms.stream().map(s -> s.getId()).collect(Collectors.toList());
}
But now imagine it's not only Symptoms, but 50 other fields too. That's a lot of code for the same functionality. And then, it's not only Animal to AnimalDTO, but another 30 different classes with their respective DTOs too.
Also, that still leaves the way back open. From ID to entity. This can (in theory) be achieved easily with the following pseudocode:
List<EntityMemberField.class> list;
for (var entityid : listOfEntityIDsOfDto) {
Object persistedObject = entityManager.find(EntityMemberField.class, entityid);
list.add(persistedObject);
}
...
ModelMapperDestination.setField(list);
This is the same for absolutely every Entity/DTO and should automatically happen for every Entity relationship where the Entity implements MyEntityInterface.
An idea how I could achieve that would be overriding MappingEngineImpl.java from ModelMapper which I register as a Spring Service and inject the EntityManager into, but how could I get ModelMapper to use mine? Or is there maybe an easier way?
The goal is to have a fairly automated conversion from Spring Entities to their corresponding DTO by... just calling modelMapper.map(entity, EntityDTO.class);
Context: I have two tables: Questionnaire and Question Section. A Questionnaire can have many Question Sections. Questionnaires and Question Sections both have Start and End Dates to determine if they are active records.
Here are my entities as written:
#Entity
#Data
public class Questionnaire {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
private String name;
private Date startDate;
private Date endDate;
private String description;
#OneToMany(cascade = CascadeType.All,
fetch = FetchType.LAZY,
mappedBy = "questionnaire")
#JsonManagedReference
private List<QuestionSection> questionSections = new ArrayList<QuestionSection>();
}
#Entity
#Data
public class QuestionSection {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
private String name;
private String description;
private int sectionLevel;
private Date startDate;
private Date endDate;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "QUESTIONNAIRE_ID", nullable = false)
#JsonBackReference
private Questionnaire questionnaire;
}
Here is my Spring Data Repository with a single declared method:
public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {
Questionnaire findByNameAndEndDateIsNull(String name);
// Previous goal query, but worked all the way back to the above simple query
// Questionnaire findByIdAndQuestionSectionsEndDateIsNull(UUID id);
}
The above derived query generates two queries shown below:
-- For brevity
select questionnaire.id as id
questionnaire.description as description
questionnaire.end_date as end_date
questionnaire.start_date as start_date
from questionnaire
where questionnaire.name='Foo' and (questionnaire.end_date is null)
select questionsection.questionnaire_id as questionnaire id
...rest of fields here...
from question_section
where questionsection.questionnaire_id = id from above query
Then Spring Data or Hibernate is combining those two above queries into one data object representative of the questionnaire object and returning that.
My problem with this is that I would have expected One query to run with a Join between the two tables, not two and then combine the results in memory. I'm pretty experienced with Spring Data and ORMs in general and have not been able to find any documentation as to why this is happening. Honestly I wouldn't care except that my original intention was to query at the parent entity and 'filter' out children that have end dates (not active). This derived query (commented out above) exhibited the same behavior which ultimately resulted in the data set that was returned containing the end dated question sections.
I know there's 100 other ways I could solve this problem (which is fine) so this is more of an educational interest for me at this point if anyone has any insight into this behavior. I could be missing something really simple.
You should be able to do this using the Entity Graph feature introduced in JPA 2.1.
https://www.baeldung.com/jpa-entity-graph
Spring Data offers support for Entity Graphs via the #NamedEntityGraph and #EntityGraph annotations:
https://www.baeldung.com/spring-data-jpa-named-entity-graphs
So in your code:
Entity:
#Entity
#NamedEntityGraph(name = "Questionnaire.questionSections",
attributeNodes = #NamedAttributeNode("questionSections ")
)
public class Questionnaire{
//...
}
Repository:
public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {
#NamedEntityGraph("Questionnaire.questionSections")
Questionnaire findByNameAndEndDateIsNull(String name);
}
public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {
#EntityGraph(attributePaths = { "questionSections" })
Questionnaire findByNameAndEndDateIsNull(String name);
}
I think I have a bad setup for my hibernate database. I have Citizen entities who have one to many relationships with WeeklyCare entities. Below is the relevant code.
Citizen:
#Entity
#Table(name = "citizens")
public class Citizen {
#Id
#Size(max = 10, min = 10, message = "CPR must be exactly 10 characters")
private String cpr;
#OneToMany()
#JoinColumn(name = "cpr")
private List<WeeklyCare> weeklyCare;
}
WeeklyCare:
#Entity
public class WeeklyCare {
#EmbeddedId
private WeeklyCareIdentifier weeklyCareIdentifier;
}
WeeklyCareIdentifier:
#Embeddable
public class WeeklyCareIdentifier implements Serializable {
#NotNull
#Size(max = 10, min = 10, message = "CPR must tbe exactly 10 characters")
private String cpr;
#NotNull
private Integer week;
#NotNull
private Integer year;
}
I have some problems when I want to save data to the database:
I can't save WeeklyCare first, because it requires a Citizen.
When I send the citizens to my backend, the objects contain a list of WeeklyCare. When I try to save the citizens, it gives me this error: Unable to find Application.Models.WeeklyCare with id Application.Models.WeeklyCareIdentifier#b23ef67b
I can solve the problem by clearing the list of WeeklyCare on the Citizen before saving it, and then saving the list of WeeklyCare after, but that feels like a terrible way to do it.
I guess I want hibernate to ignore the list of WeeklyCare when it saves a Citizen, but acknowledge it when it fetches a Citizen. Is this possible? Or is there an even better way to do it? Thanks.
I can't save WeeklyCare first, because it requires a Citizen.
You have the "cpr" identifier used in two entities:
it's the primary Id for Citizen
it's part of the composite Id for WeeklyCare
You could, theoretically speaking, create a list of WeeklyCare (not with the way it is modeled now though) and later update the associations of each WeeklyCare to Citizen.
When I send the citizens to my backend, the objects contain a list of WeeklyCare. When I try to save the citizens, it gives me this
error: Unable to find Application.Models.WeeklyCare with id
Application.Models.WeeklyCareIdentifier#b23ef67b
The best way to map One-To-Many association is bidirectional. This will also save you from some unnecessary queries Hibernate is generating when using #OneToMany with #JoinColumn only.
1) Remove cpr from WeeklyCareIdentifier class (and probably rename the class).
#Embeddable
public class WeeklyCareIdentifier implements Serializable {
#NotNull
private Integer week;
#NotNull
private Integer year;
//constructors, getters, setters
}
2) Remove the composite #EmbeddedId in favor of Long id field:
#Entity
public class WeeklyCare {
#Id
#GeneratedValue
private Long id;
#Embedded
private WeeklyCareIdentifier weeklyCareIdentifier;
//constructors, getters, setters
}
3) Move to bidirectional mapping:
#Entity
#Table(name = "citizens")
public class Citizen {
#Id
#Size(max = 10, min = 10, message = "CPR must be exactly 10 characters")
private String cpr;
#OneToMany(
mappedBy = "citizen",
cascade = CascadeType.ALL, //cascade all operations to children
orphanRemoval = true //remove orphaned WeeklyCare if they don't have associated Citizen
)
private List<WeeklyCare> weeklyCares = new ArrayList<>(); //init collections to avoid nulls
//constructors, getters, setters
//add utility methods to manipulate the relationship
public void addWeeklyCare(WeeklyCare weeklyCare) {
weeklyCares.add(weeklyCare);
weeklyCare.setCitizen(this);
}
public void removeWeeklyCare(WeeklyCare weeklyCare) {
weeklyCares.remove(weeklyCare);
weeklyCare.setCitizen(null);
}
}
and:
#Entity
public class WeeklyCare {
#Id
#GeneratedValue
private Long id;
//having reference to the citizen entity from WeeklyCare
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "citizen_cpr")
private Citizen citizen;
#Embedded
private WeeklyCareIdentifier weeklyCareIdentifier;
//constructors, getters, setters
}
I would also recommend to use Long ids for the entities, even if the cpr is unique and so on. Convert the cpr to a normal column and introduce a DB generated ID column which you use in to join with in your internal domain and treat the cpr as a pure user-facing data column.
I have been trying to solve this for whole day but no luck! Also i tried to read most of the tutorials on the net but as you all know they all are full of useless examples that do not reflect what you need in the real world.
So here is my situation:
The database:
table: vehicles(vehicleId, brand, model, devYear, regNumber) <-- vehicleId is the PrimaryKey
table: extras(vehicleId, allowSmoke, allowFood, allowDrinks, airConditioner) <-- vehicleId is a PK and a FK.
The point is that if i have a class Vehicle and a class TravelExtras which are mapped to the database i want the Vehicle class to have an attribute TravelExtras travelExtras and get and set methods.
Unfortunatelly no matter what i tried when i try to persist the object in the databse i get various errors.
Here is an illustration:
EntityManagerFactory emfactory = Persistence.createEntityManagerFactory( "NaStopPU" );
EntityManager entitymanager = emfactory.createEntityManager( );
entitymanager.getTransaction( ).begin( );
TravelExtra travelExtra = new TravelExtra();
entitymanager.persist(travelExtra);
Vehicle vehicle = new Vehicle(2L, "10152487958556242", "Mazda", "626", "334343", 2005, 4);
vehicle.setTravelExtra(travelExtra);
entitymanager.persist(vehicle);
entitymanager.getTransaction().commit();
entitymanager.close( );
emfactory.close( );
Any one knows what kind of annotations to use for this One to one case ?
The Java Persistence wikibook has a section called Primary Keys through OneToOne and ManyToOne Relationships which seems to indicate that what you want is possible.
If I'm reading it right, for your case, it would look something like:
class Vehicle {
#Id
#Column(name = "EXTRAS_ID")
private long extrasId;
#OneToOne(mappedBy="vehicle", cascade=CascadeType.ALL)
private TravelExtra extras;
}
class TravelExtras {
#Id
#Column(name = "VEHICLE_ID")
private long vehicleId;
#OneToOne
#PrimaryKeyJoinColumn(name="VEHICLE_ID", referencedColumnName="EXTRAS_ID")
private Vehicle vehicle;
public TravelExtras(Vehicle vehicle) {
this.vehicleId = vehicle.getId();
this.vehicle = vehicle;
}
}
Note that one of your entities will need to make sure it has the same id as the other, which is accomplished in the example by the TravelExtras constructor requiring the Vehicle it is bound to.
I know this is very old qs, but for completeness of your case
you can just have (jpa 2.0)
#Entity
#Data
public class Vehicle implements Serializable{
#Id
#GeneratedValue
private long vehicleId;
.. //other props
}
#Entity
#Data
public class VehicleExtras implements Serializable{
#Id
#OneToOne (cascade = CASCADE.ALL)
#MapsId
#JoinColumn(name ="vehicleId")
private Vehicle vehicle;
#Column
private boolean allowSmoke;
..// other props.
}
should share same pk/fk for VehicleExtra table
Why don't you use an #Embedded object? When using an embedded object, you get
the logical separation you desire in your code and keep your database compliant with Entity-Relational Normalization rules.
It's weird to think on a One-to-One relationship, because even though JPA/Hibernate allows it, all data should be stored in the same table, making you model simpler, while also simplifying queries and increasing database performance by removing the need for a Join operation.
When using Embedded objects you don't have to worry about mapping IDs and bizarre relations, since your ORM is capable of understanding that your just making a code separation, instead of demanding an actual relation of One-to-One between tables.
class Vehicle {
#Id
#Column(name = "ID")
private long vehicleId;
#Column(name = "BRAND")
private String brand;
#Column(name = "MODEL")
private String model;
#Column(name = "DEV_YEAR")
private int devYear;
#Column(name = "REG_NUMBER")
private int regNumber;
#Embedded
private TravelExtra extras;
// Constructor, getters and setters...
}
.
#Embeddable
class TravelExtras {
#Column(name = "ALLOW_SMOKE")
private boolean allowSmoke;
#Column(name = "ALLOW_FOOD")
private boolean allowFood;
#Column(name = "ALLOW_DRINKS")
private boolean allowDrinks;
#Column(name = "AIR_CONDITIONER")
private boolean airConditioner;
// Default Constructor, getters and setters...
}
You can map your classes for example with Netbeans. It will generate annotations. The problem could be your dao layer. You have to persist objects in correct way. For example can't save travelExtra without Vehicle. Also be aware of owning side.
I'm trying to write a hibernate adapter for an old database schema. This schema does not have a dedicated id column, but uses about three other columns to join data.
On some tables, I need to use coalesce. This is what I came up with so far:
About the definition:
A car can have elements, assigned by the car's user or by the car's group of users.
If FORIGN_ELEMENT holds a user's name, definition will be 'u'
If FORIGN_ELEMENT holds a group's name, definition will be 'g'
This also means, one table (CAR_TO_ELEMENT) is misused to map cars to elements and cargroups to elements. I defined a superclass CarElement and subclasses CarUserElement and CarGroupElement.
state is either "active" or an uninteresting string
I set definitition and state elsewhere, we do not need to worry about this.
Use DEP_NR on the join table. If it's zero, use USR_DEP_NR. I did this with COALESCE(NULLIF()) successfully in native SQL and want to achieve the same in Hibernate with Pojos.
Okay, here we go with the code:
#Entity
#Table(name="CAR")
public class Car extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="USER_NAME")
#Type(type="TrimmedString")
private String username;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=CarGroup.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private CarGroup group;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarUserElement.class, mappedBy="car")
private Set<CarUserElement> elements;
}
#Entity
#Table(name="CAR_GROUP")
public class CarGroup extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="GROUP_NAME")
#Type(type="TrimmedString")
private String group;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Car.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private Set<Car> cars;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarGroupElement.class, mappedBy="car")
private Set<CarGroupElement> elements;
}
#MappedSuperclass
public class CarElement extends TableEntry {
#Id
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Element.class)
#JoinColumns(value={
#JoinColumn(name="ELEMENT_NAME"),
#JoinColumn(name="state"),
})
private Element element;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarGroupElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="GROUP_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
I tried all available versions of hibernate (from 3.5.1 [first version with #JoinColumnsOrFormulas] up to 4.x.x), but I always get this error:
Exception in thread "main" java.lang.ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:351)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1338)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:791)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:719)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
Other hibernate users seem to have the same problem: They can't get it working with any version, see this thread and other stackoverflow questions:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010559
To be more complete, here's my TrimmedString Class:
https://forum.hibernate.org/viewtopic.php?p=2191674&sid=049b85950db50a8bd145f9dac49a5f6e#p2191674
Thanks in advance!
PS: It works with joining just these three colulmns with just one DEP-NR-Column (i.e. either DEP_NR OR USR_DEP_NR using just #JoinColumns). But I need this coalesce(nullif()).
I ran into a similar problem, and it seems that the issue is that you are using a #Formula inside an #Id. Hibernate wants Ids to be insertable, and Formulas are read-only.
In my case I was able to work around the problem by making the individual columns Id properties on their own, and making the joined object a separate property. I don't know if this would work in your case since you're using two different columns in your formula, but if so your code might look something like:
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#Column(name="DEP_NR")
private Integer depNr;
#Id
#Column(name="USR_DEP_NR")
private Integer usrDepNr;
#Id
#Column(name="FORIGN_ELEMENT")
private String userName;
#Id
#Column(name="STATE")
private String state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME", insertable = false, updatable = false)),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE", insertable = false, updatable = false))
})
private Car car;
}
Join formulas are very fragile in Hibernate for the time being; I always had a difficult time to get them work properly.
The workaround that helped me often was to create database views which exposed the proper columns (including foreign keys that don't exist in the original tables). Then I mapped the entities to the views using classing Hibernate/JPA mappings.
Sometimes there are redundant joins in the generated SQL when using such entities, but the database optimizes such queries in most cases so that the execution plan is optimal anyway.
Another approach could be using #Subselects, which are some kind of Hibernate views, but I expect them to be less performant than the classic database views.
I ran into the cast exception as well and I'm on Hibernate 5.x.
Until Hibernate dedicates time to fix the issue, I found that while this guy's approach may not be cleanest (he even eludes to that fact!), it works.
You just need to add the #Column mappings (and get/set methods) to your association table objects that are returning null and manually set the values when you populate the relation data. Simple but effective!