Spring and JPA 2.0 - Single value Primary Key through OneToOne - java

I have a simple table (ActivityLog) and I want it to have a PK that is also a FK to another table (User).
It seems to be a common thing to have, and I tried to follow this wikibook
Primary Keys through OneToOne and ManyToOne Relationships. The example there involved a composite key. I need just a primitive key, so I ended up with:
#Entity
public class User {
#Id
private Long id;
// other stuff
}
#Entity
public class ActivityLog {
#Id
#OneToOne(optional = false)
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
// other stuff
}
Unfortunately i am getting:
Caused by: java.lang.IllegalArgumentException: This class [class com.example.ActivityLog] does not define an IdClass
at org.hibernate.metamodel.internal.AbstractIdentifiableType.getIdClassAttributes(AbstractIdentifiableType.java:183)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:253)
I tried to annotate ActivityLog with:
#IdClass(Long.class)
(even though from what I understand it is applicable only for composite keys), yet I am getting the exact same error.
Is my case different than what's on the mentioned wikibook?
Is Spring at fault here? (As suggested in this question? (no accepted answers)).

This should help:
#Entity
public class ActivityLog {
#Id
#Column(name = "user_id")
private Long id;
#OneToOne(optional = false)
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
// other stuff
}
Btw. I would expect, that you need more logs per user, so you would probably need some additional (generated) id anyway ...

Related

Hibernate-OneToMany mapping for existing DB Tables

I am trying to join to Hibernate Entities in a OneToOne Mapping. I am able to fetch the data for a given primary key from the Main Entity, the joining entity, however, returns null. I am new to hibernate and any help will be appreciated.
I have two Tables,
PT_CORE
Primary Key: ptId - Integer;
Foreign Key: stId(ST_AUX) - Integer;
Columns: ptId, ptName
ST_AUX
Primary Key: stId;
Columns: stId, stName
The two tables get populated by other applications and mine is a read-only operation.
Below is my first Entity class(PtCore.java)
#Entity
#Table(name="PT_CORE")
public class PtCore implements Serializable{
#Id
#Column(name="ptId", nullable = false)
private int id;
#Column(nullable=false)
private int stId; //The Foreign key column
#OneToOne
#JoinTable( name = "core_aux", joinColumns = {#JoinColumn(Name="ptId")},
inverseJoinColumns = {#JoinColumn(Name="stId")}
)
private StAux staux;
//Getters, setters and toString() for above
}
StAux is another Entity, defined as below,
#Entity
#Table(name="ST_AUX")
public class StAux implements Serializable {
#Id
#Column(nullable=false)
private Integer stId;
#OneToOne
private PtCore ptcore;
#Column
private String stName;
//Getters, Setters and toString follow.
}
I do below in the Service method:
PtCore obj = (PtCore) session.get(PtCore.class,1);
System.out.println(obj);
In the Results, I get the value of ptName, but the stAux class variables are null, Indicating that the join does not work as expected.
First of all you have the mapping information existing in your PT_CORE. And I assume it is something like FOREIGN KEY (stid) REFERENCES (stid). If you want to use existing schema and existing data I guess there is no mapping table core_aux really existing. At least you did not mention it. However it is visible as #JoinTable annotation but still there is this above mentioned foreign key which seems to be the real mapping (so again not the join table).
I suggest the following
remove this
#Column(nullable=false)
private int stId; //The Foreign key column
from your PtCore. I think it is not needed. Also in PtCore, remove the #JoinTable (because what I told above) and add mapping informaiion to #OneToOne annotation, like:
#OneToOne
#JoinColumn(name = "stid")
private StAux staux;
from your PT_CORE.
Then in StAux alter also a bit:
#Id
#Column(name = "stid") // this might not be needed but if there is like "st_id"...
private Integer stId; // so just for sure
#OneToOne(mappedBy = "staux")
private PtCore ptcore;
Because you have existing tables and constraints there might raise errors if hibernate tries to auto-generate those again by JPA instructions.
Check this for example for more information.
UPDATE: just realized also that in your title is #OneToMany but in your code is #OneToOne.
So you might want to elaborate your question and/or title a bit.
In your relation, the owning side is PtCore, the inverse side is StAux.
In bidirectional OneToOne relations, the inverse side has to have the mappedBy attribute. Actually, the mappedBy attribute contains the name of the association-field on the owning side.
So, you must change your inverse side code (StAux Entity). You have to add mappedBy attribute to #OneToOne in StAux class:
#OneToOne(mappedBy="staux")
private PtCore ptcore;

Hibernate Mapping Problems: Multiple Embeddables + ElementCollections

I have a LocalizedString Embeddable that looks like this:
#Embeddable
public class LocalizedString {
#ElementCollection(fetch = FetchType.EAGER)
private Map<String, String> stringMap;
// getter, setter
}
and an Article class that is supposed to make use of the LocalizedString:
#Entity
public class Article {
#Id
#GeneratedValue
private long id;
#Embedded
private LocalizedString title;
#Embedded
private LocalizedString text;
// getter, setter
}
Generating the tables works just fine, but when I try to insert an Article I get the following exception:
Duplicate entry '1-test2' for key 'PRIMARY'
After looking at the database structure it's obvious why. Hibernate only generated one article_string_map table with the a primary key constraint over the article id and the key of the map.
Googling this problem led me to this question on SO and the answer to include the #AttributeOverride annotations:
#Entity
public class Article {
#Id
#GeneratedValue
private long id;
#AttributeOverride(name="stringMap",column=#Column(name="title_stringMap"))
#Embedded
private LocalizedString title;
#AttributeOverride(name="stringMap",column=#Column(name="text_stringMap"))
#Embedded
private LocalizedString text;
}
This does not work either though, since Hibernate now complains about this:
Repeated column in mapping for collection:
test.model.Article.title.stringMap column: title_string_map
I do not understand what exactly is causing this error and I couldn't really translate the things I did find out about it to my specific problem.
My question is, what else do I need to fix to make LocalizedString work as an Embeddable? I'd also like to know why Hibernate is saying that I mapped title_string_map twice, even though I don't mention it twice in my entire project. Is there some kind of default mapping going on that I need to override?
How can I tell Hibernate to map this correctly?
(Also, I don't have a persistence.xml since I'm purely using annotations for configuration)
I figured it out on my own.
In order to map a ElementCollection I had to use #AssociationOverride combined with the joinTable attribute. The working Article class looks like this now:
#Entity
public class Article {
#Id
#GeneratedValue
private long id;
#AssociationOverride(name = "stringMap", joinTable = #JoinTable(name = "title_stringMap"))
#Embedded
private LocalizedString title;
#AssociationOverride(name = "stringMap", joinTable = #JoinTable(name = "text_stringMap"))
#Embedded
private LocalizedString text;
// getters, setters
}

Hibernate disable default behaviour

I have a problem with my Hibernate making assumptions on what to call columns.
Specifically, when I do a #ManyToOne field, where I refer to a column in the other Table.
What happens is that, If I do not enter a #JoinColumn annotation as well, it maps the field with an underscore in it's name.
For example, i have this class:
#Entity
public class User extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userFK")
private List<TwitterAccount> twitterAccounts;
/* GETTERS & SETTERS OMITTED */
}
And then I have the TwitterAccount class:
#Entity
public class TwitterAccount extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
private User userFK;
}
What happens is that when it tries to get the Twitter accounts for a certain user, I get the exception: MySQLSyntaxErrorException: Unknown column 'twitteracc1_.userFK_id' in 'field list'
Look at what it tries to map the userFK to: userFK_id. Of course it doesn't exist! I haven't given it that name.
So the question comes down to: Is it possible to turn off this functionality? (The functionality that turns the column name into 'field_'foreignkey')
I am aware that using #JoinColumn(name = "userFK") would solve it, but I'd rather turn it off instead.
Regards
This is the default as specified by the JPA specification
The concatenation
of the following: the name
of the referencing relationship
property or field of the referencing
entity or embeddable class;
""; the name of the referenced
primary key column. If there is no
such referencing relationship
property or field in the entity, or if
the join is for an element collection,
the join column name is
formed as the concatenation of the
following: the name of the entity;
""; the name of the referenced
primary key column.
In hibernate this is implemented in a NamingStrategy in this case the EJB3NamingStrategy. You can implement your own version of this deciding whatever you want to use. But that will probably only complicate/confuse people (which might expect the standards to apply).

Map two entities using a shared foreign key column in hibernate

I have four entities to map together, "Association", "Account", "Transaction" and "TransactionEvent". The id of Association is a simple integer id. Account and Transaction each have embedded id's consisting of a mapping to an Association and a number.
TransactionEvent should have an embedded id consisting of one Account and one Association. Now, each of those are mapped to an Association, and I want it to be the same Association for one TransactionEvent.
JPA Annotations is used for the Hibernate mapping, but I cannot make this work. I have tried forcing the same column name for the Association key, but Hibernate complains about repeated columns.
Is this possible to solve, or am I not thinking straight?
Here are the annotated classes, but I trimmed away getters/setters and non-id columns, annotations from the javax.persistence namespace:
#Entity
public class Association implements Serializable {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
}
#Embeddable
public class AccountPK implements Serializable {
#ManyToOne(optional=false)
private Association association;
#Column(nullable=false)
private int number;
}
#Embeddable
public class TransactionPK implements Serializable {
#ManyToOne
private Association association;
#GeneratedValue(strategy=GenerationType.AUTO)
private long number;
}
#Embeddable
public class AccountEventPK implements Serializable {
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name="association_id", referencedColumnName="association_id"),
#JoinColumn(name="account_number", referencedColumnName="number")
})
private Account account;
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name="association_id", referencedColumnName="association_id"),
#JoinColumn(name="transaction_number", referencedColumnName="number")
})
private Transaction transaction;
}
Actual Account, Transaction and AccountEvent entities are on the form
#Entity
public class Account implements Serializable {
#EmbeddedId
private AccountPK id;
}
I don't have much experience with placing associations directly in the embedded id component since this is not supported by JPA but is Hibernate specific.
As an alternative my suggestion would be to use the approach described in the Composite Primary Keys section of the JPA wikibook:
(...) JPA 1.0 requires that all #Id
mappings be Basic mappings, so if
your Id comes from a foreign key
column through a OneToOne or
ManyToOne mapping, you must also
define a Basic #Id mapping for the
foreign key column. The reason for
this is in part that the Id must be a
simple object for identity and caching
purposes, and for use in the IdClass
or the EntityManager find() API.
Because you now have two mappings for
the same foreign key column you must
define which one will be written to
the database (it must be the Basic
one), so the OneToOne or ManyToOne
foreign key must be defined to be
read-only. This is done through
setting the JoinColumn attributes
insertable and updatable to false,
or by using the
#PrimaryKeyJoinColumn instead of the
#JoinColumn.
A side effect of having two mappings
for the same column is that you now
have to keep the two in synch. This is
typically done through having the set
method for the OneToOne attribute
also set the Basic attribute value to
the target object's id. This can
become very complicated if the target
object's primary key is a
GeneratedValue, in this case you
must ensure that the target object's
id has been assigned before relating
the two objects.
(...)
Example ManyToOne id annotation
...
#Entity
#IdClass(PhonePK.class)
public class Phone {
#Id
#Column(name="OWNER_ID")
private long ownerId;
#Id
private String type;
#ManyToOne
#PrimaryKeyJoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID")
private Employee owner;
...
public void setOwner(Employee owner) {
this.owner = owner;
this.ownerId = owner.getId();
}
...
}
This looks like to be what you're looking for (and maybe less complicated). I'd try to implement this solution (incrementally).

How to properly implement a domain object with composite id in Hibernate?

I have the following domain objects:
public class Department {
private long departmentId;
}
public class Manager {
private long managerId;
}
public class Project {
private ProjectId compositeId;
#ManyToOne
private Department department;
#ManyToOne
private Manager manager;
}
public class ProjectId {
private long departmentId;
private long managerId;
}
Project is identified by a composite key (departmentId,managerId). The question is how should Project.setManager(..) or Project.setDepartment(..) be implemented? Is the implemention listed below the best practice?
public void setManager( Manager manager ) {
this.manager = manager;
this.compositeId.setManagerId( manager.getId() );
}
My understanding is that compositeId needs to be updated whenever an property is set.
A harder and related question is how should Project.setCompositeId(..) be implemented? Project wouldn't be able to update property manager nor department based on a composite id (long). Overwriting the compositeId without updating the properties would leave Project at an incongruous state.
I suggest the following:
#Entity
#IdClass(ProjectId.class)
public class Project {
#Id #Column(name="DEPARTMENT_ID")
private long departmentId;
#Id #Column(name="MANAGER_ID")
private long managerId;
#ManyToOne
#PrimaryKeyJoinColumn(name="DEPARTMENT_ID", referencedColumnName="DPT_ID")
private Department department;
#ManyToOne
#PrimaryKeyJoinColumn(name="MANAGER_ID", referencedColumnName="MGR_ID")
private Manager manager;
...
}
This mapping is very well explained in the JPA Wikibook:
JPA 1.0 requires that all #Id mappings
be Basic mappings, so if your Id comes
from a foreign key column through a
OneToOne or ManyToOne mapping, you
must also define a Basic #Id mapping
for the foreign key column. The reason
for this is in part that the Id must
be a simple object for identity and
caching purposes, and for use in the
IdClass or the EntityManager find()
API.
Because you now have two mappings for
the same foreign key column you must
define which one will be written to
the database (it must be the Basic
one), so the OneToOne or ManyToOne
foreign key must be defined to be
read-only. This is done through
setting the JoinColumn attributes
insertable and updatable to false, or
by using the #PrimaryKeyJoinColumn
instead of the #JoinColumn.
A side effect of having two mappings
for the same column is that you now
have to keep the two in synch. This is
typically done through having the set
method for the OneToOne attribute also
set the Basic attribute value to the
target object's id. This can become
very complicated if the target
object's primary key is a
GeneratedValue, in this case you must
ensure that the target object's id has
been assigned before relating the two objects.
(...)
Example ManyToOne id annotation
...
#Entity
#IdClass(PhonePK.class)
public class Phone {
#Id
#Column(name="OWNER_ID")
private long ownerId;
#Id
private String type;
#ManyToOne
#PrimaryKeyJoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID")
private Employee owner;
...
public void setOwner(Employee owner) {
this.owner = owner;
this.ownerId = owner.getId();
}
...
}
Reference
JPA Wikibook
Primary Keys through OneToOne and ManyToOne Relationships

Categories