Can somebody please give me an example of a unidirectional #OneToOne primary-key mapping in Hibernate ? I've tried numerous combinations, and so far the best thing I've gotten is this :
#Entity
#Table(name = "paper_cheque_stop_metadata")
#org.hibernate.annotations.Entity(mutable = false)
public class PaperChequeStopMetadata implements Serializable, SecurityEventAware {
private static final long serialVersionUID = 1L;
#Id
#JoinColumn(name = "paper_cheque_id")
#OneToOne(cascade = {}, fetch = FetchType.EAGER, optional = false, targetEntity = PaperCheque.class)
private PaperCheque paperCheque;
}
Whenever Hibernate tries to automatically generate the schema for the above mapping, it tries to create the primary key as a blob, instead of as a long, which is the id type of PaperCheque. Can somebody please help me ? If I can't get an exact solution, something close would do, but I'd appreciate any response.
I saved this discussion when I implemented a couple of #OneToOne mappings, I hope it can be of use to you too, but we don't let Hibernate create the database for us.
Note the GenericGenerator annotation.
Anyway, I have this code working:
#Entity
#Table(name = "message")
public class Message implements java.io.Serializable
{
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "message_id")
public MessageContent getMessageContent()
{
return messageContent;
}
}
#Entity
#Table(name = "message_content")
#GenericGenerator(name = "MessageContent", strategy = "foreign",
parameters =
{
#org.hibernate.annotations.Parameter
(
name = "property", value = "message"
)
}
)
public class MessageContent implements java.io.Serializable
{
#Id
#Column(name = "message_id", unique = true, nullable = false)
// See http://forum.hibernate.org/viewtopic.php?p=2381079
#GeneratedValue(generator = "MessageContent")
public Integer getMessageId()
{
return this.messageId;
}
}
Your intention is to have a 1-1 relationship between PaperChequeStopMetaData and PaperCheque? If that's so, you can't define the PaperCheque instance as the #Id of PaperChequeStopMetaData, you have to define a separate #Id column in PaperChequeStopMetaData.
Thank you both for your answers. I kept experimenting, and here's what I got working :
#Entity
#Table(name = "paper_cheque_stop_metadata")
#org.hibernate.annotations.Entity(mutable = false)
public class PaperChequeStopMetadata implements Serializable, SecurityEventAware {
private static final long serialVersionUID = 1L;
#SuppressWarnings("unused")
#Id
#Column(name = "paper_cheque_id")
#AccessType("property")
private long id;
#OneToOne(cascade = {}, fetch = FetchType.EAGER, optional = false, targetEntity = PaperCheque.class)
#PrimaryKeyJoinColumn(name = "paper_cheque_id")
#JoinColumn(name = "paper_cheque_id", insertable = true)
#NotNull
private PaperCheque paperCheque;
#XmlAttribute(namespace = XMLNS, name = "paper-cheque-id", required = true)
public final long getId() {
return this.paperCheque.getId();
}
public final void setId(long id) {
//this.id = id;
//NOOP, this is essentially a pseudo-property
}
}
This is, by all means, a disgusting hack, but it gets me everything I wanted. The paperCheque property accessors are as normal (not shown). I've run into this kind of unidirectional OneToOne mapping problem before and settled for much worse solutions, but this time I decided I was going to figure out out, so I kept hacking away at it. Once again, thank you both for your answers, it's much appreciated.
Just updating this question for future views.
When this question was made i think there wasn't a proper solution for this problem. But since JPA 2.0 you can use #MapsId to solve this problem.
Reference with proper explanation: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
You should stay away from hibernate's OneToOne mapping, it is very dangerous. see http://opensource.atlassian.com/projects/hibernate/browse/HHH-2128
you are better off using ManyToOne mappings.
Related
I'm getting a weird error while I'm debugging my POC.
I have 2 entities:
#Getter
#Setter
#Entity
#Table(name = "APPLICANT")
public class Applicant implements Serializable {
private static final long serialVersionUID = 6060170457948717553L;
#Id
#Column(name = "applicant_id", insertable = false, nullable = false)
private Long applicantId;
#Column(name = "application_id", unique = true)
private String applicationId;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "application_id", referencedColumnName = "application_id", insertable =
false, updatable = false)
private ApplicationEntity applicationEntity;
#Getter
#Setter
#Entity
#Table(name = "APPLICATION")
public class ApplicationEntity implements Serializable{
private static final long serialVersionUID = 7300036359295729197L;
#Id
#Column(name = "APPLICATION_ID")
private String id;
#OneToOne(mappedBy = "applicationEntity", fetch = FetchType.LAZY)
private Applicant applicant;
These classes has the repositories interfaces extending from CrudRepository, and in the Applicant repository I have a custom method to get the entity with the applicationId:
Applicant findByApplicationId(String applicationId);
But, when I'm debugging, I see the following message in the intellij debuguer on the applicationEntity attribute:
Method threw 'org.hibernate.LazyInitializationException' exception. Cannot evaluate org.example.postgres.jpa.model.ApplicationEntity$HibernateProxy$qa4PKx8V.toString()
The value qa4PKx8V changes every time that I perform a new test.
I tried a lot of combinations in the #Join annotation, I've deleted the lombook annotations, I've used the #Transactional annotation either, but is always the same error.
A key point to note, is that I can get the data from the table with any error, I just see this message in the debugger, so my question is, this is a thing of intellij or something like that? Or I need to fix this with configuration or changing something in my code?
Thanks.
I am assuming you have an autogenerated toString() implementation?
In general, you should avoid referencing lazily-loaded properties in toString(), equals(), hashCode() etc. Failing to do so will result in LazyInitializationException surprises like the one you're facing, triggered by the aforementioned methods whenever they try to access lazy properties outside of an active transaction context.
(This is indeed 'a thing of intellij', in the sense that although the debugged code is probably surrounded by a transaction, the Intellij inspector evaluates the expression on a separate thread where no transaction is active = no persistence context is open. Also, it will only happen with #XxxToOne(optional = false) properties)
Let's say that you have an Person entity as follows:
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "person_id")
private long id;
#Column(columnDefinition = "varchar(255)")
private String name;
...
}
Now say that you want to define a Marriage entity, holding two Person, with a start date, an end date (end date might be null if still married), and nrOfChildren.
How would you approach this?
One way could be to add the following to the Person entity:
#JoinTable(name = "Person_Marriage",
joinColumns = {
#JoinColumn(name = "person_id")},
inverseJoinColumns = {
#JoinColumn(name = "marriage_id")})
private Set<Marriage> marriages;
And have a Marriage entity as follows:
#Entity
public class Marriage {
...
#ManyToMany(mappedBy = "marriages")
private Set<Person> couple; //always composed by two elements
#Temporal(TemporalType.DATE)
private Date married_from;
...
}
Of course, the couple set above, would always only contain 2 elements, a constrain that I would enforce by code.
I guess something like this could work, but it seems rather sloppy, as I would also need to ensure by code that Marriage(A,B)=Marriage(B,A), if you get my point..
Do you have a better idea? :)
I would go maybe with making the 2 persons for the Marriage explicit with a composite key:
#Entity #IdClass(MarriageId.class)
public class Marriage {
#Id Person person1;
#Id Person person2;
}
I'm unsure about the exact syntax for JPA, but you get the point. :)
I would like to know the difference between #JsonManagedReference and #JsonBackReference in Jackson?
#JsonManagedReference is the forward part of reference – the one that
gets serialized normally. #JsonBackReference is the back part of
reference – it will be omitted from serialization.
So they really depend on the direction of your relationship
public class User {
public int id;
public String name;
#JsonBackReference
public List<Item> userItems;
}
public class Item {
public int id;
public String itemName;
#JsonManagedReference
public User owner;
}
#JsonManagedReference -> Manages the forward part of the reference and the fields marked by this annotation are the ones that get Serialised
#JsonBackReference -> Manages the reverse part of the reference and the fields/collections marked with this annotation are not serialised.
Use case:
You have a one-many or many-many relationships in your entities/tables and not using the above would lead to errors like
Infinite Recursion and hence stackoverflow - > Could not write content: Infinite recursion (StackOverflowError)
The above errors occurs because Jackson (or someother similiar) tries to serialise both ends of the relationship and ends up in a recursion.
#JsonIgnore performs similiar functions but the above mentioned annotations are preferable.
As write Rajat Verma, his solution works perfectly. Thanks man you saved me lot of time and anger :-)
The important Part:
You need define fields as List, I had that as Set before and this solution NOT WORKING (appears as infinite loop)!
I add my solution:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)
public class Agent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(mappedBy = "subscribers")
#ApiModelProperty(dataType = "List", example = "[1,2,3]") // for Swagger
#JsonIdentityReference(alwaysAsId = true) // show only id of Topic
private final List<Topic> subscribeTopics = new ArrayList<>()
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)
public class Topic {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "topic_agent",
joinColumns = #JoinColumn(name = "fk_topic_id"),
inverseJoinColumns = #JoinColumn(name = "fk_agent_id"))
#ApiModelProperty(dataType = "List", example = "[1,2,3]")
#JsonIdentityReference(alwaysAsId = true)
private final List<Agent> subscribers = new ArrayList<>();
}
#JsonManagedReference and #JsonBackReference are designed to handle this two-way linkage between fields, one for Parent role, the other for Child role.
For avoiding the problem, linkage is handled such that the property
annotated with #JsonManagedReference annotation is handled normally
(serialized normally, no special handling for deserialization) and the
property annotated with #JsonBackReference annotation is not
serialized; and during deserialization, its value is set to instance
that has the "managed" (forward) link.
This question is very similar to: JPA (Hibernate, EclipseLink) mapping: why doesn't this code work (chain of 2 relationships using JPA 2.0, #EmbeddedId composite PK-FK)?
Actually my only (from meaningful that I spotted) difference is that I use #IdClass and that I most probably won't be able to switch to a different provider than hibernate.
but anyway here is the code (removed parts that where unimportant):
PermissionContextType.java:
#Entity
#IdClass(PermissionContextTypePk.class)
public class PermissionContextType{
#Id
private String id;
#Id
#JoinColumn (name = "PROJECT", referencedColumnName = "ID")
#ManyToOne ()
private Project project;
public static class PermissionContextTypePk implements Serializable{
public String project;
public String id;
// ... eq and hashCode here ...
}
}
PermissionContext.java:
#Entity
#IdClass(PermissionContextPk.class)
public class PermissionContext{
#Id
private String id;
#Id
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "ID")
})
#ManyToOne
private PermissionContextType permissionContextType;
public static class PermissionContextPk implements Serializable{
public String id;
public PermissionContextTypePk permissionContextType;
// ... eq and hashCode here ...
}
}
Permission.java:
#Entity
#IdClass(PermissionPk.class)
public class Permission{
#Id
private String id;
#Id
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE"),
#JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID")
})
#ManyToOne
private PermissionContext permissionContext;
public static class PermissionPk implements Serializable{
public String id;
public PermissionContextPk permissionContext;
// ... eq and hashCode here ...
}
}
and what I get is:
org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a #MapsId: PermissionContext
Caused by: org.hibernate.AssertionFailure: org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a #MapsId: PermissionContext
does anybody know if this is a hibernate bug and I should post it on their issue tracking system (and pray that I would be able to update to given hibernate version) or is there something fundamentally wrong with my way of binding the entities?
I've checked it with the hibernate implementation on EAP 6.1 (4.2.0) as well as on wildfly (don't really know which one.)
Ok, so this is what I found so far :
Thanks fr my friend : https://hibernate.atlassian.net/browse/HHH-5764 which most probably is the reason for this behaviour.
And I found a workaround :
Permission.java:
#Entity
#IdClass(PermissionPk.class)
public class Permission{
#Id
private String id;
// for the next 3 fields there are no public acessors, so the public API of the class was not changed !
#Id
#Column(name = "PROJECT")
private String projectId;
#Id
#Column(name = "PERMISSIONCONTEXTTYPE")
private String permissionContextTypeId;
#Id
#Column(name = "PERMISSIONCONTEXT")
private String permissionContextId;
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT", updatable = false, insertable = false),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE", updatable = false, insertable = false),
#JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID", updatable = false, insertable = false)
})
#ManyToOne
private PermissionContext permissionContext;
public static class PermissionPk implements Serializable{
// previously they where private as well, but removed public constructor for the sake of simplicity of the question - so no changes where necesary in public API of the class !
private String id;
private String projectId;
private String permissionContextTypeId;
private String permissionContextId;
public PermissionPk () {}
public PermissionPk (String aId, PermissionContextPk aPermissionContext) {
this.id = aId;
permissionContextId = aPermissionContext.id;
permissionContextTypeId = aPermissionContext.permissionContextType.id;
projectId = aPermissionContext.permissionContextType.project;
}
... eq and hashCode here ...
}
}
The good thing about this workaround is that it does not change the public API of the class in any way
(the only change was that I needed to make fields in Pk's of context and contexttype visible to the PermissionPk - they where private before with only a public constructor [but again simplified for the question]), nor did it change the jpql queries, and at the same time workaround is scalable (to any tier amount - as long as every even pk does not contain another pk), so if the bug will be resolved it will be easy to remove the workaround.
I would still gladly accept any comments on either my workaround or the question in itself.
Today I found another workaround :)
You can omit #IdClass entirely and use hibernate specific ability to create composite keys on the fly as apparently it is not affected by this bug.
The drawback here is that:
it is entirely Hibernate specific not covered by JPA at all.
you cannot do em.find(ClassName.class,new ClassPk(args...)) as there is no ClassPk at all.
But if you could use anything else than hibernate you could just as well use something without this bug - so probably 1 is not a problem really. and there is a possibility that you don't really need the em.find for this entity (or can live with creating it thru session or jpql query).
I am trying to get the following type of mapping to work
Table event has the following columns:
id (PK)
prodgroup
errandtype
table errandtype : errandtype
table prodgroup: prodgroup
I have corresponding JPA classes
#Entity
#Table(name="event")
public class MyEvent {
#Id
int id;
// what mapping should go here?
Prodgroup prodgroup;
// what mapping should go here?
ErrandType errandtype;
}
#Entity
public class Prodgroup {
#Id
private String prodgroup;
}
#Entity
public class ErrandType {
#Id
private String errandtype;
}
Ok so questions are marked as comments in the code but I'll try to be explicit anyway.
In the above example I want my Prodgroup and ErrandType fields in the MyEvent class to be set to corresponding Prodgroup and Errandtype instances
I have tried #OneToOne relationships with #joincolumns and with mappedby attribute, but I just can't get it working and I've lost all sense of logical approach. My grasp of JPA entity mapping is clearly weak.
So can anyone bring some clarity?
It should be:
#Entity
#Table(name="event")
public class MyEvent {
#Id
int id;
// what mapping should go here?
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "prodgroup_id", insertable = true, updatable = true)
Prodgroup prodgroup;
// what mapping should go here?
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "errandtype_id", insertable = true, updatable = true)
ErrandType errandtype;
}
#Entity
public class Prodgroup {
#Id
private String prodgroup;
}
#Entity
public class ErrandType {
#Id
private String errandtype;
}
FetchType Eager means the object will be always loaded (would be "Lazy" by default if not specified).
CascadeType.ALL means mearge/persist/remove will be also done to linked tables.
Sebastian
Your table columns event.prodgroup and event.errandtype are foreign keys to respective tables (prodgroup, errandtype). So you need #ManyToOne association (because many events may share one prodgroup or errantype).