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;
}
Related
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.
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*
In my Spring boot app, there are two types of entities: User and Group:
User can own 0 to N groups
Group can have 1 to M members
In the User class there is a list of Group that he/she owns or is a member of, and in the Group class, there is a list of User (i.e. members).
These classes refer to each other using hibernate annotations.
class User {
#ManyToMany(cascade = CascadeType.REFRESH)
private List<Group> groups;
}
class Group {
#ManyToOne(cascade = CascadeType.REFRESH)
#NotNull
#JoinColumn(name="OWNER_ID", referencedColumnName="id")
private User owner;
#ManyToMany
#JoinTable(joinColumns = #JoinColumn(referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(referencedColumnName = "id"))
private List<User> members;
}
In the User service layer there's a Delete method which is supposed to delete a user from the repository. This delete should fire a series of actions: all the groups owned by that user gets deleted, and the deleted groups should be removed from list of groups of their members. All these should be saved to the repository.
If I add other types of entities to this network, this process gets much more complicated.
My question is: Doesn't hibernate handle this automatically ? Should I grab each member and delete the group one by one and save it to the repository ?
CascadeType.REFRESH means Managed objects can be reloaded from the database by using the refresh method.
This will not help you solving your requirement. You need to use “orphanRemoval = true” CascadeType. “orphanRemoval = true” removes an owned object from the database when it’s removed from its owning relationship.
Example:
EmployeeEntity.java
#Entity #Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
private static final long serialVersionUID = -1798070786993154676L;
#Id #Column(name = "ID", unique = true, nullable = false)
private Integer employeeId;
#Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
private String firstName;
#Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
private String lastName;
#OneToMany(orphanRemoval = true, mappedBy = "employee")
private Set<AccountEntity> accounts;
}
AccountEntity.java
#Entity (name = "Account") #Table(name = "Account")
public class AccountEntity implements Serializable
{
private static final long serialVersionUID = 1L;
#Id #Column(name = "ID", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer accountId;
#Column(name = "ACC_NO", unique = false, nullable = false, length = 100)
private String accountNumber;
#ManyToOne
private EmployeeEntity employee;
}
OR You can use CascadeType.ALL too.
For further reading, go through below link:
CascadeTypes
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.
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.