JPA: Composite Primary and Foreign Keys - java

I have situation where I have some legacy tables.
Assets, with composite PK (assetId, fiscalId, recordType)
AssetSystems, with composite PK (assetId, fiscalId, systemId)
And they have a relationship.
Asset 1 - 1 AssetSystems
where, Systems FK (assetId, fiscalId)
Here's my code.
Asset.java
#Entity
#Table(name="Assets")
public class Asset implements Serializable {
#EmbeddedId
private AssetKey compositeKey;
#OneToOne(fetch=FetchType.LAZY, mappedBy="asset")
private AssetSystem assetSystem;
// other fields and setters - getters
}
AssetKey.java
#Embeddable
public class AssetKey implements java.io.Serializable {
#Column(name="assetID")
private String assetID;
#Column(name="fiscalPeriodID")
private BigInteger fiscalPeriodID;
#Column(name="recordType")
private String recordType;
// setter - getter
}
AssetSystem.java
#Entity
#Table(name="AssetSystems")
public class AssetSystem implements Serializable {
#EmbeddedId
private AssetSystemKey compositeKey;
#OneToOne
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name="assetID", referencedColumnName="assetID"),
#PrimaryKeyJoinColumn(name="fiscalPeriodID", referencedColumnName="fiscalPeriodID")
})
private Asset asset;
// other fields and setter - getter
}
AssetSystemKey.java
#Embeddable
public class AssetSystemKey implements Serializable {
#Column(name="assetID")
private String assetID;
#Column(name="fiscalPeriodID")
private BigInteger fiscalPeriodID;
#Column(name="systemID")
private BigInteger systemID;
// setter - getter
}
When I try to access the DB, I get this exception
java.lang.IllegalArgumentException:
org.hibernate.TypeMismatchException: Provided id of the wrong type for
class com.wb.adapter.model.AssetSystem. Expected: class
com.wb.adapter.model.AssetSystemKey, got class
com.wb.adapter.model.AssetKey at
org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:274) at
com.wb.adapter.main.AssetDbReader.readDB(AssetDbReader.java:47) at
com.wb.adapter.main.AssetDbReader.main(AssetDbReader.java:30) Caused
by: org.hibernate.TypeMismatchException: Provided id of the wrong type
for class com.wb.adapter.model.AssetSystem. Expected: class
com.wb.adapter.model.AssetSystemKey, got class
com.wb.adapter.model.AssetKey
I have also tried replacing the PrimaryKeyJoinColumns with simple JoinColumn which results in this exception.
Caused by: org.hibernate.AnnotationException:
referencedColumnNames(assetID, fiscalPeriodID) of
com.wb.adapter.model.AssetSystem.asset referencing
com.wb.adapter.model.Asset not mapped to a single property
I am new to JPA and not sure what am I doing wrong. Also, is there a better way to model the above mentioned tables in JPA, which will in effect resolve this issue.
Many thanks in advance.
Sahil

Try to replace #PrimaryKeyJoinColumns with #JoinColumns and add #MapsId("compositeKey") for Asset reference.

Related

JPA #Id on #OneToOne Entity with a Composite Key

I have an #Entity class, with an #Id annotation and a #OneToOne annotation on the same field. Usually this would not be a problem, but the entity class of the field with these annotations uses a composite key. This is causing more complications than I anticipated.
Here is the entity class that is posing the problem:
#Entity
public class ReportDetails implements Serializable {
#Id
#OneToOne
private MachineLine machineLine;
}
And here is the MachineLine entity class that is being used as an ID in ReportDetails:
#Entity
#IdClass(MachineLine.MachineLineKey.class)
public class MachineLine {
#Id
#ManyToOne
private Machine machine;
#Id
private long lineIndex;
public static class MachineLineKey implements Serializable {
private Machine machine;
private long lineIndex;
}
}
I have left out any extra fields and the getters and setters from these class definitions, to save space.
When I try to run my application it gives the following exception:
java.lang.IllegalArgumentException: This class [class ReportDetails] does not define an IdClass
When I put an #IdClass annotation on ReportDetails it then requires defining the individual fields of whatever class I define in #IdClass, like in MachineLine. However, I am trying to avoid doing this, in favour of having the whole MachineLine entity returned whenever a ReportDetails entity is retrieved from the database.
Is there a way of having MachineLine as the ID field of ReportDetails, without having to define extra fields within ReportDetails?
This is what JPA calls a "derived identity". You might try something like this:
ReportDetails:
#Entity
public class ReportDetails implements Serializable {
// all attributes map by the relationship: AttributeOverride is not allowed
#EmbeddedId
private MachineLine.Id id;
#MapsId
#JoinColumns({
#JoinColumn(name="machineId", referencedColumnName="machineId"),
#JoinColumn(name="machineLineIndex", referencedColumnName="index")
})
#OneToOne
private MachineLine machineLine;
// ...
}
MachineLine:
#Entity
public class MachineLine {
#EmbeddedId
private Id id;
#MapsId("machineId") // maps machineId attribute of embedded id
#ManyToOne
private Machine machine;
// ...
#Embeddable
public static class Id implements Serializable {
private long machineId; // corresponds to PK type of Machine
private long index;
// ...
}
}
Machine:
#Entity
public class Machine {
#Id
private long id;
#OneToMany(mappedBy = "machine")
private List<MachineLine> lines;
// ...
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.

Spring and JPA 2.0 - Composite key in many to many relationship with extra column

I have two entities Foo and Bar in many-to-many relationship. The joining entity is FooBar, and since this entity has another property (its own id), I used #ManyToOne on the owner side (FooBar) and #OneToMany in dependent entities (Foo and Bar). How to create a FooBarRepository extending CrudRepository without the explicit composite key field inside FooBar? Ideally, I don't want to change the members of my FooBar class.
I tried to use #IdClass, but I don't want to have fields fooId and barId inside FooBar and I am getting this exception:
Caused by: org.hibernate.AnnotationException: Property of #IdClass not found in entity com.nano.testers.test.FooBar: barId
I also tried to follow the documentation of IdClass and reference columns by name explicitly, but I failed (maybe the solution lies somewhere here?)
The names of the fields or properties in the primary key class and the primary key fields or properties of the entity must correspond and their types must be the same.
I tried to change the names of fields inside Foo and Bar to just id, so that they would be referenced as foo_id and bar_id in the joining table, but the exception was the same.
I don't want to use #EmbeddedId, if that means I need to have a field of FooBarPk type inside the FooBar class.
#Entity
public class Foo {
#Id
private Long fooId;
#OneToMany(mappedBy = "foo", cascade = CascadeType.ALL)
private Set<FooBar> foobars;
}
#Entity
public class Bar {
#Id
private Long barId;
#OneToMany(mappedBy = "bar", cascade = CascadeType.ALL)
private Set<FooBar> foobars;
}
#Entity
//#IdClass(FooBarPk.class)
public class FooBar implements Serializable {
#Id
private Long fooBarId;
#Id
#ManyToOne
#JoinColumn
private Foo foo;
#Id
#ManyToOne
#JoinColumn
private Bar bar;
}
public class FooBarPk implements Serializable {
private Long fooId;
private Long barId;
private Long fooBarId;
}
public interface FooBarRepository extends CrudRepository<FooBar, FooBarPk> {
}
It looks like the names of the fields in the composite key class have to be the same as names of the referenced entities. I think these names don't follow the clean code principles, but I will have to live with this solution for now.
public class FooBarPk implements Serializable {
private Long foo;
private Long bar;
private Long fooBarId;
}

Using Ebean View `#View` without duplicating models

I am trying to create Ebean views on tables based on a simple join and I am running into issues when I try to extend the Model for the base table.
The Views fields and the Models fields are the exact same.
My table Model looks like this:
#Entity
#Table(name = "assets")
public class Asset extends EnvironmentModel<Integer> {
#Id
#Column
#PrimaryKey
#Attribute(index = 0)
private int assetId;
#Column
#Attribute(index = 1)
private String make;
etc...
}
That works just fine.
Now what I am trying to to do with the View is:
#View(name = "assets_view")
public class AssetView extends Asset {
}
I thought I might be able to do this because the AssetView and the Asset having the same exact fields.
When I do it this way I get the exception:
Caused by: javax.persistence.PersistenceException: models.asset.AssetView is NOT an Entity Bean registered with this server?
So my next attempt was to add the #Entity annotation to the View class. e.g.
#Entity
#View(name = "assets_view")
public class AssetView extends Asset {
}
I get the following exception when compiling:
Error injecting constructor, java.lang.IllegalStateException: Checking class models.asset.AssetView and found class models.asset.Asset that has #Entity annotation rather than MappedSuperclass?
But I can't remove the #Entity annotation from my Asset class because I need that to do inserts.
My questions is:
Is there any way to a have a view and a table share the same model, so I can query from the view and insert/update into the table?
Ok, I found an answer and I don't know if this is obvious.
Basically, I just made my base class a #MappedSuperClass e.g.
#MappedSuperclass
public class _Asset extends EnvironmentModel<Integer> {
#Id
#Column
#PrimaryKey
#Attribute(index = 0)
private int assetId;
#Column
#Attribute(index = 1)
private String make;
etc..
}
Then I extended my Asset table and AssetView from that Mapped super class e.g.
#Entity
#Table(name = "assets")
public class Asset extends _Asset {
}
--
#Entity
#View(name = "assets_view")
public class AssetView extends _Asset {
public static final Model.Find<Integer, AssetView> finder = new Model.Finder<>(AssetView.class);
}

Hibernate: Using part of composite FK in another entity PK

im relative new to Hibernate Mappings im trying to achieve this functionality between the class Post and Comentario without luck
Relational model
#Embeddable
public class PostPK implements Serializable {
#Column(name="idPost")
private int postID;
#Column(name="idUsuario")
private int userIDFK;
-------------------------------
#Entity
#Table(name="Post")
public class Post {
#EmbeddedId
private PostPK id;
#ManyToOne
#MapsId(value="userIDFK")
#JoinColumn(name="idUsuario")
private Usuario usuario;
#OneToMany(mappedBy="post")
private List<Comentario> comentarios;
#Column(name="titulo")
private String titulo;
-----------------------------------
#Embeddable
public class ComentarioPK implements Serializable{
#Column(name="idComentario")
private int comentarioId;
#Column(name="idPost")
private int postIdFK;
---------------------------
#Entity
#Table(name="Comentario")
public class Comentario {
#EmbeddedId
private ComentarioPK id;
#ManyToOne
#MapsId("postIdFK")
#JoinColumn(name="idPost",referencedColumnName="idPost")
private Post post;
#Column(name="texto")
private String texto;
without mapping comentario and its fields in Post its working fine but when i decide to map it i get this error
Unable to find column reference in the #MapsId mapping: idUsuario
is it not finding the idUsuario column in Comentario table? i dont want to add it , i can achieve joins in mysql but i dont know how to do it in Hibernate
#MapsId annotation is used to map the primary key fields of the parent entity with the child entity(with the same name).
In your case your are having composite primary key in your parent entity but in child entity you want to refer only one field of it.
PostPK has two fields : idPost and idUsuario. But in Comentario class when your are specifying ManyToOne relationship you are mentioning single column in #JoinColumn(which is idPost) and no field for idUsuario is available in your mapping. But as per the behavior of #MapsId annotation both the fields(idPost and idUsuario) are expected in Comentario class.
Thus, in your case #MapsId annotation won't work

Mapping Multiple Classes to a Table in Hibernate, Without a DTYPE Column

I have two hibernate classes: a base class, and an extended class that has additional fields. (These fields are mapped by other tables.)
For example, I have:
#Entity
#Table(name="Book")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
public class B extends A {
public String node_ID;
// ...
}
public class Node {
public String ID; // maps to B.node_ID
// ...
}
How do I map this in Hibernate? The hibernate documentation states three types of inheritence configurations: one table per class, one table with a type column, and a join table -- none of which apply here.
The reason I need to do this is because class A is from generic framework that's reused over multiple projects, and class B (and Node) are extensions specific to one project -- they won't be used again. In the future, I may have perhaps a class C with a house_ID or some other field.
Edit: If I try the above pseudo-code configuration (two entities mapped to the same table) I get an error that the DTYPE column doesn't exist. The HQL has a "where DTYPE="A" appended.
This is possible by mapping the #DiscriminatorColumn and #DiscriminatorValue to the same values for both classes; this can be from any column you use that has the same data regardless of which type (not sure if it works with null values).
The classes should look like so:
#Entity
#Table(name="Book")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="published")
#DiscriminatorValue(value="true")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
#DiscriminatorValue(value="true")
public class B extends A {
public String node_ID;
// ...
}
For anyone who got here like me and does not want to have the dtype column but instead want to use the same table for more than one entity as is I would recommend using this
Basically you can create a Base like this
#MappedSuperclass
public abstract class BaseBook<T extends BaseBook> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
... any other variables, getters + setters
}
#Entity
#Table(name= "book")
public class BookA extends BaseBook<BookA>{
//Default class no need to specify any variables or getters/setters
}
#Entity
#Table(name= "book")
public class BookB extends BaseBook<BookB>{
#Column(name = "other_field")
private String otherFieldInTableButNotMapedInBase
... Any other fields, getter/setter
}
From the above we have created base super class which does not have any entity or table mapping. We then create BookA to be default with the Entity + Table mapping. From there we can create other Entities all extending from BaseBook but pointing to one table

Categories