I'm looking for a way to associate a entity with another entity purely through hibernate (no extra relation db column mappings) and not requiring separate DAO calls. I searched around for a solution and the only thing I could find was #Formula
but I could not get it to work. Consider:
#Entity
class CommonEntity {
#MagicAnnotation("maybe with HQL?")
private SuperEntity superEntity;
}
#Entity
class SuperEntity { }
What this means is that sometimes CommonEntity is a SuperEntity and I want to have a getter on the POJO itself so it has access the SuperEntity via a simple get(). Is there any way to do this cleanly so that when I do something like commonEntityDAO.get(1L); where 1L IS a SuperEntity, then the entity will be set?
The tables in the database would look like:
table common_entity ( common_entity_id int primary key, name string );
table super_entity ( super_entity_id int primary key, extra_field string, common_entity_id int );
Since you have an is a relationship, this is a primary candidate for inheritance. Depending on the database representation you like there are multiple inheritance strategies. check this
Can't you simply use #OneToOne? Your database schema looks suitable for it.
#Entity
class CommonEntity {
#OneToOne(mappedBy = "commonEntity")
private SuperEntity superEntity;
}
#Entity
class SuperEntity {
#OneToOne
#JoinColumn(name = "common_entity_id")
private CommonEntity commonEntity;
}
Note that in this case you need a bidirectional relationship with SuperEntity being an owning side, since it holds the foreign key.
Related
I am sorry if the Subject was misleading but here is the scenario that I have.
#Entity
Class A {
#Id
private String id;
#OneToMany
private Set<B> b;
}
#Entity
Class B {
#Id
private String c;
private String d;
}
#Entity
Class C {
#Id
private String e;
private String f;
}
Currently, I am retrieving the entity A with its set of objects from Entity B. The thing is that Entity B and Entity C are not related by foreign key and they need to be joined by their primary keys, so when I retrieve the entity A, in the set of objects, I will get the columns from the entities B and C joined together.
Is there any way to tell Hibernate to join the columns from entities B and C when I try to get the object A ?
In JPA 2.0 for joining related entities join is used. For this FK was needed.
Problem with joining unrelated entities is solved in JPA 2.1 standard, Hibernate 5.1 and above. You can use join on unrelated columns.
Here is the problem i have:
class CurrencyPrice {
#Transient
String pair;
float spotPrice;
Date timeStamp;
}
And I have 3 table the names of which stand for "usd value of euro/gbp/yen respectively": usd_euro, usd_gbp & usd_yen. They all have the same 3 columns: id, spotprice, timestamp.
For some reason i cannot have a single table. The transient instance variable 'pair' will have the following values depending on what it represents: "usd_euro", "usd_gbp" & "usd_yen"
And depending on the value in 'pair' I want to insert a row in one of the tables, eg: if I have the value "usd_yen" in 'pair' then the object should be persisted in usd_yen table.
And when I want to fetch data, I want JPA to decide which table to SELECT from based on the value in 'pair'
This is simple in JDBC but is there a way to do this in JPA?
Thank you.
If I understand your requirements correctly, this might actually be feasible in JPA now (those threads you cite are quite old), if you can use inheritance on your entity and an additional join table and if it's acceptable that the ID for each type is not contiguous.
You could basically define your classes like this then:
#Entity
#Table(name="curr_base") // choose a suitable name
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="currency", discriminatorType=DiscriminatorType.STRING) // your join table needs this column in addition to the id
public abstract class CurrencyPrice {
#Id
private int id;
}
#Entity
#Table(name="usd_euro")
#DiscriminatorValue("usd_euro")
public class UsdEuroPrice extends CurrencyPrice {
float spotPrice;
Date timeStamp;
}
#Entity
#Table(name="usd_gbp")
#DiscriminatorValue("usd_euro")
public class UsdGbpPrice extends CurrencyPrice {
float spotPrice;
Date timeStamp;
}
#Entity
#Table(name="usd_yen")
#DiscriminatorValue("usd_euro")
public class UsdYenPrice extends CurrencyPrice {
float spotPrice;
Date timeStamp;
}
I replicated spotPrice and timeStamp on each subclass so that you don't have to modify your existing table definitions - of course it would be much cleaner to only have them on the superclass/join table.
This mapping allows it for example to perform a EntityManager.persist(new UsdGbpPrice(...)) and have JPA insert a row into the right table. For more information, look here.
I have a 'best practice' question for a scenario.
Scenario:
Multiple entities in a DB, for example, Document, BlogPost, Wiki can be shared by individuals. Instead of creating a share table for each entity, a single Share table is created. The issue is, how to map the share table with different entities?
I have three options, please advise which option is best, and if there is a better option.
Option1:
Create table Shares as:
SHARES
id (unique)
entityId (non DB enforced FK to DOCUMENTS, WIKIS, POSTS etc.)
entityType
sharedBy
sharedWith
sharedDate
Here, entityId will be a FK to documentId, wikiId, postId etc. etc. and entityType will identity what type the entityId is.
This has issues in Hibernate modelling, when creating Share to entity mapping, such as share.getDocument() or share.getWiki() etc.
Option 2:
Create table Shares which only holds share information, and then create resolution tables that tie the share to the entity.
SHARES
id(PK)
sharedBy
sharedWith
sharedDate
shareType (helper field for searches)
SHARES_DOCUMENTS
share_id (unique ID and FK, one to one with SHARES)
document_id (FK to DOCUMENTS)
SHARES_POST
share_id (unique ID and FK, one to one with SHARES)
post_id (FK to POSTS)
more share tables here.
So, hibernate wise, Share can have one to one for each of the share types (like share.getDocument(), share.getPost(), and shareType will identify which relationship is 'active' )
Option 3
Similar to option 1, but create individual columns instead of entity id
SHARES
id (unique ID)
documentId (FK to DOCUMENTS, nullable)
postId (FK to POSTS, nullable)
wikiId (FK to WIKIS, nullable)
sharedBy
sharedWith
sharedDate
sharedType
Here, each column could be mapped to respective entity, but they are nullable. sharedType can identify which relationship is 'active'.
So, the question is , which practice is best, both database wise as well as hibernate mapping (and eventual querying, performance wise).
Thanks
M. Rather
As suggested by TheStijn, after looking into different ways to setup inheritance relationships, I went with 'Single Table per class hierarchy' approach, and ended up with the table like:
SHARES
---------
id PK
shared_by FK to User
shared_with FK to User
shared_Date
document_id nullable FK to Document
post_id nullable FK to Posts
... more ids here to link to more entities
type_discriminator (values, DOCUMENT, POST ... )
On Hibernate/Java side,
One Share abstract class as...
#Entity
#Table(name="SHARES")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE_DISCRIMINATOR", discriminatorType=DiscriminatorType.STRING)
public abstract class Share {
#Id
#Column( name="ID", nullable=false )
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#ManyToOne
#JoinColumn( name="SHARED_BY", nullable=false )
private User sharedBy;
#ManyToOne
#JoinColumn( name="SHARED_WITH", nullable=false )
private User sharedWith;
#Column(name="SHARED_DATE", columnDefinition="TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP", nullable=false)
#Temporal(TemporalType.TIMESTAMP)
private Date sharedDate;
...
}
And two normal classes..
#Entity
#DiscriminatorValue("DOCUMENT")
public class SharedDocument extends Share {
#ManyToOne
#JoinColumn( name="DOCUMENT_ID", nullable=true )
private Document document;
....
}
#Entity
#DiscriminatorValue("POST")
public class SharedPost extends Share {
#ManyToOne
#JoinColumn( name="POST_ID", nullable=true )
private Post post;
....
}
As for usage, use the concrete classes only as:
#Test
public void saveNewDocumentShare(){
SharedDocument sharedDocument = new SharedDocument();
sharedDocument.setDocument(document1);
sharedDocument.setSharedBy(teacher1);
sharedDocument.setSharedWith(teacher2);
sharedDocument.setSharedDate(new Date());
sharedDocument.setCreatedBy("1");
sharedDocument.setCreatedDate(new Date());
sharedDocument.setModifiedBy("1");
sharedDocument.setModifiedDate(new Date());
SharedDocument savedSharedDocument = dao.saveSharedDocument(sharedDocument);
assertNotNull(savedSharedDocument);
assertThat(savedSharedDocument.getId(),notNullValue());
}
#Test
public void saveNewPostShare(){
SharedPost sharedWikiPage = new SharedWikiPage();
sharedPost.setPost(post1);
sharedPost.setSharedBy(teacher1);
sharedPost.setSharedWith(teacher2);
sharedPost.setSharedDate(new Date());
sharedPost.setCreatedBy("1");
sharedPost.setCreatedDate(new Date());
sharedPost.setModifiedBy("1");
sharedPost.setModifiedDate(new Date());
SharedPost savedSharedPost = dao.saveSharedPost(sharedPost);
assertNotNull(savedSharedPost);
assertThat(savedSharedPost.getId(),notNullValue());
}
This is clearly a many-to-many relationship.
Default scenario for mapping those type of things is to use a separate table for connection information.
Something like:
table shared_connections {
number owner_id
,number shared_id
}
All objects that are shareable should extend some basic class ex: AbstractSharedObject. (use #MappedSuperclass annotation and care about #Inheritance strategy).
and inside Individual class :
private Collection<AbstractSharedObject> shares;
map this collection as ManyToMany relationship.
P.S. For this to work you will need to guarantee that the ids of all shareable objects are unique.
I have to tables I want to map to each other.
I want to populate 2 drop down lists: code_r and code_l.
When i choose a value from code_r, code_l should display only certain records.
In my database I have 2 tables:
Table code_r
===================
CODE INT
LIBELLE VARCHAR
And
Table code_l
===================
ID BIGINT
CODE_R_ID INT
LIBELLE VARCHAR
One code_r can have multiple code_l associated with it (based on the code_r_id (not a defined as a Foreign key in the code_l definition). Of course, a code_l can only be associated to one code_r.
The following SQL query works fine:
SELECT *
FROM code_r r
left join `code_l` l on l.code_r_id = r.code;
How should I implement that using using JPA/Hibernate-3.5 annotations in the CodeR and CodeL classes??
Any help would be appreciated. Thanks in advance.
With Hibernate (and now standardized in JPA 2.0), you can use a unidirectional one-to-many association without a join table using a JoinColumn annotation:
Annotate the CodeR like this:
#Entity
public class CodeR {
#Id
private Integer code;
private String libelle;
#OneToMany
#JoinColumn(name="CODE_R_ID")
Set<CodeL> codeLs = new HashSet<CodeL>():
// getters, setters
}
And CodeL
#Entity
public class CodeL {
#Id
private Integer id;
private String libelle;
// getters, setters, equals, hashCode
}
And the JPQL query:
SELECT r FROM CodeR LEFT JOIN r.codeLs
in the CodeR class:
#OneToMany(mappedBy="code_r_id")
Collection elementsFromL;
in the CodeL class:
#ManyToOne
CodeR code_r_id;
#Entity
public class Person {
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
private List<Location> locations;
[...]
}
#Embeddable
public class Location {
[...]
}
Given the following class structure, when I try to add a new location to the list of Person's Locations, it always results in the following SQL queries:
DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson
And
A lotsa' inserts into the PERSON_LOCATIONS table
Hibernate (3.5.x / JPA 2) deletes all associated records for the given Person and re-inserts all previous records, plus the new one.
I had the idea that the equals/hashcode method on Location would solve the problem, but it didn't change anything.
Any hints are appreciated!
The problem is somehow explained in the page about ElementCollection of the JPA wikibook:
Primary keys in CollectionTable
The JPA 2.0 specification does not
provide a way to define the Id in the
Embeddable. However, to delete or
update a element of the
ElementCollection mapping, some unique
key is normally required. Otherwise,
on every update the JPA provider would
need to delete everything from the
CollectionTable for the Entity, and
then insert the values back. So, the
JPA provider will most likely assume
that the combination of all of the
fields in the Embeddable are unique,
in combination with the foreign key
(JoinColunm(s)). This however could be
inefficient, or just not feasible if
the Embeddable is big, or complex.
And this is exactly (the part in bold) what happens here (Hibernate doesn't generate a primary key for the collection table and has no way to detect what element of the collection changed and will delete the old content from the table to insert the new content).
However, if you define an #OrderColumn (to specify a column used to maintain the persistent order of a list - which would make sense since you're using a List), Hibernate will create a primary key (made of the order column and the join column) and will be able to update the collection table without deleting the whole content.
Something like this (if you want to use the default column name):
#Entity
public class Person {
...
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
#OrderColumn
private List<Location> locations;
...
}
References
JPA 2.0 Specification
Section 11.1.12 "ElementCollection Annotation"
Section 11.1.39 "OrderColumn Annotation"
JPA Wikibook
Java Persistence/ElementCollection
In addition to Pascal's answer, you have to also set at least one column as NOT NULL:
#Embeddable
public class Location {
#Column(name = "path", nullable = false)
private String path;
#Column(name = "parent", nullable = false)
private String parent;
public Location() {
}
public Location(String path, String parent) {
this.path = path;
this.parent= parent;
}
public String getPath() {
return path;
}
public String getParent() {
return parent;
}
}
This requirement is documented in AbstractPersistentCollection:
Workaround for situations like HHH-7072. If the collection element is a component that consists entirely
of nullable properties, we currently have to forcefully recreate the entire collection. See the use
of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info. In order to delete
row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than
the current "WHERE COL = ?" (fails for null for most DBs). Note that
the param would have to be bound twice. Until we eventually add "parameter bind points" concepts to the
AST in ORM 5+, handling this type of condition is either extremely difficult or impossible. Forcing
recreation isn't ideal, but not really any other option in ORM 4.
We discovered that entities we were defining as our ElementCollection types did not have an equals or hashcode method defined and had nullable fields. We provided those (via #lombok for what it's worth) on the entity type and it allowed hibernate (v 5.2.14) to identify that the collection was or was not dirty.
Additionally, this error manifested for us because we were within a service method that was marked with the annotation #Transaction(readonly = true). Since hibernate would attempt to clear the related element collection and insert it all over again, the transaction would fail when being flushed and things were breaking with this very difficult to trace message:
HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]
Here is an example of our entity model that had the error
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
public class Entity2 {
private UUID someUUID;
}
Changing it to this
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
#EqualsAndHashCode
public class Entity2 {
#Column(nullable = false)
private UUID someUUID;
}
Fixed our issue. Good luck.
I had the same issue but wanted to map a list of enums: List<EnumType>.
I got it working like this:
#ElementCollection
#CollectionTable(
name = "enum_table",
joinColumns = #JoinColumn(name = "some_id")
)
#OrderColumn
#Enumerated(EnumType.STRING)
private List<EnumType> enumTypeList = new ArrayList<>();
public void setEnumList(List<EnumType> newEnumList) {
this.enumTypeList.clear();
this.enumTypeList.addAll(newEnumList);
}
The issue with me was that the List object was always replaced using the default setter and therefore hibernate treated it as a completely "new" object although the enums did not change.