JPA join table with more than one entity - java

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.

Related

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;
}

Why does the order of #JoinColumns matter?

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*

Hibernate List Mapping Annotation

Well i have this problem
These are my tables
this is my code for "Compra"
#Entity
#Table(name = "compra")
public class Compra implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "codigo", unique = true, nullable = false)
private int codigo;
#ManyToOne
#JoinColumn(name = "codProveedor", nullable = false)
private Proveedor proveedor;
#Column(name = "tipoComprobante", nullable = false)
private String tipoComprobante;
#Temporal(TemporalType.DATE)
#Column(name = "fechaFactura", nullable = false)
private Date fechaFactura;
#Temporal(TemporalType.DATE)
#Column(name = "fechaLlegada", nullable = false)
private Date fechaLlegada;
#Column(name = "serie", nullable = false)
private String serie;
#Column(name = "numero", nullable = false)
private int numero;
#Column(name = "importe", nullable = false)
private double importe;
#Column(name = "vigencia", nullable = false)
private boolean vigencia = true;
#ElementCollection
private List<DetalleCompra> lstDetalle = new ArrayList<DetalleCompra>();
// getters and setters ...
And this is my code for "DetalleCompra"
#Entity
#Table(name = "detalleCompra")
public class DetalleCompra implements Serializable {
#Id
#GeneratedValue(generator = "gen")
#GenericGenerator(name = "gen", strategy = "foreign", parameters = #Parameter(name = "property", value = "compra"))
#Column(name = "codCompra", nullable = false)
private int codCompra;
#ManyToOne
#JoinColumn(name = "codPresentacion", nullable = false)
private Presentacion presentacion;
#Column(name = "imei", nullable = false)
private String imei;
#Column(name = "simcard", nullable = false)
private String simcard;
getters and setters ...
Well everything looks fine, but when i want to save i have this problem
org.hibernate.TransientObjectException: object references an unsaved transient instance – save the transient instance before flushing: DetalleCompra
well it is clear because when i want to save Compra and DetalleCompra, the second table expect the fk value
public void registrar(Compra compra) {
try {
session = HibernateUtil.getSessionFactory().openSession();
trans = session.beginTransaction();
session.save(compra);
trans.commit();
} catch (Exception e) {
trans.rollback();
throw e;
} finally {
session.close();
}
}
Well the pk of table "compra" is generated well but for the other table does not recognized this value autogenerated, why?, how can i solve that?
#ElementCollection
Defines a collection of instances of a basic type or embeddable class.
Must be specified if the collection is to be mapped by means of a
collection table.
You use wrong annotation to represent relation. There is one to many relation between Compra and DetalleCompra.
You should change #ElementCollection annotation to #OneToMany. Do not forget to specify join columns #JoinColumn(name="codCompra"). I assume that Presentacion is properly mapped.
See also
Unidirectional Mapping vs. Bidirectional Mapping
mappedBy attribute
#OneToMany annotation
#ElementCollection annotation

#Manytomany add extra field with no join Entity

I created a #ManyToMany relation between two table, without have a relational entity through them.
Something like this:
#Entity
public class Category{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CATEGORY_ID", unique = true, nullable = false)
private Long categoryId;
}
#Entity
public class Content {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CONTENT_ID", unique = true, nullable = false)
private Long contentId;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(
name="CATEGORIES_CONTENTS",
joinColumns={
#JoinColumn(name="CONTENT_ID",
referencedColumnName="CONTENT_ID",
nullable = false)},
inverseJoinColumns={
#JoinColumn(name="CATEGORY_ID",
referencedColumnName="CATEGORY_ID",
nullable = false)
})
private List<Category> categories;
}
Now, after the end of the project, I have the need to add an extra field in the relational table to handle a new feature.
Is it possible to do that, without create the related entity? I don't want to change a lot of things.
I already read this good article: Can add extra field(s) to #ManyToMany Hibernate extra table?
but it isn't what I need.
Tanks a lot, Davide.

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.

Categories