Hibernate. Generic Composite PK issue - java

I have an issue with composite primary key in Hibernate.
Example:
I have a class that represents base primary key for all instances of this.
public abstract class PrimaryKey implements Serializable { /* ... */ }
It has nothing but implements java.io.Serializable interface and can be used in generics or another class's methods as parameter to narrow an accepted classes.
Another primary key class should inherit it and add your specific fields as keys. For example:
public class PassportPK extends PrimaryKey {
private String number;
private String series;
public PassportPK() {}
public PassportPK(String number, String series) {
this.number = number;
this.series = series;
}
// Getters/setters are below.
}
Then it's used in the appropriate entity like this:
#Entity
#Table(name = "T_PASSPORTS")
#IdClass(PassportPK.class)
public class Passport implements Serializable {
#Id
#Column(name = "F_NUMBER")
private String number;
#Id
#Column(name = "F_SERIES")
private String series;
public Passport() {}
// Getters/setters are below.
}
Everything works fine if I have a deal with entities like this.
But some entities in my project have a simple primary key like int, long, String, and etc.
In this case I'd like to have a generic primary key like this one:
public class SimplePK<T extends Serializable> extends PrimaryKey {
/**
* Represents a simple key field of entity (i.e. int, long, String, ...);
*/
private T id;
public SimplePK() {}
public SimplePK(T id) {
this.id = id;
}
public T getId() {
return this.id;
}
public void setId(T id) {
this.id = id;
}
}
QUESTION: How can I resolve it in annotation mapping way?
p.s. When I'm trying to resolve it like in the previous example (via #IdClass(SimplePK.class) I catch a "org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [application-context.xml]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Property com.testprj.entity.SimplePK.id has an unbound type and no explicit target entity. Resolve this Generic usage issue or set an explicit target attribute (eg #OneToMany(target=) or use an explicit #Type" exception.
p.p.s. I use Hibernate with Spring framework for wiring components.
I would be grateful for any help!

I think that you can't use a generic type inside the id Class.
Used the #IdClass to indicate the Class for the Composite Primary Key, if you want to used only one attribute like primary key you have to use #Id over the declaration and remove #IdClass.
Example:
#Entity
#Table(name = "T_PASSPORTS")
public class Passport implements Serializable {
#Id
private String id; //Or int, long...
public Passport() {}
// Getters/setters are below.
}

Related

How to use "findBy" in an Entity with a Composite PK (Hibernate JPA)

I am learning with bootspring.
findByDate(int date); used to work until I've moved int Date into the inner class.
Now I can save new entries but I can't retrive them byDate
What do I need to change?
#Transactional
public interface ExpirationDAO extends JpaRepository<ExpirationDTO, Long> {
public ExpirationDTO findByDate(int date);
}
and
#Embeddable
public static class IdKey implements Serializable{
#NotNull
int date;
#ManyToOne
ProductDTO product;
public IdKey(){
}
//setters and getters
}
#EmbeddedId
private IdKey id;
#NotNull
int units;
public ExpirationDTO(){
}
//setters and getters
}
throws this exception:
org.springframework.data.mapping.PropertyReferenceException: No property date found for type ExpirationDTO!
You should include name of embedded key class in repository instead of Long.
Try this one (not tested):
public interface ExpirationDAO extends JpaRepository<ExpirationDTO, IdKey> {
public List<ExpirationDTO> findByIdDate(int date);
}
There after findBy Id is yours EmbeddedId and Date is attribute of embeddable class. And one more thing: if you use only part of embedded key, you can't expect only one result...
You should include name of embedded key class in repository and also add an underscore (_)
Tested below:
public interface ExpirationDAO extends JpaRepository<ExpirationDTO, IdKey> {
public List<ExpirationDTO> findByIdKey_Date(Date date);
}

Primary Key Column Name with Spring Data JPA and Hibernate

I have the following setup with Spring Data JPA and Hibernate as the persistence provider. All of my entities inherit from a base class
#MappedSuperclass
public class BaseEntity {
#Id
private id;
#Version
private String version;
//more common fields
}
For example:
#Entity
public class Foo extends BaseEntity {
}
This leads to a primary key column with name "ID" to be generated on the "FOO" table. I would like to change the naming of the primary key column. It should reflect the name of class or table. So it should be "FOO_ID" instead of just "ID".
I know that I could do this statically by using #Column(name = "FOO_ID"). But that would mean I have to do this for every Entity. Is there a more dynamic way to achieve this?
I know this is an old question, but stumbled across this looking for an answer... Eventually found this solution elsewhere:
#Entity
#AttributeOverride(name="id", column=#Column(name="FOO_ID"))
public class Foo extends BaseEntity {
}
All your subClasses will have the same ID column name because of the inheritance, you can specify a common id colum name for all subClasses in the Base entity Class
Why use inheritance then? Just do it without inheritance.
You could use getters/setters to rename your fields
Ex:
class Foo {
private Long id;
public Long getFooId() {
return this.id;
}
public void setFooId(Long fooId) {
this.id = fooId;
}
}

How to create a generic entity model class that supports generic id including auto generated ids?

I have three kinds of primary keys for tables:
INT auto generated primary key which use AUTO_INCREMENT capacity from database vendor (MySQL)
CHAR(X) primary key to store a user readable value as key (where X is a number and 50 <= X <= 60)
Complex primary keys, composed by 2 or 3 fields of the table.
Also, there are some group of fields that may be present (or not):
version, INT field.
createdBy, VARCHAR(60) field, and lastUpdatedBy, VARCHAR(60) field (there are more fields but these covers a basic example).
Some examples of above:
Table1
id int primary key auto_increment
version int
value char(10)
createdBy varchar(60)
lastUpdatedBy varchar(60)
Table2
id char(60) primary key
shortDescription varchar(20)
longDescription varchar(100)
Table3
field1 int primary key
field2 int primary key
amount decimal(10, 5)
version int
With all this in mind, I need to create a generic set of classes that supports these requirements and allows CRUD operations using Hibernate 4.3 and JPA 2.1.
Here's my current model (getters/setters avoided to shorten the code sample):
#MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected T id;
}
#MappedSuperclass
public abstract class VersionedEntity<T> extends BaseEntity<T> {
#Version
protected int version;
}
#MappedSuperclass
public abstract class MaintainedEntity<T> extends VersionedEntity<T> {
#Column
protected String createdBy;
#Column
protected String lastUpdatedBy;
}
#Entity
public class Table1 extends MaintainedEntity<Long> {
#Column
private String value;
}
#Entity
public class Table2 extends BaseEntity<String> {
#Column
private String shortDescription;
#Column
private String longDescription;
}
I'm currently testing save instances of Table1 and Table2. I have the following code:
SessionFactory sf = HibernateUtils.getSessionFactory();
Session session = sf.getCurrentSession();
session.beginTransaction();
Table1 newTable1 = new Table1();
newTable1.setValue("foo");
session.save(newTable1); //works
Table2 newTable2 = new Table2();
//here I want to set the ID manually
newTable2.setId("foo_id");
newTable2.setShortDescription("short desc");
newTable2.setLongDescription("long description");
session.save(newTable2); //fails
session.getTransaction().commit();
sf.close();
It fails when trying to save Table2 and I get the following error:
Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:996)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3887)
The error message is obvious because a CHAR(X) field doesn't have a default value and won't have it (AFAIK). I tried changing the generation strategy to GenerationType.AUTO and got the same error message.
How can I remodel these classes in order to support these requirements? Or even better, how could I provide a generation strategy that depends on the key of the entity I'm saving, which could be auto generated or provided by me?
Involved technologies:
Java SDK 8
Hibernate 4.3.6
JPA 2.1
MySQL and Postgres databases
OS: Windows 7 Professional
Note: the above may (and probably will) change in order to be supported for other implementations of JPA 2.1 like EclipseLink.
Did not try this, but according to Hibernate's api this should not be complicated by creating custom implementation of IdentityGenerator.
It's generate method gets and object for which you are generating the value so you can check the type of the id field and return appropriate value for your primary key.
public class DynamicGenerator implements IdentityGenerator
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
if (shouldUseAutoincrementStartegy(object)) { // basing on object detect if this should be autoincrement or not, for example inspect the type of id field by using reflection - if the type is Integer use IdentityGenerator, otherwise another generator
return new IdentityGenerator().generate(seession, object)
} else { // else if (shouldUseTextKey)
String textKey = generateKey(session, object); // generate key for your object
// you can of course connect to database here and execute statements if you need:
// Connection connection = session.connection();
// PreparedStatement ps = connection.prepareStatement("SELECT nextkey from text_keys_table");
// (...)
return textKey;
}
}
}
Having this simply use it as your generation strategy:
#MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
#Id
#GenericGenerator(name="seq_id", strategy="my.package.DynamicGenerator")
protected T id;
}
For Hibernate 4, you should implement IdentifierGenerator interface.
As above is accepted for Hibernate it should be still possible to create it in more generic way for any "jpa compliant" provider. According to JPA api in GeneratedValue annotation you can provide your custom generator. This means that you can provide the name of your custom generator and you should implement this generator for each jpa provider.
This would mean you need to annotate BaseEntity with following annotation
#MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
#Id
#GeneratedValue(generator="my-custom-generator")
protected T id;
}
Now you need to register custom generator with name "my-custom-generator" for each jpa provider you would like to use.
For Hibernate this is surly done by #GenericGenerator annotation as shown before (adding #GenericGenerator(name="my-custom-generator", strategy="my.package.DynamicGenerator" to BaseEntity class on either id field or BaseEntity class level should be sufficient).
In EclipseLink I see that you can do this via GeneratedValue annotation and registering it via SessionCustomizer:
properties.put(PersistenceUnitProperties.SESSION_CUSTOMIZER,
"my.custom.CustomIdGenerator");
public class CustomIdGenerator extends Sequence implements SessionCustomizer {
#Override
public Object getGeneratedValue(Accessor accessor,
AbstractSession writeSession, String seqName) {
return "Id"; // generate the id
}
#Override
public Vector getGeneratedVector(Accessor accessor,
AbstractSession writeSession, String seqName, int size) {
return null;
}
#Override
protected void onConnect() {
}
#Override
protected void onDisconnect() {
}
#Override
public boolean shouldAcquireValueAfterInsert() {
return false;
}
#Override
public boolean shouldOverrideExistingValue(String seqName,
Object existingValue) {
return ((String) existingValue).isEmpty();
}
#Override
public boolean shouldUseTransaction() {
return false;
}
#Override
public boolean shouldUsePreallocation() {
return false;
}
public void customize(Session session) throws Exception {
CustomIdGenerator sequence = new CustomIdGenerator ("my-custom-generator");
session.getLogin().addSequence(sequence);
}
}
Each provider must give a way to register id generator, so you would need to implement and register custom generation strategy for each of the provider if you want to support all of them.
You can "workaround" this forcing derived class to implement method which will ensure the Id is assigned and annotate this method with #PrePersist. You can provide default implementation for classes for which the Id will be auto generated.
Somethig like:
#MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected T id;
#PrePersist
public void ensureIdAssigned() {
ensureIdAssignedInternal();
}
public abstract void ensureIdAssignedInternal();
}
#MappedSuperclass
public abstract class AutoIdMaintaintedEntity<T> extends MaintainedEntity<T> { // provide default implementation for Entities with Id generated by #GeneratedValue(strategy=GenerationType.IDENTITY) on BaseEntity superclass
public void ensureIdAssignedInternal() {
// nothing here since the Id will be automatically assigned
}
}
#Entity
public class Table1 extends AutoIdMaintaintedEntity<Long> {
#Column
private String value;
}
#Entity
public class Table2 extends BaseEntity<String> {
#Column
private String shortDescription;
#Column
private String longDescription;
public void ensureIdAssignedInternal() {
this.id = generateMyTextId();
}
private String generateMyTextId() {
return "text id";
}
}
Inheritance hierarchies fight ORM.
So keep things simple and stay a little closer to the database implementation. Don't map a hierarchy of abstract superclasses for this, but embed annotated POJO's for chunks of shared columns. They may well turn out to be handy to work with in the rest of your code as well.
Create #Embeddable classes for the shared fields, and make the class for your composite ID #Embeddable too.
#Embeddable
public class Maintained implements Serializable{
private String maintainedBy;
private String updatedBy;
// getters and setters
}
#Embeddable
public class CompositeId implements Serializable{
#Column
private int id1;
#Column
private int id2;
...
}
The simplest version of your implementation classes then look like this:
#Entity
public class Table1 {
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Id
protected Long id;
#Version
private String version;
#Embedded
private Maintained maintained;
...
public Maintained getMaintained(){
return maintained;
}
}
For the String ID, no auto generation:
#Entity
public class Table2 {
#Id
private String id;
#Column
private String shortDescription;
#Column
private String longDescription;
...
}
And the composite ID as #EmbeddedId:
#Entity
public class Table3 {
#EmbeddedId
private CompositeId id;
#Version
private String version;
#Column
private int amount;
...
}
As an extra benefit, you can mix and match adding more of these traits if you like, since you're no longer constrained by the inheritance tree.
(But you can keep the hierarchy in place containing getters and setters and default delegates if existing code relies on it and/or benefits from it.)

Hibernate Exception: Unable to find properties ... in entity annotated with #IdClass

I have this composite key class:
#Embeddable
public class TaskEntityId implements Serializable {
private static final long serialVersionUID = 1L;
public String objectUuid;
#Column(name="DOMAIN_OBJECT_UUID", nullable=false, length=36)
public String getObjectUuid() { return objectUuid; }
public void setObjectUuid(String uuid) { this.objectUuid = uuid; }
public String taskName;
#Column(name = "TASK_NAME", nullable=false, length=32)
public String getTaskName() { return taskName; }
public void setTaskName(String taskName) { this.taskName = taskName; }
public Date createdTimestamp = new Date();
#Column(name = "CREATED_TS", nullable=false, updatable=false)
#Temporal(TemporalType.TIMESTAMP)
public Date getCreatedTimestamp() { return createdTimestamp; }
public void setCreatedTimestamp(Date createdTimestamp) { this.createdTimestamp = createdTimestamp; }
#Override
public boolean equals(Object o) {...}
#Override
public int hashCode() {...}
#Override
public String toString() {...}
}
Which is used in this entity class:
#Entity
#Table(name = "TASK")
#IdClass(TaskEntityId.class)
public class TaskEntity implements Serializable {
#EmbeddedId
TaskEntityId id;
public TaskEntityId getId() { return id; }
public void setId(TaskEntityId id) { this.id = id; }
...
I also added to this class some extra getters based on suggestions to other similar questions:
public String getObjectUuid() { return id.getObjectUuid(); }
public String getTaskName() { return id.getTaskName(); }
public Date getCreatedTimestamp() { return id.getCreatedTimestamp(); }
Still, I get this exception:
Error in custom provider, org.hibernate.AnnotationException: Unable to find properties (createdTimestamp, objectUuid, taskName) in entity annotated with #IdClass:c.m.d.p.TaskEntity
The solution is to remove the
#IdClass(TaskEntityId.class)
annotation from the TaskEntity class. And I also needed an empty default constructor.
To map a composite key, you can use the EmbeddedId or the IdClass annotations. I know this question is not strictly about JPA but the rules defined by the specification also applies. So here they are:
2.1.4 Primary Keys and Entity Identity
...
A composite primary key must correspond to either a single persistent field or property or to a set of such fields or properties as described below. A primary key class must be defined to represent a composite primary key. Composite primary keys typically arise when mapping from legacy databases when the database key is comprised of several columns. The EmbeddedId and IdClass annotations are used to denote composite primary keys. See sections 9.1.14 and 9.1.15.
...
The following rules apply for composite primary keys:
The primary key class must be public and must have a public no-arg constructor.
If property-based access is used, the properties of the primary key class must be public or protected.
The primary key class must be serializable.
The primary key class must define equals and hashCode methods. The semantics of value equality for these methods must be consistent with the database equality for the database types to which the key is mapped.
A composite primary key must either be represented and mapped as an embeddable class (see Section 9.1.14, “EmbeddedId Annotation”) or must be represented and mapped to multiple fields or properties of the entity class (see Section 9.1.15, “IdClass Annotation”).
If the composite primary key class is mapped to multiple fields or properties of the entity class, the names of primary key fields or properties in the primary key class and those of the entity class must correspond and their types must be the same.
With an IdClass
The class for the composite primary key could look like (could be a static inner class):
public class TimePK implements Serializable {
protected Integer levelStation;
protected Integer confPathID;
public TimePK() {}
public TimePK(Integer levelStation, String confPathID) {
this.id = levelStation;
this.name = confPathID;
}
// equals, hashCode
}
And the entity:
#Entity
#IdClass(TimePK.class)
class Time implements Serializable {
#Id
private Integer levelStation;
#Id
private Integer confPathID;
private String src;
private String dst;
private Integer distance;
private Integer price;
// getters, setters
}
The IdClass annotation maps multiple fields to the table PK.
With EmbeddedId
The class for the composite primary key could look like (could be a static inner class):
*
#Embeddable
public class TimePK implements Serializable {
protected Integer levelStation;
protected Integer confPathID;
public TimePK() {}
public TimePK(Integer levelStation, String confPathID) {
this.id = levelStation;
this.name = confPathID;
}
// equals, hashCode
}
*
And the entity:
#Entity
class Time implements Serializable {
#EmbeddedId
private TimePK timePK;
private String src;
private String dst;
private Integer distance;
private Integer price;
//...
}
The #EmbeddedId annotation maps a PK class to table PK.
Differences:
From the physical model point of view, there are no differences
EmbeddedId somehow communicates more clearly that the key is a composite key and IMO makes sense when the combined pk is either a meaningful entity itself or it reused in your code.
#IdClass is useful to specify that some combination of fields is unique but these do not have a special meaning.
They also affect the way you write queries (making them more or less verbose):
with IdClass
select t.levelStation from Time t
with EmbeddedId
select t.timePK.levelStation from Time t

Hibernate composite key which are foreigen key to another table

I have two table Part and SubPart. Part table has general fields like id, name, desc etc. The SubPart table has part_id, sub_part_id as composite key. Both of these columns are referring to Part table and has a one to many mapping for each of them, like for each part_id in the Part table there can be multiple entries in SubPart table for both the columns. I'm having problem defining the composite key for the SubPart table. I tried the Embedded tag but its not working. How can I address this problem. Thanks a lot.
Part table like this.
#Entity
#Table(name="Part")
public class Part {
#Id
#GeneratedValue
#Column(name="Part_Id")
private int id;
#Column(name="Part_Number")
private String partNumber;
#Column(name="Part_Name")
private String partName;
}
Sub Part Table
#Entity
#Table(name="SubPart")
public class SubPart {
// part and subPart combination is the compound key here.
#ManyToOne
#JoinColumn(name="Part_Id")
private Part part;
#ManyToOne
#JoinColumn(name="Sub_Part_Id")
private Part subPart;
#Column(name="Quantity")
private Integer quantity;
}
You said
I'm having problem defining the composite key for the SubPart table
When you have a compound primary key, you must define a class (Usually a static inner class) which defines your compound primery key (Just an advice: because Hibernate makes use of proxies, prefer to put your annotated mapping on the getter's instead of field members)
/**
* When both entity class and target table SHARE the same name
* You do not need #Table annotation
*/
#Entity
public class SubPart implements Serializable {
#EmbeddedId
private SubPartId subPartId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="PART_ID", insertable=false, updateable=false)
private Part part;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="SUP_PART_ID", insertable=false, updateable=false)
private SubPart subPart;
/**
* required no-arg constructor
*/
public SubPart() {}
public SubPart(SubPartId subPartId) {
this.subPartId = subPartId;
}
// getter's and setter's
/**
* It MUST implements Serializable
* It MUST overrides equals and hashCode method
* It MUST has a no-arg constructor
*
* Hibernate/JPA 1.0 does not support automatic generation of compound primary key
* You SHOULD set up manually
*/
#Embeddable
public static class SubPartId implements Serializable {
#Column(name="PART_ID", updateable=false, nullable=false)
private Integer partId;
#Column(name="SUB_PART_ID", updateable=false, nullable=false)
private Integer subPartId;
/**
* required no-arg constructor
*/
public SubPartId() {}
public SubPartId(Integer partId, Integer subPartId) {
this.partId = partId;
this.subPartId = subPartId;
}
// getter's and setter's
#Override
public boolean equals(Object o) {
if(!(o instanceof SubPartId))
return null;
final SubPartId other = (SubPartId) o;
return new EqualsBuilder().append(getPartId(), other.getPartId())
.append(getSubPartId(), other.getSubPartId())
.isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder().append(getPartId())
.append(getSubPartId())
.toHashCode();
}
}
}
Notice Part and SubPart mapping has been marked as insertable=false, updateable=false Because the mapping has been defined in the compound primary key. Hibernate does not allow you mapping two properties WITH THE SAME COLUMN unless you mark insertable=false, updateable=false. Otherwise you will see this nice exception
Should be marked with insertable=false, updateable=false
I think I would declare a field of type Map<Part,SubPart> in class Part and declare it as #OneToMany.
Actually, in this particular case, it could event be a Map<Part,Integer> because the only other field is the quantity. The SubPart class is not needed.

Categories

Resources