Hibernate MS SQL Join issue - java

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.

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

Hibernate unexpected deletes when finding entities

We have a Java ee application running on JBoss 6.4 GA using JPA and Hibernate with the following entities:
#Entity
#SequenceGenerator(name = "sequence", sequenceName="SEQ_CAMPAIGNS_ID",allocationSize = 1)
#Table(name = "CAMPAIGN")
public class CampaignEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence")
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "IS_ACTIVE", nullable = false)
private boolean active;
#Column(name = "START_DATE", nullable = false)
private Date startDate;
#Column(name = "END_DATE", nullable = false)
private Date endDate;
#Column(name = "LEGAL_ENTITY_ID", nullable = false)
private Integer legalEntityId;
#Column(name = "DEPARTMENT", nullable = false)
#Enumerated(value = EnumType.STRING)
private Department department;
#Column(name = "CATEGORY", nullable = false)
#Enumerated(value = EnumType.STRING)
private Category category;
#Embedded
CampaignConditionsEntity campaignConditions;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "campaign", orphanRemoval = true)
#OrderBy
private List<CodeEntity> campaignCodes;
public CampaignEntity() {
}
And the following CampaignConditionsEntity:
#Embeddable
public class CampaignConditionsEntity implements Serializable {
private static final String CAMPAIGN_ID = "CAMPAIGN_ID";
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "CAMPAIGN_COND_TRIP_TYPE", joinColumns = #JoinColumn(name = CAMPAIGN_ID))
private Set<TripTypeConditionEntity> tripTypeConditions;
And the following CodeEntity:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#SequenceGenerator(name = "sequence", sequenceName = "SEQ_CODES_ID", allocationSize = 1)
public abstract class CodeEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence")
#Column(name = "ID", nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CAMPAIGN_ID")
private CampaignEntity campaign;
#OneToOne(mappedBy = "code", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, orphanRemoval = true)
private DiscountEntity discount;
#Column(name = "MAX_USAGES", nullable = false)
private Integer maxUsages;
#Column(name = "UNLIMITED_USAGES", nullable = false)
private boolean unlimitedUsages;
#Column(name = "NEGATIVE_SH", nullable = false)
private boolean negativeSH;
#Column(name = "UNIQUE_BUYER", nullable = false)
private boolean uniqueBuyer;
#Column(name = "START_DATE")
private Date startDate;
#Column(name = "END_DATE")
private Date endDate;
#Embedded
private CodeConditionsEntity codeConditions;
public CodeEntity() {
}
This is the CodeConditionsEntity:
#Embeddable
public class CodeConditionsEntity implements Serializable {
private static final String CODE_ID = "CODE_ID";
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "CODE_COND_TRIP_TYPE", joinColumns = #JoinColumn(name = CODE_ID))
private Set<TripTypeConditionEntity> tripTypeConditions;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "CODE_COND_CARRIERS", joinColumns = #JoinColumn(name = CODE_ID))
private Set<CarrierConditionEntity> carrierConditions;
This is the CarrierConditionEntity:
#Embeddable
public class CarrierConditionEntity implements Serializable {
#Column(name = "CARRIER", nullable = false, length = 3)
private String carrierCode;
#Column(name = "IS_INCLUDED", nullable = false)
private boolean included;
The problem is that in the logs we are finding unexpected deletes when the only operation that we are doing are finds of particular campaign entities.
In the production logs we find the following deletes
Hibernate: delete from CODE_COND_CARRIERS where CODE_ID=? and CARRIER=? and IS_INCLUDED=?
do you have any suggestion?
thanks
I have some suggestions :)
Be aware of what is a Persistence Context (EntityManager instance in JPA terminology / Session in Hibernate one), the entity lifecycle and transaction scope (unit of work)
Do not mutate entity state if you don't expect the changes to be reflected in database, or at least detach the entity before mutating it.
Mark your transaction as "readOnly" if you only fetch data in the related unit of work. (beware that if you have many "Transactional" methods joining the same physical transaction, the flag is set by the surrounding one and cannot be overridden by inner logical transactions). That way the EntityManager won't be flushed at the end of the transaction and pending changes won't be persisted to the database.
You can track the method triggering the unexpected deletion using an EntityListener on the related entity and printing the current strackTrace (new Throwable().printStackTrace()/ log(new Throwable()) in the PreRemove method
I found where was the problem:
The problem was that the Entities didn't have the equals() and the hashcode() implemented. Also there were entities that have a #PostLoad that modified the entity after loading it from database. Then in this situation Hibernate though that there was a change in those entities that didn't have the equals and the hashcode, and then it delete all of them and inserted again in the database (to have the same entities before the query)
Adding the equals and hashcode methods and deleting postload removed the unexpected deletes and inserts from the logs.
regards

Unidirectional OneToMany with Composite Primary key not persisted

I have an ER relationship from a legacy DB (MS SQL Server based as below
The way that I'm currently trying to convert this to the JPA 2.1 style is as below
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Orders implements Serializable {
#Id
#GeneratedValue
#Column(name = "OrderNumber", nullable = false)
private Integer orderNumber;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(
name = "OrderHistory",
joinColumns = {
#JoinColumn(name = "OrderNumber", referencedColumnName = "OrderNumber", nullable = false)
}
)
private List<OrderHistory> orderHistory;
----Other properties, Getters and Setters
}
#Entity
#PrimaryKeyJoinColumn(name = "OrderNumber")
public class SpecialOrders extends Orders implements Serializable {
#JoinColumn(name = "OrderNumber", referencedColumnName = "OrderNumber", nullable = false)
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(FetchMode.SELECT)
#OrderBy("sequenceNumber ASC")
private List<Items> items;
----Other properties, Getters and Setters
}
#Entity
#IdClass(ItemsPK.class)
public class Items implements Serializable {
#Id
#Column(name = "OrderNumber", nullable = false, insertable = false, updatable = false)
private Integer orderNumber;
#Id
#Column(name = "SequenceNumber", nullable = false)
private Integer sequenceNumber;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(
name = "CustomOptions",
joinColumns = {
#JoinColumn(name = "OrderNumber", referencedColumnName = "OrderNumber", nullable = false),
#JoinColumn(name = "SequenceNumber", referencedColumnName = "SequenceNumber", nullable = false)
}
)
private List<CustomOptions> customOptions;
----Other properties, Getters and Setters
}
#Entity
public class ItemsPK implements Serializable {
#Id
#Column(name = "OrderNumber", nullable = false, insertable = false, updatable = false)
private Integer orderNumber;
#Id
#Column(name = "SequenceNumber", nullable = false)
private Integer sequenceNumber;
----Getters and Setters
}
#Entity
#IdClass(CustomOptionsPK.class)
public class CustomOptions implements Serializable {
#Id
#Column(name = "OrderNumber", nullable = false, insertable = false, updatable = false)
private Integer orderNumber;
#Id
#Column(name = "SequenceNumber", nullable = false)
private Integer sequenceNumber;
#Id
#Column(name = "OptionNumber", nullable = false)
private Integer optionNumber;
----Other properties, Getters and Setters
}
public class CustomOptionsPK implements Serializable {
#Id
#Column(name = "OrderNumber", nullable = false, insertable = false, updatable = false)
private Integer orderNumber;
#Id
#Column(name = "SequenceNumber", nullable = false)
private Integer sequenceNumber;
#Id
#Column(name = "OptionNumber", nullable = false)
private Integer optionNumber;
----Getters and Setters
}
With the above code, I see that hibernate does below
INSERTs into Orders and gets the GeneratedId for OrderNumber
INSERTs into SpecialOrders, using the orderNumber retrieved above.
Attemps to INSERT into Items table, with a NULL value in the orderNumber and then fails because the orderNumber is a NOT NULL column.
Subsequently, If add a "Simple" primary key to Items table and make the orderNumber as a NULLable column, then the below happens:
INSERTs into Orders and gets the GeneratedId for OrderNumber
INSERTs into SpecialOrders, using the orderNumber retrieved above.
INSERTs into Items table with orderNumber as NULL value and gets the generated id of the Items table row.
Updates the row of Items table with the orderNumber from parent, using the retrieved id for Items.
Attemps to INSERT into CustomOptions table, with a NULL value in the orderNumber and then fails because the orderNumber is a NOT NULL column.
As per the above sequence, it seems that:
Composite Primary key doesnt seem to be working correctly or not supported.
Hibernate is handling the OneToMany relationship inefficiently by issuing an INSERT followed by an UPDATE, instead of just an insert.
Any idea if my understanding is correct? The only way of fixing this issue seems to be that I need to remove the composite primary key and replace it with a simple one.

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

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