JPA OneToOne relationship - java

I am building a project using the Play framework and I am having trouble getting my head around JPA #OneToOne relationships.
I currently have two classes:
User Object
#Entity
#Table( name="users" )
public class Users extends Model {
#OneToOne( mappedBy="userId", fetch=FetchType.LAZY, cascade = CascadeType.ALL )
#ForeignKey( name="userId", inverseName="userId" )
UserSettings userSettings;
public userId;
public userName;
}
UserSettings
#Entity
#Table( name="user_settings" )
public class UserSettings extends Model {
#OneToOne( cascade = CascadeType.ALL,targetEntity=User.class )
public String userId;
public String xml;
public UserSettings( String userId ){
this.userId = userId;
}
}
The idea is that I am trying to set the userId field within User as a foreign key within UserSettings. I have tried a few different ways to achieve this and my code always throws an error. The most common error I recveive is:
Referenced property not a (One|Many)ToOne.
However, When I try to set the userId in UserSettings using the code above, I receive the following exception:
A javax.persistence.PersistenceException has been caught, org.hibernate.PropertyAccessException: could not get a field value by reflection getter of reader.User.id
Can anybody help explain how I can achieve my desired goal?

Read section 5.2 of the hibernate reference about the difference between entities and values. You're trying to map a String as an entity. Only entities can be a (One|Many)ToOne, as the error is telling you. I.e., instead of String userId, you should be using User user, and instead of mappedBy="userId", mappedBy="user".

If you extend Model, Play will generate an primary key "id" by default for each entity. If this is not what you want you should extend Generic model instead.
The simplest way would be to have user as a property of user settings:
#Entity
#Table( name="user_settings" )
public class UserSettings extends Model{
#OneToOne
public Users user;
...
#Entity
#Table( name="users" )
public class Users extends Model {
#OneToOne(cascade = CascadeType.ALL)
public UserSettings settings;
Maintaining User and UserSettings in each entity with OneToOne allows you to have bi-directional searches.
If you want to use your own key change from Model to GenericModel and defined your foreign key on the user object.

Related

JPA Many-To-Many relationship with an extra enum column [duplicate]

This question already has an answer here:
Many-to-Many Relationship (with properties) in Google App Engine for Java
(1 answer)
Closed 7 years ago.
I'm trying to persist an entity that has a Map of Objects to Enum into a Google App Engine datastore. The entity classes are annotated using JPA.
Event class
import com.google.appengine.datanucleus.annotations.Unowned;
import com.google.appengine.api.datastore.Key;
import java.util.Map;
import javax.persistence.*;
import lombok.Builder;
import lombok.Data;
#Entity
#Builder
public #Data class Event {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
// I want a map belonging to event in order to query a particular user whether he confirmed his participation in the event
// All addressees are initially present in this map with response set to UNDEFINED
// If user has received and read notification, than the response is updated to YES, NO, or MAYBE
#Unowned
#ElementCollection
#CollectionTable(name = "user_response")
#MapKeyJoinColumn(name = "user_id")
#Enumerated
#Column(name = "response")
private Map<User, Response> addressees;
}
Response class
public enum Response {
UNDEFINED, YES, NO, MAYBE
}
I haven't defined any references in User class to this map. It's a unidirectional relationship.
User class
#Entity
public #Data class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
}
The Event.addressees column seems pretty tricky. So I ran my test to check if everything was working correctly. Well, it was not. I got an exception when I tried to save an Event entity to the datastore:
java.lang.IllegalArgumentException: addressees: Response is not a supported property type.
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedSingleValue(DataTypeUtils.java:235)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:199)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:173)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:148)
at com.google.appengine.api.datastore.PropertyContainer.setProperty(PropertyContainer.java:101)
According to DataNucleus Enum is a persistable data type by default. So I don't understand why I get the error message saying "Response is not a supported property type".
I suspected that the problem was with the User class. Maybe the association from Event to Users was not enough, and User should also have an association to Events. So I've added the events field to User as follows:
#Unowned
#ElementCollection
#CollectionTable(name = "user_event_responses")
#ManyToMany(mappedBy="addressees", targetEntity = Event.class)
#MapKeyJoinColumn
#Enumerated
#Column(name = "response")
private Map<Event, Response> events;
It didn't work anyway. Then I've read similar questions, and found no quick answer.
Please, show me an example of many-to-many relationship with an extra column in DataNucleus / JPA!
Problem of creating two classes that have a Many-To-Many relationship, but the relational join table has additional data, is a frequent problem.
Good examples on this topic I've found at WikiBooks - Java Persistence / Many-To-Many and in the article Mapping a Many-To-Many Join Table with extra column using JPA by Giovanni Gargiulo. References in the official documentation I've found much, much later: Unowned Entity Relationships in JDO and Unsupported Features of JPA 2.0 in AppEngine.
In this case the best solution is to create a class that models the join table.
So an EventUserResponse class would be created. It would have a Many-To-One to Event and User, and an attribute for the additional data. Event and User would have a One-To-Many to the EventUserResponse. Unfortunately I didn't managed how to map a composite primary key for this class. And DataNucleus Enhancer refused to enhance an entity class without primary key. So I've used a simple auto-generated ID.
The result should be like
Here are the sources:
EventUserAssociation class
#Entity
#Table(name = "event_user_response")
#NoArgsConstructor
#AllArgsConstructor
#Getter #Setter
#EqualsAndHashCode(callSuper = true, exclude = {"attendee", "event"})
public class EventUserAssociation extends AbstractEntity {
#Unowned
#ManyToOne
#PrimaryKeyJoinColumn(name = "eventId", referencedColumnName = "_id")
private Event event;
#Unowned
#ManyToOne
#PrimaryKeyJoinColumn(name = "attendeeId", referencedColumnName = "_id")
private User attendee;
#Enumerated
private Response response;
}
If Lombok annotations (#NoArgsConstructor for example) seem unfamiliar to you, you may want to take a look at the ProjectLombok. It does a great job to save us from a boilerplate code.
Event class
#Entity
#Builder
#EqualsAndHashCode(callSuper = false)
public #Data class Event extends AbstractEntity {
/* attributes are omitted */
// all addressees are initially present in this map with response set to UNDEFINED
// if user has received and read notification, than the response is updated to YES, NO, or MAYBE
#Singular
#Setter(AccessLevel.PRIVATE)
#OneToMany(mappedBy="event", cascade = CascadeType.ALL)
private List<EventUserAssociation> addressees = new ArrayList<>();
/**
* Add an addressee to the event.
* Create an association object for the relationship and set its data.
*
* #param addressee a user to whom this event notification is addressed
* #param response his response.
*/
public boolean addAddressee(User addressee, Response response) {
EventUserAssociation association = new EventUserAssociation(this, addressee, response);
// Add the association object to this event
return this.addressees.add(association) &&
// Also add the association object to the addressee.
addressee.getEvents().add(association);
}
public List<User> getAddressees() {
List<User> result = new ArrayList<>();
for (EventUserAssociation association : addressees)
result.add(association.getAttendee());
return result;
}
}
User class
#Entity
#NoArgsConstructor
#RequiredArgsConstructor
#Getter #Setter
public class User extends AbstractEntity {
/* non-significant attributes are omitted */
#Setter(AccessLevel.PRIVATE)
#Unowned
#OneToMany(mappedBy="attendee", cascade = CascadeType.ALL)
private List<EventUserAssociation> events = new ArrayList<>();
public static User find(String attribute, EntityManager em) {
/* implementation omitted */
}
}
AbstractEntity class
#MappedSuperclass
#NoArgsConstructor
#EqualsAndHashCode
public abstract class AbstractEntity {
#Id
#Column(name = "_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Getter
protected Key id;
}
EMFService class
public abstract class EMFService {
#Getter
private static final EntityManagerFactory emfInstance = Persistence.createEntityManagerFactory("transactions-optional");
}
Example of usage:
EntityManager em = EMFService.getFactory().createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
User fromContact = User.find(fromId, em);
Event event = Event.builder()
/* attributes initialization */
.build();
em.persist(event);
User toUser = User.find(toId, em);
event.addAddressee(toUser, Response.UNDEFINED);
tx.commit();
} finally {
if (tx.isActive()) tx.rollback();
em.close();
}
Cross-group transactions should be allowed for this to work (what if they aren't?). Add the following property to persistence.xml:
<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />
At last, regarding the code in the question, it is not allowed to have a primary key with the name key in AppEngine.

JPA - Mapping OneToMany association between the same table using an intermediate table

I'm creating an application where one large aspect is the ability for users to share content with friends. I'm trying to represent this in the object model and I'm having trouble getting the association to work properly. I'm using a mapping table that records the friender and the friendee, both of which are represented by the primary key (id) of the user. A user can have many friends, and also be referenced by other users. This is what the schema looks like:
Users:
int user_id (PK)
varchar(32) email
varchar(64) password
Users_Map:
int users_map_id (PK)
int friendee_id (FK references users(user_id))
int friender_id (FK references users(user_id))
And this is how I have the User entity set up:
#Data
#Entity
#Table(name = "users")
public class User extends AbstractPersistable<Long> {
#Id
#Column(name = "user_id")
private Long id;
#Column
private String email;
#Column
private String password;
#OneToMany
#JoinTable(name = "users_map",
joinColumns = { #JoinColumn(name = "friender_id") },
inverseJoinColumns = { #JoinColumn(name = "friendee_id") })
private List<User> friends;
}
I run into the following error when deploying the application:
org.hibernate.AnnotationException: A Foreign key refering
com.x.webapp.data.entity.User from
com.x.webapp.data.entity.User has the wrong number of
column. should be 2
I've tried quite a few other configurations, including adding a "referencedColumnName" attribute to each #JoinColumn, but they have also yielded errors. I'm also not entirely sure whether the schema I currently have is the best way to go about mapping users together.
I appreciate any help!
Removing the extension of AbstractPersistable fixed the problem - that contained an #Id reference and clashed with the #Id reference I put inside of User.

Issue with JPA #MappedSuperclass in a separate jar with Hibernate

My problems come from the fact that I am trying to reuse a mapped superclass that contains some basic fields such as a Long Id.
This mapped superclass looks like this:
#MappedSuperclass
public abstract class AbstractBaseEntity {
protected Integer id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", nullable = false, unique = true, columnDefinition = "int")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
It sits in a jar so that everyone can reuse it easily.
Apparently, all works perfectly except when entities that extend it actually have relationships between them and you try to get data using queries based on those relationships.
Example:
Say you have entity Organization that has one or more User (s):
#Entity
#Table(name = "Organizations")
public class Organization extends AbstractBaseEntity {
private Set<User> users;
#OneToMany(mappedBy = "organization", fetch = FetchType.LAZY)
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
#Entity
#Table(name = "Users")
public class User extends AbstractBaseEntity {
private Organization organization;
#ManyToOne
#JoinColumn(name = "Organization_ID", nullable = false)
public Organization getOrganization() {
return organization;
}
public void setOrganization(Organization organization) {
this.organization = organization;
}
}
Now here's the problem: If I use a DETACHED organization object as a parameter to a query like this:
SELECT u from User u where u.organization = ?1
then Hibernate throws this following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.Organization
This doesn't make any sense to me, it shouldn't require an attached entity for this kind of query since all it needs is its id.
If, however, i take out AbstractBaseEntity from the jar and put it in the same project as Organization and User, it all works perfectly, with detached Organizations and all...
This looks very much like a bug to me. Any clues on how to work around it?
PS. I've tried explicitly specifying the AbstractBaseEntity as a class in persistence.xml as specified here - JPA #MappedSuperclass in separate JAR in Eclipse (weird but worth a try) ...doesn't work
Sorry to say, but I would assume you simply can not "pull" a MappedSuperclass from a different compilation unit. This is because the JPA provider maybe uses code instrumentation to access the entity fields.
Have you tried to create a clone class in your own workarea?
Regards from Germany,
Thomas
According to JPA spec, if you have jpa classes in separate jar you should add it to the persistence.xml (I don't know, if Hibernate requires that, but you can give it a try). Try to add following entry to your persistence.xml <jar-file>entity.jar</jar-file>
Check this post how to define path in the jar-file tag.
Just a guess, but entities need to implement Serializable. See if that fixes your issue.

Hibernate Annotations with a collection

I am trying to implement my model using hibernate annotations. I have 3 classes, image, person, and tags. Tags is a a table consisting of 4 fields, an id, personId, imageId, and a createdDate. Person has the fields name, id, birthdate, etc. My image class is defined as follows:
#Entity
#Table(name="Image")
public class Image {
private Integer imageId;
private Set<Person> persons = new HashSet<Person>();
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
public Integer getImageId() {
return imageId;
}
public void setImageId(Integer imageId) {
this.imageId = imageId;
}
#ManyToMany
#JoinTable(name="Tags",
joinColumns = {#JoinColumn(name="imageId", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="personId", nullable=false)})
public Set<Person> getPersons() {
return persons;
}
public void setPersons(Set<Person> persons) {
this.persons = persons;
}
If I remove the annotations on the getPersons() method I can use the classes and add and remove records. I want to fetch all the tags with the image and I am trying to use a set. I keep getting the following error:
org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: com.exmaple.persons, no session or session was closed
Can someone please help me and let me know what I am doing wrong?
Thank you
This error message - which actually has nothing to do with your association mapping strategy or annotations - means that you have attempted to access a lazy-loaded collection on one of your domain objects after the Session was closed.
The solution is to either disable lazy-loading for this collection, explicitly load the collection before the Session is closed (for example, by calling foo.getBars().size()), or making sure that the Session stays open until it is no longer needed.
If you are not sure what lazy-loading is, here is the section in the Hibernate manual.
Thanks for the response matt. I am confused now. My query to retrieve the image looks like this:
public Image findByImageId(Integer imageId) {
#SuppressWarnings("unchecked")
List<Image> images = hibernateTemplate.find(
"from Image where imageId=?", imageId);
return (Image)images.get(0);
}
I thought that I can call the single hql query and if my mappings are correct it will bring back the associated data.
I was looking at this example at this link hibernate mappings:
2.2.5.3.1.3. Unidirectional with join table
A unidirectional one to many with join table is much preferred. This association is described through an #JoinTable.
#Entity
public class Trainer {
#OneToMany
#JoinTable(
name="TrainedMonkeys",
joinColumns = #JoinColumn( name="trainer_id"),
inverseJoinColumns = #JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
#Entity
public class Monkey {
... //no bidir
} Trainer describes a unidirectional relationship with Monkey using the join table TrainedMonkeys, with a foreign key trainer_id to Trainer (joinColumns) and a foreign key monkey_id to Monkey (inversejoinColumns).

JPA #OneToMany join on part of a multipart key

A Profile table has a one to many association with a Privilege table. The privilege table has a multipart key, of a profile_id and a privilege_id. I want to join from the Profile table to the Privilege table only on the profile_id and get back a collection of privileges.
In my Profile class I have
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "PROF_ID")
public List<ProfilePrivilegeEO> getProfilePrivileges()
{
return m_profilePrivileges;
}
My Privilege class has
private ProfilePrivilegeId m_profileId;
#EmbeddedId
public ProfilePrivilegeId getProfileId()
{
return m_profileId;
}
Where ProfilePrivilegeId is
#Embeddable
public class ProfilePrivilegeId
implements Serializable
{
private Integer m_profileId;
private Integer m_privNumber;
#Column(name = "PROF_ID")
public Integer getProfileId()
{
return m_profileId;
}
#Column(name = "PRIV_NUM")
public Integer getPrivNumber()
{
return m_privNumber;
}
.....
}
However, when i do that the static weaver says:
The #JoinColumns on the annotated element [method getProfilePrivileges] from the entity
class [class com.acme.ProfileEO] is incomplete. When the source entity class uses a
composite primary key, a #JoinColumn must be specified for each join column using the
#JoinColumns. Both the name and the referencedColumnName elements must be specified in
each such #JoinColumn.
However, the Profile table has no knowledge of privilege_ids... I don't see why JPA should demand that i specify both keys of the privilege table, that's just an arbitrary decision made by jpa with no valid reason why... What do i need to do to get this to work? (I am using EclipseLInk.)
Create an PrivilegeId class that encapsulate the ids. Make that class #Embedded and put it in the Privilege with #EmbeddedId.
In the PrivilegeId class, put an #OneToMany to the Profile and the privilege id.

Categories

Resources