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.
Related
I have 2 models with one to one relationship
#Entity
#Table(name = "Form_Item_Production")
public class FormItemProduction {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id", nullable = false)
private Long itemId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "shift_lookup_id", insertable = false, updatable = false)
private AppLookup appLookup;
getter and setter
}
The other one
#Entity
#Table(name = "App_Lookup")
public class AppLookup {
#Id
#GeneratedValue
#Column(name = "Lookup_Id", nullable = false)
private Long lookupId;
#Column(name = "Lookup_Name", length = 30, nullable = false)
private String lookupName;
getter and setter
}
When I try to persist to save the formitemproduction values
public boolean insertItem(List<FormItemProduction> f) {
for (FormItemProduction i : f) {
System.out.println("A" + i.getAppLookup().getLookupId()); // prints the correct id of applookup
i.setItemId(null);
entityManager.persist(i);
}
entityManager.flush();
return true;
}
I get this error
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.mamee.factory.security.entity.AppLookup
From my understanding this is unidirectional one to one mapping so I don't quite understand why I'm getting the error detached?
You have itemId which can't be null.
#Column(name = "item_id", nullable = false)
private Long itemId;
and you actually set itemId to null
i.setItemId(null);
So this line
entityManager.persist(i);
Is not able to persist your data.
Fixed by changing from:
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "shift_lookup_id", insertable = false, updatable = false)
private AppLookup appLookup;
to:
#OneToOne
#JoinColumn(name = "shift_lookup_id", insertable = true, updatable = true)
private AppLookup appLookup;
You have set cascade to ALL:
#OneToOne(cascade = CascadeType.ALL)
Which means you are cascading all operations down to the related field AppLookup.
You can set cascade to none, and you will no longer get your error, but no db operations will be executed for AppLookup field.
The "TypeMismatchException: Provided id of the wrong type" error thrown when tried to merge detached entity. It works if the object wasn't detached. It also works if ids aren't #EmbeddedId.
A sample repo can be found here https://github.com/joes-code/hibernate-map
// Asset.java
#Entity
#Table(name = "asset")
public class Asset {
#EmbeddedId
private AssetId id;
#Column(name = "asset_cost"
private BigDecimal price;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "asset_id", referencedColumnName = "asset_id", nullable = false, insertable = false, updatable = false, foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT))
private AssetDetail assetDetail;
}
// AssetId.java
#Embeddable
public class AssetId {
#Column(name = "asset_id", nullable = false)
private Integer assetId;
}
// AssetDetail.java
#Entity
#Table(name = "asset_detail")
public class AssetDetail {
#EmbeddedId
private AssetDetailId id;
#Column(name = "description", length = 35)
private String description;
}
// AssetDetailId.java
#Embeddable
public class AssetDetailId {
#Column(name = "asset_id", nullable = false)
private Integer assetId;
}
I'm using Hibernate 5.4.3.Final
Any ideas what I did wrong? It seems that Hibernate is assuming Asset and AssetDetail share the same Id class?
I am having trouble mapping my database domain model to the program entities in one case where the entity is essentially a join table (a period) which combines two other entities (a timeslot and a day). Another entity (a lesson) then has a reference to this period entity, determining when it occurs.
When I try to save a lesson with a new period using saveOrUpdate(lesson) hibernate throws an IdentifierGenerationException
org.hibernate.id.IdentifierGenerationException: null id generated for:class com.trials.domain.Period
The database looks like below (not the real database, just the key tables and columns)
In the java hibernate model, I have used an embedded id for the primary key of the period class and the lesson class then has a reference to a period.
Period.java
#Entity
#Table(name = "period")
public class Period{
#EmbeddedId
private PeriodId periodId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "day_idday", nullable = false, insertable = false, updatable = false)
private Day day;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "timeslot_idtimeslot", nullable = false, insertable = false, updatable = false)
private Timeslot timeslot;
//constructors, getters, setters, hashcode, and equals
}
And the embedded id just has the primary key columns:
PeriodId.java
#Embeddable
public class PeriodId implements Serializable {
#Column(name = "timeslot_idtimeslot")
private int timeslotId;
#Column(name = "day_idday")
private int dayId;
//constructors, getters, setters, hashcode, and equals
}
Then there is the lesson class that uses the period defined as:
Lesson.java
#Entity
#Table(name = "lesson")
public class Lesson {
#Id
#Column(name = "idlesson")
private int lessonId;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumns({#JoinColumn(name = "period_timeslot_idtimeslot", nullable = false, updatable = false), #JoinColumn(name = "period_day_idday", nullable = false, updatable = false)})
private Period period;
//constructors, getters, setters, hashcode, and equals
}
The Timeslot and Day entity classes are both very basic pojos, and their ids use GenerationType.AUTO. So my problems are:
What causes this IdentifierGenerationException
How to avoid it while keeping the same database model
Thanks in advance
Put those guys
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "day_idday", nullable = false, insertable = false, updatable = false)
private Day day;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "timeslot_idtimeslot", nullable = false, insertable = false, updatable = false)
private Timeslot timeslot;
inside the PeriodId class and throw away those ints. I have done a mapping similar to yours this way and it works.
I was able to create the following mapping for my case (scala code) and could totally throw away the #Embeddable class:
#Entity
#Table(name = "payment_order_item", schema = "pg")
#IdClass(classOf[PaymentOrderItem])
final class PaymentOrderItem extends Serializable{
#Id
#ManyToOne
#JoinColumn(name = "order_item_id", referencedColumnName = "id")
var orderItem: OrderItem = _
#Id
#ManyToOne
#JoinColumn(name = "payment_id", referencedColumnName = "id")
var payment: Payment = _
}
So the following should work for you then
#Entity
#Table(name = "period")
#IdClass(Period.class)
public class Period extends Serializable{
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "day_idday", referencedColumnName = "id", nullable = false)
private Day day;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "timeslot_idtimeslot", referencedColumnName = "id", nullable = false)
private Timeslot timeslot;
//constructors, getters, setters, hashcode, and equals
}
On a first glance,
You're missing the generated value annotation in the embedded id class.
#Embeddable
public class PeriodId implements Serializable {
#GeneratedValue
#Column(name = "timeslot_idtimeslot")
private int timeslotId;
#GeneratedValue
#Column(name = "day_idday")
private int dayId;
//constructors, getters, setters, hashcode, and equals
}
I have the following #Entity classes
#Entity
public class Asset {
#Id
private long id;
#Enumerated(EnumType.STRING)
private TagType tagType;
private String tagId;
#OneToOne(cascade = CascadeType.ALL, optional = true, orphanRemoval = true)
#JoinColumns(value = {
#JoinColumn(name = "tagId", referencedColumnName = "tagId", nullable = true, insertable = false, updatable = false),
#JoinColumn(name = "tagType", referencedColumnName = "tagType", nullable = true, insertable = false, updatable = false)})
private TagInfo tagInfo;
...
}
and
#Entity
public class TagInfo {
#Id
private String tagId
#Id
private TagType tagType
...
}
When EclipseLink start using "eclipselink.ddl-generation" = "drop-and-create-tables", it creates the ASSET table with the following constraint:
CONSTRAINT FK_ASSET_TAGTYPE FOREIGN KEY(TAGTYPE,TAGID) REFERENCES TAGINFO(TAGTYPE,TAGID))
Is there anyway to have EclipseLink NOT create the above constraint on the Asset table?
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.