Why does the order of #JoinColumns matter? - java

I have discovered some strange behavior with Hibernate. I have a One to Many relationship between two entities that use embedded composite primary keys like this. (and yes, I know the data design is awful, but this is the schema I have to work with)
#Entity
#Table(name = "T_PLACE")
public class Place {
#EmbeddedId
private PlacePK id;
#OneToMany(mappedBy = "place")
private List<Mode> modes;
// getters and setters
}
#Embeddable
public class PlacePK implements Serializable {
#Column(name = "COMPANY")
private String company;
#Column(name = "AREA")
private String area;
#Column(name = "COLOR")
private String color;
// getters and setters
}
#Entity
#Table(name = "T_MODE")
public class Mode {
#EmbeddedId
private ModePK id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "COMPANY", insertable = false, updatable = false),
#JoinColumn(name = "AREA", insertable = false, updatable = false),
#JoinColumn(name = "COLOR", insertable = false, updatable = false),
})
private Place place;
private String function;
// getters and setters
}
#Embeddable
public class ModePK implements Serializable {
#Column(name = "COMPANY")
private String company;
#Column(name = "AREA")
private String area;
#Column(name = "COLOR")
private String color;
#Column(name = "MODE_ID")
private String color;
// getters and setters
}
But the resulting HQL ends up ordering it like this when querying for a place's modes
where
company=?
and color=?
and area=?
and it ends up binding area to the second ? and color to the third ?.
It doesn't work unless I change the order of the #JoinColumns to put color before area.
#JoinColumns({
#JoinColumn(name = "COMPANY", insertable = false, updatable = false),
#JoinColumn(name = "COLOR", insertable = false, updatable = false),
#JoinColumn(name = "AREA", insertable = false, updatable = false),
})
So my question is, what is causing this behavior? What determines the order of the where clause in the HQL? This isn't any issue because I've figured out how to make it work, but I'd like to understand it.
I am using spring-boot-starter-data-jpa:1.5.10-RELEASE which uses Hibernate 5.
Edit
Here is how I'm producing the HQL
#Repository
public interface PlaceRepository extends JpaRepository<Place, PlacePK> {}
and then in a test:
PlacePK placePK = new PlacePK();
placePK.setCompany("Acme");
placePK.setArea("XYZ");
place.PK.setColor("Blue");
Place place = placeRepository.findOne(placePK);
List<Mode> modes = place.getModes(); // ends up being an empty PersistBag until I switch the order of the #JoinColumns
assertNotNull(modes);

The order mattes because based on this order Hibernate builds a join condition. Hibernate doesn't know how to map specific columns to each other (even though, it could do it through naming comparison, but...). So it does it by simply putting them in the same order as you specified versus the ID columns.
To see what difference ordering does, switch on logging on the produced SQL queries. You will see that if you order is not aligned with the order of the ID keys, your fetch query may end up in something like
...join PlacePK pk ON pk.COMPANY = m.AREA*emphasized text*

Related

#ManyToOne relationship is not audited

I have two entities:
#Entity
#Table(name = "entity_a")
#Audited
public class EntityA {
#Column(name = "entity_a_uuid", columnDefinition = "char", updatable = false)
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
/**
* #deprecated in favor of uuid
*/
#Deprecated
#Id
#GeneratedValue
#Column(name = "entity_a_id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "entity_a_id", nullable = false)
#BatchSize(size = 100)
#NotAudited
private List<EntityB> entityBs = new ArrayList<>();
}
and
#Entity
#Audited
#Table(name = "entity_b")
public class EntityB {
#Id
#Column(name = "entity_b_uuid", columnDefinition = "char", updatable = false)
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "entity_a_id", nullable = false, insertable = false, updatable = false)
private EntityA entityA;
}
Each is correctly audited into two tables entity_a_audit and entity_b_audit. However, the entity_a_id field in entity_b_audit is always null.
Some details:
If I do not have the #NotAudited in EntityA, I will get an error that says something to the effect of: The table EntityA_EntityB_audit does not exist. This seems like it's trying to audit them as a single table, which I do not want.
I have tried applying #Audited(targetAuditMode = elationTargetAuditMode.NOT_AUDITED) to each side. If applied only in EntityA, I get the above error. If applied only in EntityB, nothing changes. If applied in both, I get the error above. If applied to neither, I get the error above.
I suspect the entity_a_id is null in entity_b_audit because the id isn't generated until EntityA hits the DB. entity_a_id is auto-incrementing in the entity_a table.
Using hibernate-envers-5.4.32.Final.jar
Ultimately, I would like for entity_a_id to not be null in entity_b_audit. Alternatively, if I could somehow get entity_a_uuid to be captured instead, that would also suffice.
Any help would be greatly appreciated! Thanks.
You marked the column as insertable = false, updatable = false, so there is nothing to audit here, because Hibernate can never change the value of that column.

Circumventing Attribute Limitations of Many-to-Many Relations in JPA

I am using PostgreSQL 12.11, JPA 3.1.0, and Hibernate 5.6.10. This might become important because I am doing things that apparently do not work with JPA 2.0.
My goal is to add an attribute to a many-to-many relationship. I found this posting. #Mikko Maunu states that "There is no concept of having additional persistent attribute in relation in JPA (2.0)." To me, this sounds like what I want to do is not possible. However, the answer is rather old and might not be complete anymore.
Beside the time gap and the version gap, this is, in my opinion, a new question because I am doing something that is probably questionable and not part of the original thread.
What I did is this:
Create a #ManyToMany relationship in JPA and specify a #JoinTable.
Manually define an entity with identical table name to the table specified in 1. For this table, I chose a composite primary key using #IdClass. I also added my attribute.
Inside one of the n:m-connected entities, create a #OneToMany relationship to the connection-table-entity created in 2. However, I did not create a corresponding #ManyToOne relationship as that would have created an error.
As a result, I can access the original entities and their relation as many-to-many, but also the relation itself, which is not an entity in the original ERM, but it is for JPA. First tests show this seems to be working.
I am aware, however, that I basically access the same part of the persistence (the PostgreSQL database) through two different ways at the same time.
Now my questions are:
Is this a valid way to do it? Or will I get in bad trouble at one point?
Is there a situation where I will need to refresh to prevent trouble?
Is this something new in JPA > 2.0, or just an extension to the original answer?
This should help.
Here is how I do it:
#Entity
#Table(name = "person", schema = "crm")
public final class Person implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person", orphanRemoval = true)
private Set<PersonEmail> emails = new HashSet<>();
}
#Entity
#Table(name = "email", schema = "crm")
public final class Email implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#Column(name = "email", nullable = false, length = 64, columnDefinition = "varchar(64)")
private String localPart;
#Column(name = "domain", nullable = false, length = 255, columnDefinition = "varchar(255)")
private String domain;
}
#Entity
#Table(name = "person_email", schema = "crm")
public final class PersonEmail implements Serializable {
#EmbeddedId
private PersonEmailId id;
// The mapped objects are fetched lazily.
// This is a choice.
#ToString.Exclude
#MapsId("personId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Person person;
#ToString.Exclude
#MapsId("emailId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Email email;
// Here's an extra column.
#Column(name = "type", nullable = false, columnDefinition = "email_type_t")
#Convert(converter = EmailType.EmailTypeConverter.class)
private EmailType type;
public final void setPerson(final Person person) {
this.person = person;
id.setPersonId(this.person.getId());
}
public final void setEmail(final Email email) {
this.email = email;
id.setEmailId(this.email.getId());
}
#Embeddable
public static final class PersonEmailId implements Serializable {
#Column(name = "person_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long personId;
#Column(name = "email_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long emailId;
}

Many-to-Many JPA Hibernate mapping composite key through join table error

I have the following two classes (and their pk classes) with these annotations. I removed setters/getters/hashcode/equals to compress the example
I am ultimately getting this error
org.hibernate.MappingException: Repeated column in mapping for collection: com.stackOverflow.Features.thingCategory column: YEAR
My guess is that because "year" is shared in the join table, I have messed up some syntax for associating my entities. Note these are views which have an implicit relationship between their entities, that I am trying to model in my annotations. I have tried some JPA modelling tools which are giving me the same errors after modelling. I have tried setting the join columns to insertable and updatable to false as well.
I can of course just write a SQL query, but I really surprised at how hard this seems to me in Hibernate.
Feature Entity
#Entity
#Table(name = "myTableOfFeatures")
public class Feature {
private static final long serialVersionUID = 1L;
#EmbeddedId
private FeatureKey id;
#Column(name="displayText")
private String description;
//bi-directional many-to-many association
#ManyToMany
#JoinTable(
name="joinTableToThingCategories"
, joinColumns={
#JoinColumn(name="name", referencedColumnName="name", insertable = false, updatable = false),
#JoinColumn(name="year", referencedColumnName="year", insertable = false, updatable = false)
}
, inverseJoinColumns={
#JoinColumn(name="year", referencedColumnName="year", insertable = false, updatable = false),
#JoinColumn(name="title", referencedColumnName="title", insertable = false, updatable = false)
}
)
private List<ThingCategory> thingCategory;
public Feature() {
}
// ... gets and sets
}
#Embeddable
public class FeatureKey implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(name="name")
private String myName;
private String year;
public FeatureKey() {
}
// ... gets and sets and equals and hashes
}
ThingCategory Entity
#Entity
#Table(name = "CategoriesOfThings")
public class ThingCategory implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ThingCategoryKey id;
private String comment;
//bi-directional many-to-many association to categories
#ManyToMany(mappedBy="thingCategory")
private List<Feature> features;
public ThingCategory() {
}
// ... gets and sets
}
#Embeddable
public class ThingCategoryKey implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
private String year;
#Column(name="categoryName")
private String title;
public ThingCategoryKey() {
}
// ... gets and sets and equals and hashes
}
This is an example from JPA 2.1 specification.
I hope it helps.
#Entity
public class Employee {
#Id int id;
#Embedded ContactInfo contactInfo;
...
}
#Embeddable
public class ContactInfo {
#ManyToOne Address address; // Unidirectional
#ManyToMany List<PhoneNumber> phoneNumbers; // Bidirectional
}
#Entity
public class PhoneNumber {
#Id int phNumber;
#ManyToMany(mappedBy="contactInfo.phoneNumbers")
Collection<Employee> employees;
}
I highly recommend you this specification. JPA 2.1
This might work, but I haven't tried myself; so I wanted to write this as a comment but it is cumbersome to write code as a comment. So you might want to try it if it works (sorry no time to try it out). If it doesn't work leave me a comment and I'll delete it.
#JoinTable(
name="joinTableToThingCategories",
joinColumns={
#JoinColumn(name="feature_name", referencedColumnName="name", insertable = false, updatable = false),
#JoinColumn(name="feature_year", referencedColumnName="year", insertable = false, updatable = false)
},
inverseJoinColumns={
#JoinColumn(name="tcat_year", referencedColumnName="year", insertable = false, updatable = false),
#JoinColumn(name="tcat_title", referencedColumnName="title", insertable = false, updatable = false)
}
)
private List<ThingCategory> thingCategory;

Hibernate MS SQL Join issue

I have two tables in the clients mssql database. The first is a job table - so I created an Job entity which contains the load type and load weight and all that stuff - works fine.
My problem now is that there is a second table that includes informations about the load and unload point. The second table, I call it JEP, has a primary key consisting of several items: the type (load or unload), the zip code and the customer number.
I created an entity JobEndPoint and NetBeans also created an object representing the primary key JobEndPointPK containing all that fields.
I want to add two JobEndPoint (loadPoint and unloadPoint) to my Job entity. My problem is now: how do I annotate that in Hibernate? In my opinion it is an #OneToOne relation ship. It would be perfect if I could specify a SELECT statement like SELECT * FROM JEP WHERE type="load" AND customer_nr="123" AND zip_code="123 ...". Is that possible with Hibernate?
Thanks for your help!
Regeards,
Marco
Here are the Entities:
#Entity
#Table(name = "Auftragsdaten", catalog = "...", schema = "dbo")
public class Job implements Comparable<Object>, Serializable {
private static final long serialVersionUID = 4285871251915951149L;
#Id
#Basic(optional = false)
#Column(name = "`id`", nullable = false)
int id;
#Column(name = "`AufNr`", nullable=false)
int jobId;
#Transient
List<Integer> jobsAdded;
#Column(name = "`Beladedatum`", nullable=false)
#Temporal(TemporalType.DATE)
Date loadDate;
#Column(name = "`Beladezeit`")
#Temporal(TemporalType.TIME)
Date loadTimeFrom;
#Transient
Date loadTimeTo;
#Column(name = "`Entladedatum`", nullable=false)
#Temporal(TemporalType.DATE)
Date unloadDate;
#Column(name = "`Entladezeit Beginn`")
#Temporal(TemporalType.TIME)
Date unloadTimeFrom;
#Column(name = "`Entladezeit Ende`")
#Temporal(TemporalType.TIME)
Date unloadTimeTo;
#Transient
List<JobEndPoint> froms;
#OneToOne
#JoinColumns ({
#JoinColumn(name="`Beladetyp`", referencedColumnName = "`Ladetyp`", insertable = false, updatable = false),
#JoinColumn(name="`AbsNr`", referencedColumnName = "`KundenNr`", insertable = false, updatable = false),
#JoinColumn(name="`Verkehrsart`", referencedColumnName = "`VerkArt`", insertable = false, updatable = false),
#JoinColumn(name="`von LKZ`", referencedColumnName = "`LKZ`", insertable = false, updatable = false),
#JoinColumn(name="`von PLZ`", referencedColumnName = "`PLZ`", insertable = false, updatable = false)
})
JobEndPoint fromPoint;
#Transient
JobEndPoint toPoint;
#Column(name = "`Verkehrsart`", length = 10, nullable=false)
#Enumerated
JobType type;
#Column(name = "`Anzahl Paletten CCG1`")
int numberCCG1;
#Column(name = "`Anzahl Paletten CCG2`")
int numberCCG2;
#Transient
int numberFullContainer;
#Transient
int numberEmptyContainer;
#Column(name = "`Anzahl Container`")
int numberContainer;
#Column(name = "`Anz Stellplätze`")
int numberUnits;
#Column(name = "`Bruttogewicht`", nullable=false)
int loadWeight;
#ManyToOne
#JoinColumn(name="`Kühlkennzeichen`")
CoolingCode coolingCode;
}
#Entity
#Table(name = "BES", catalog = "...", schema = "dbo")
public class JobEndPoint implements Serializable {
private static final long serialVersionUID = 1017986852824783744L;
#Id
protected JobEndPointPK jobEndPointPK;
(...)
}
#Embeddable
public class JobEndPointPK implements Serializable {
#Basic(optional = false)
#Column(name = "`Ladetyp`", nullable = false, length = 50)
#Enumerated
EndPointType type;
#Basic(optional = false)
#Column(name = "`KundenNr`", nullable = false)
int customerId;
#Basic(optional = false)
#Column(name = "`VerkArt`", nullable = false, length = 10)
#Enumerated
JobType jobType;
#Basic(optional = false)
#Column(name = "`LKZ`", nullable = false, length = 3)
String countryCode;
#Basic(optional = false)
#Column(name = "`PLZ`", nullable = false, length = 7)
String zipCode;
}
In general, I would recommend using a generated internal primary key instead of the composite key. However, if you need to stick with your composite key, here are some ideas that hopefully help.
I understand that JobEndPointPK is implemented as an identifier component (see the Hibernate Reference, chapter 8.4). Note: it is critical that it implements the equals and hashCode` methods correctly, as Hibernate relies on these.
Updated: Provided that your JobEndPoint and JobEndPointPK looks something like this:
#Embeddable
class JobEndPointPK {
#Column(name = "type", nullable = false)
#Enumerated
EndPointType type;
#Column(name = "zipCode", nullable = false)
String zipCode;
#Column(name = "customerNumber", nullable = false)
int customerId;
// equals, hasCode, getters, setters etc.
}
#Entity
class JobEndPoint {
#Id
private JobEndPointPK key;
// getters, setters etc.
}
The mapping annotation would be something like:
#Entity
class Job {
#OneToOne
#JoinColumns ({
#JoinColumn(name="loadPointType", referencedColumnName = "type"),
#JoinColumn(name="loadPointZip", referencedColumnName = "zipCode"),
#JoinColumn(name="loadPointCust", referencedColumnName = "customerNumber")
})
private JobEndPoint loadPoint;
// similarly for unloadPoint
// other properties
}
The example is adapted from here.
I am not sure how to deal with JobEndPointPK.type though, as for loadPoint it is obviously Load and for unloadPoint, Unload, so you most probably don't want to store it separately in the DB. My gues is that you can specify the value with the #Formula annotation, but I haven't seen any concrete example for this.
Note that all this code is purely experimental, I haven't tested it.
There are other variations on the theme. For more details, see the section "Composite keys with annotations" in Chapter 8 of Java Persistence with Hibernate.

JPA join table with more than one entity

I have an Entity that looks like this:
public class NpcTradeGood implements Serializable, Negotiabble {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected NpcTradeGoodPK npcTradeGoodPK;
#JoinColumn(name = "npc_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
#ManyToOne(optional = false)
private Npc npc;
}
#Embeddable
public class NpcTradeGoodPK implements Serializable {
#Basic(optional = false)
#Column(name = "npc_id", nullable = false)
private long npcId;
#Basic(optional = false)
#Column(name = "good_id", nullable = false)
private long goodId;
#Basic(optional = false)
#Column(name = "type", nullable = false)
#Enumerated(EnumType.STRING)
private ItemType type;
}
Is there a way to tell JPA which OneToMany relationship it is based on the type column (enumeration)?
Like if its a part or any other entity it automatically gets the related entity.
Thanks in advance.
In your PK object, you don't need to store the ids as longs (actually, this is true every time you need a reference to an entity). When mapping to the actual DB schema, JPA replaces all the references to other entities by thoes entities' ids.
So, if you use this (notice that I replaced your 'long' ids with actual references to the entities):
#Embeddable
public class NpcTradeGoodPK implements Serializable {
#ManyToOne
#JoinColumn(name = "npc_id", nullable = false)
private Npc npc;
#ManyToOne
#JoinColumn(name = "good_id", nullable = false)
private Good good;
#Column(name = "type", nullable = false)
#Enumerated(EnumType.STRING)
private ItemType type;
}
... JPA will map this to the DB, using: "long npc_id" where we refer to "Npc npc"; and "long good_id" where we refer to "Good good".
One important thing: you cannot use #Column with #ManyToOne. You may use #JoinColumn instead which will allow you to do the same things you do now.
Also, you don't need to specify all those 'optionals'. 'nullable' should take care of that.
Edited: ah, the Npc in NpcTradeGoodPK will probably collide with the Npc in the entity that embeds it. Consider renaming one of them.

Categories