How to model a friendship relationship in hibernate? - java

I need to have a friendship relationship. I have a friendship class with two primary keys that each is a Member class. I am receiving following exception:
org.hibernate.MappingException: Foreign key (FK_8ynretl1yt1xe3gcvfytrvpq:Friendship [])) must have same number of columns as the referenced primary key (Member [username])
Friendship
#Entity
public class Friendship implements Serializable {
/**
*
*/
private static final long serialVersionUID = -1234656876554786549L;
#Id
#ManyToOne
Member requester;
#Id
#ManyToOne
Member friend;
#Temporal(javax.persistence.TemporalType.DATE)
Date date;
Member
#Entity
public class Member {
#Id
#MapsId
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "username")
Credential credential;
#Column(nullable = false)
String fname;
#Column(nullable = false)
String lname;
#Column(nullable = false)
short gender;
Credential
#Entity
public class Credential {
#Id
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false)
private String password;
#Column(nullable = false)
private String authority;
#Column(nullable = false)
private boolean enabled;

Putting aside that Member and Credential should implement Serializable if multiple id properties without identifier type are used, your mappings are good, and this seems to be a bug in Hibernate.
Solution 1
I managed to make this work by declaring referencedColumnName in friend and requester associations in Friendship:
#Id
#ManyToOne
#JoinColumn(referencedColumnName = "username")
Member requester;
#Id
#ManyToOne
#JoinColumn(referencedColumnName = "username")
Member friend;
This way we explicitly tell Hibernate which columns the composite id references, so that it does not have to figure it out itself.
Solution 2
The solution 1 made me think of what could be the cause of the bug in Hibernate. It seems that it is somehow affected by the order in which Hibernate processes the entity mappings. If you explicitly declare the referenced column, everything works fine, otherwise it seems that Hibernate does not know all the details about the referenced column at the time it builds the composite key.
So I changed the order in which I add annotated classes to the session factory configuration to:
Credential
Member
Friendship
and then everything worked with your original mappings (after implementing Serializable in Member and Credential).
I added the classes in this order programmatically to the Configuration class, but I assume the same effect could be achieved by specifying this order in the persistence.xml or hibernate.cfg.xml:
<class>Credential</class>
<class>Member</class>
<class>Friendship</class>
Nevertheless, this solution is just for demonstrative purposes (you or someone else can later reorder the classes without keeping this issue in mind), so I suggest using solution 1.
Note
You know your use cases better, but in my personal opinion you should use #IdClass or #EmbeddedId since they are standardized in JPA; multiple id properties without identifier type is a Hibernate specific feature. Besides being able to easier construct the primary key object by which you will search and query the corresponding entities, a dedicated PK object is usually much lighter and offers better performance when serialized, especially if second level cache is enabled.

You have to add a separate ID field to the Member class for the #MapsID annotation to map. Like this:
#Entity
public class Member implements Serializable {
#Id
private String username;
#MapsId
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "username")
Credential credential;
#Column(nullable = false)
String fname;
#Column(nullable = false)
String lname;
#Column(nullable = false)
short gender;
}

You are missing the modeling of the primary key in the Friendship class.
For example:
#Embeddable
public class FriendshipPK implements Serializable
{
#Column(name = "requester_id")
protected String requesterId;
#Column(name = "friend_id")
protected String friendId;
}
Friendship class can now be modified as follows:
#Entity
public class Friendship implements Serializable
{
#EmbeddedId
protected FriendshipPK friendshipId = new FriendshipPK();
#ManyToOne
#MapsId("requesterId")
Member requester;
#ManyToOne
#MapsId("friendId")
Member friend;
#Temporal(javax.persistence.TemporalType.DATE)
Date date;
}
I have updated the Member class slightly:
#Entity
public class Member implements Serializable
{
#Id
protected String memberId;
#MapsId
#OneToOne(optional = false)
#JoinColumn(name = "username")
Credential credential;
#Column(nullable = false)
String fname;
#Column(nullable = false)
String lname;
#Column(nullable = false)
short gender;
}
I removed the cascade from Member class, and created the credentials objects first. But you can change this as fit.

In the Friendship class try specifying the #JoinColumn as well:
#Entity
public class Friendship implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "username")
Member requester;
...
}

Related

Relation between Entity and Object from service

I'm trying to make a relation between my Book entity and a list of languages that I retrieve through a service.
In my database, each book has a: ID, TITLE, CATEGORY_ID (FK), LANG_ID
Book.java:
#Entity
#Table(schema = Constants.SHEMA, name = "Book")
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "TITLE")
private String title;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
private Language language; // -> The Column associated in the database is Long LANG_ID
}
Category.java:
#Entity
#Table(schema = Constants.SHEMA, name = "Category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
}
Language.java:
public class Language implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
}
I understood the relation between Book & Category as both of them are tables in my database. However, Language is something that I get from a service and isn't persisted in my database.
The languages I get are just an ID and a Name for the language.
My question is: In order to link the language ID to my LANG_ID (the ID of the language in my Book table), what annotation (ManyToOne, Entity, ...) should I write for Language? Should I also put it in my persistence.xml ? I tried a couple but it seems like it's not working well.
Thank you very much
I don't think it is good practice to mix persisted data with non-persisted data as it can cause other unexpected problems. Anyway you can try something like this:
#Entity
#Table(schema = Constants.SHEMA, name = "Book")
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "TITLE")
private String title;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
#Column(name = "LANG_ID")
private Integer langId;
#Transient
private Language language;
#PostLoad
public void loadLanguage() {
// get the language data here
}
}
The language field has no database table, so you cannot use any mapping annotation. From the Java EE docs:
public #interface Transient
Specifies that the property or field is not persistent. It is used to annotate a property or field of an entity class, mapped superclass, or embeddable class.
Example:
#Entity
public class Employee {
#Id int id;
#Transient User currentUser;
...
}
The #PostLoad annotation declares a method to be called after the entity is loaded:
public #interface PostLoad
Specifies a callback method for the corresponding lifecycle event. This annotation may be applied to methods of an entity class, a mapped superclass, or a callback listener class.
First of all, did you consider to store language in your database? I mean language are mostly the same, doesn't change too often, you can also store in a properties file and read them at runtime to use them later.
Anyway, I think you should:
first get from external system languages
store in variable / in memory cache ( like a Map<Long,String> where you can store id and name )
read your data from database
for each row you do
read book language id, read the cache, get out data you need
If you can't change model, just use a dto with your entity and the language and you're fine

Spring Boot & JPA inner Join of 2 Tables

unfortunately I have a small understanding problem.
I'm trying to use JPA to create an inner join via 2 tables.
The situation:
Table: Projects
Fields:
ID (PK)
Name
Table: Users
Fields:
ID (PK)
username
password
Table: permissions
Fields:
ID (PK)
permissionName
Table: permissionsMapping (NO PK)
project_id (FK => projects.ID
user_id (FK => users.ID)
permission_id (FK => permissons.ID)
I need all projects that have at least one entry in the permissionsMapping table.
It is important to say that you are using it for logging in to the REST service OAuth2 so Users.ID = Current User should be set.
the Project Model:
public class ProjectModel
{
#Column(name = "ID", nullable = false)
private long ID;
#Column(name = "Name", nullable = false)
private String Name;
enter code here
#OneToMany(cascade=CascadeType.ALL, targetEntity=PermissionsMappingModel.class)
private Set<PermissionsMappingModel> permissionsMapping;
}
the permissionsMapping Model:
public class PermissionsMappingModel
{
#Column(name = "project_id", nullable = false)
private long project_id;
#Column(name = "user_id", nullable = false)
private long user_id;
#Column(name = "permission_id", nullable = false)
private long permission_id;
}
between ask
If I do not use an #Id anotation in permissionsMapping the service does > not start anymore because it says it misses the identifier. So how do I > tell him that the table has no primary key?
How do I connect that best now?
In the best case I get all the projects for the current user
In the first step, the permission would not be that important. It would be important that I get all projects for which there is an entry with the current user in the permissionsMapping table.
As I described it, I get the following error:
Caused by: org.hibernate.AnnotationException: Illegal attempt to map a
non collection as a #OneToMany, #ManyToMany or ...
I realize that this is probably the basics, but I do not quite understand it yet
Can anyone explain to me?
You need to use #EmbeddedId and #Embeddable annonations for defining you composite entity and key.
#Data #Entity #Table(name="Projects")
public class Projects {
#Id
private Long projectId;
private String projectName;
#OneToMany(mappedBy = "mappingId.projectId")
private Set<PermissionMapping> permissionMappings = new HashSet<PermissionMapping>();
}
#Data #Entity #Table(name="Users")
public class Users {
#Id
private Long userId;
private String userName;
private String password;
}
#Data #Entity #Table(name="Permissions")
public class Permissions {
#Id
private Long permissionId;
private String permissionName;
}
#Data #Entity #Table(name="PermissionMapping")
public class PermissionMapping {
#EmbeddedId
private PermissionMappingId mappingId;
#Embeddable
#Data
public static class PermissionMappingId implements Serializable{
private Long projectId;
private Long userId;
private Long permissionId;
}
}
For more reading:
1. EmbeddedId
2. Embeddable

Issue with One to One Mapping in Hibernate?

I am just started with hibernate 4 (annotations), I have following three model Classes with One to One and One to Many Relationship.`
#Entity
#Table(name = "USERS",uniqueConstraints = {#UniqueConstraint(columnNames={"EMAIL_ID"})})
public class UserBasicInfo implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="userId")
private Long userId;
#Column(name="EMAIL_ID")
private String emailId;
#Column(name="PASSWORD")
private String password;
#OneToMany(fetch = FetchType.LAZY, mappedBy="userBasicInfo",cascade = CascadeType.ALL)
private Set<UserDeviceInfo> userDeviceInfo;
#OneToOne(mappedBy="userBasicInfo",cascade = CascadeType.ALL)
private UserAdvancedInfo userAdvancedInfo;
///////// Getter setters
}
and
#Entity
#Table(name = "USERS_ADVANCED_DETAILS")
public class UserAdvancedInfo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ADVANCED_INFO_ID")
private Integer advancedInfoId;
#Column(name="GENDER")
private Gender gender;
#Column(name="UNIT_OF_MEASUREMENT")
private UnitOfMeasurement unitOfMeasurement;
#Column(name="HEIGHT")
private Double height;
#OneToOne
#JoinColumn(name="userId", insertable = true, updatable = true, nullable = true)
private UserBasicInfo userBasicInfo;
//////////////////////// Getter and Setter ////////////////////////
}
On the basis of above model classes I have two different REST APIs.
In the first API, only basic info will inserted and that is working fine.
But in the second API I have to update On UserAdvancedInfo on the basis of primary key of UserBasicInfo.
I have used update, merge, saveOrUpdate, but instead of updating data these all methods inserting a new row every time.
Kindly help me out.
Thanks,
To update the entity, you should need to set primary key to UserAdvancedInfo object and set UserBasicInfo object with there primary key to UserAdvancedInfo object :
UserAdvancedInfo info = new UserAdvancedInfo();
UserBasicInfo userInfo =new UserBasicInfo();
info.setUserBasicInfo(userInfo);
Hibernate checks for primary key for updation.

Is there a way to delete a child entity in a #OneToMany relationship (with a JoinTable) without having to fetch the full collection?

I've got the following classes/relationship (getters & setters not displayed, but present):
public class Contract implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
private String number;
private String volume;
#OneToMany(cascade=CascadeType.REMOVE)
#JoinTable(joinColumns = #JoinColumn(name = "contract_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "attachment_id", referencedColumnName = "id"))
private List<Attachment> attachments;
}
public class Attachment implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
#Lob
#Basic(fetch=FetchType.LAZY)
#Column(length=2147483647)
private byte[] contents;
private String name;
}
As per my needs/design, I am using a join table.
If I want to delete an attachment from the Contract, I need to load the Contract, and then loop through all the attachments until I find the one I want to remove and remove it from the list.
Although this is functional, it will require a lot of DB communication. If the list of attachments is long, and contains large contents, it will also require large bandwidth.
Is there any other way I can remove it? If I try to remove the attachment directly (ex: Attachment.findById().delete()), it will fail due to the FK relationship - won't it? (I haven't tried this yet, but I suspect it).
Additionally, if I have a very large list of attachments, iterating through them one by one until I find the correct one is not very efficient either.
Does JPA provide any other/better solution?
There's one workaround solution I know - you can create an entity class for join table.
You will have to give a name to your join table within #JoinTable annotation (name attribute), lets say ContractAttachment. Then you can create entity:
#Entity(name = "ContractAttachment") // note the same name of table
#IdClass(ContractAttachmentId.class)
public class ContractAttachment implements Serializable {
static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name="contract_id") // same mappings for columns
private Contract contract;
#Id
#ManyToOne(cascade = CascadeType.REMOVE)
#JoinColumn(name="attachment_id") // same mappings for columns
private Attachment attachment;
// you will also have to override equals and hashcode methods here
}
The class ContractAttachmentId should look like:
public class ContractAttachmentId implements Serializable {
private long contract; // note the same fields names
private long attachment;
// this class should also implement hashcode and equals
}
Now you can remove a single entry in join table and even cause attachment object to be deleted too.
ContractAttachment ca = em.createQuery("select ca from ContractAttachment ca " +
"where ca.contract = :contract and ca.attachment = :attachment")
.setParameter("contract", selectedContract)
.setParameter("attachement", selectedAttachment)
.getSingleResult();
em.remove(ca);

How can I mark a foreign key constraint using Hibernate annotations?

I am trying to use Hibernate annotation for writing a model class for my database tables.
I have two tables, each having a primary key User and Question.
#Entity
#Table(name="USER")
public class User
{
#Id
#Column(name="user_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="username")
private String username;
// Getter and setter
}
Question Table.
#Entity
#Table(name="QUESTION")
public class Questions extends BaseEntity{
#Id
#Column(name="question_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name="question_text")
private String question_text;
// Getter and setter
}
And I have one more table, UserAnswer, which has userId and questionId as foreign keys from the above two tables.
But I am unable to find how I can reference these constraints in the UserAnswer table.
#Entity
#Table(name="UserAnswer ")
public class UserAnswer
{
#Column(name="user_id")
private User user;
//#ManyToMany
#Column(name="question_id")
private Questions questions ;
#Column(name="response")
private String response;
// Getter and setter
}
How can I achieve this?
#Column is not the appropriate annotation. You don't want to store a whole User or Question in a column. You want to create an association between the entities. Start by renaming Questions to Question, since an instance represents a single question, and not several ones. Then create the association:
#Entity
#Table(name = "UserAnswer")
public class UserAnswer {
// this entity needs an ID:
#Id
#Column(name="useranswer_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "question_id")
private Question question;
#Column(name = "response")
private String response;
//getter and setter
}
The Hibernate documentation explains that. Read it. And also read the javadoc of the annotations.
There are many answers and all are correct as well. But unfortunately none of them have a clear explanation.
The following works for a non-primary key mapping as well.
Let's say we have parent table A with column 1
and another table, B, with column 2 which references column 1:
#ManyToOne
#JoinColumn(name = "TableBColumn", referencedColumnName = "TableAColumn")
private TableA session_UserName;
#ManyToOne
#JoinColumn(name = "bok_aut_id", referencedColumnName = "aut_id")
private Author bok_aut_id;
#JoinColumn(name="reference_column_name") annotation can be used above that property or field of class that is being referenced from some other entity.

Categories

Resources