Spring Boot & JPA inner Join of 2 Tables - java

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

Related

How to set Id of one entity to the Id of another entity using JPA?

I'm new at Spring Boot's JPA concept so need your help in deciding how to import just the ID of another entity, say User into HealthData entity. Following is my User entity:
#Entity
#Table(name = "user",uniqueConstraints = {#UniqueConstraint(columnNames = "email")})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#Email
#Column(nullable = false)
private String email;
private String imageUrl;
#Column(nullable = false)
private Boolean emailVerified=false;
#JsonIgnore
private String password;
#NonNull
#Enumerated(EnumType.STRING)
private AuthProvider authProvider;
private String providerId;
}
And I wish to define HealthData entity in the following manner :
#Entity
#Table(name = "HealthData",uniqueConstraints = {#UniqueConstraint(columnNames = "id")})
public class HealthData {
#Id
private Long id; //how to import id of User here?
#Column
private Double height;
#Column
private Double weight;
#Column
private int age;
...other columns
}
Now, I wish to use Id of User to this entity(kind of making parent-child relationship) . I don't want to add User class object in HealthData. I thought of using #OneToOne in HealthData but then it would add User in it. How can i just include Id from parent table in child table?
In this case, your HealthData has a reference to User, and I'm not sure why you wouldn't have mapped this as a foreign key. If you are able to do so, I'd suggest the following:
#Entity
#Table(name = "HealthData")
public class HealthData {
#Id
#OneToOne
#JoinColumn(name = "id")
private User user;
#Column
private Double height;
#Column
private Double weight;
#Column
private int age;
...other columns
}
JPA then handled setting the "ID" to the value within your user instance for you, and can persist both in the same transaction automatically. Allowing references to be marked as IDs is known as a derived ID and supported I believe since JPA 2.0.
As for efficiency, you can still lazy fetch or even not fetch the user instance. It is simple to just map the ID column as a basic using a slightly different approach:
#Entity
#Table(name = "HealthData")
public class HealthData {
#Id
private Long id;
#MapsId
#OneToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "id")
private User user;
#Column
private Double height;
#Column
private Double weight;
#Column
private int age;
...other columns
}
JPA will set both the User id as well as the healthData.id values based on what it generates for the user Id sequence when you set the healthData.user reference.
You can use getters and setters to set the value of user id in the healthdata table.

Hibernate mapping join on one column of constants table

I have 2 tables, the first one is quite variable, the second one contains only constants:
USER.ID USER.NAME USER.USER_TYPE (FK on USER_TYPE.ID)
INT VARCHAR(64) INT(1)
----------------------------------
1 Alex 3
2 Jane 1
3 Carl 3
USER_TYPE.ID USER_TYPE.VALUE
INT(1) VARCHAR(64)
------------------------------
1 PENDING
2 REGISTERED
3 BANNED
4 ACTIVE
The foreign key USER.USER_TYPE is required and refering to a primary key USER_TYPE.ID in table USER_TYPE (one-to-one relation). Here is my mapping in Hibernate.
User.java
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE")
private UserType userType;
}
UserType.java
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "VALUE")
private String value;
}
My goal is to keep the enumerated values in the database. How to map UserType's value instead of id to User and validate it? I want to pass the constant VALUE to the String instead of its ID.
private String userType;
The expected result of the first user would be:
User[id=1, name=Alex, userType=Banned]
User[id=2, name=Jane, userType=Pending]
User[id=3, name=Carl, userType=Banned]
My attempt was to use this annotation on definition of table twice with both colums switched
#SecondaryTable(name="USER_TYPE",
pkJoinColumns={#PrimaryKeyJoinColumn(name="ID", referencedColumnName="USER_TYPE")}
)
and get the VALUE with
#Column(table="USER_TYPE", name="VALUE")
private String UserType;
however it leads to the error
Unable to find column with logical name: USER_TYPE in org.hibernate.mapping.Table(USER) and its related supertables and secondary tables
First you need to change the relation from #OneToOne to #ManyToOne as UserType can be used by one or many User and User can have one and one UserType.
Secondly use referencedColumnName which references :
The name of the column referenced by this foreign key column.
So User entity will be:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE", referencedColumnName = "VALUE")
private UserType userType;
}
In UserType you should apply a unique constraint using #NaturalId to value field + do not provide its setter, to prevent duplicate values as It may lead to inconsistency:
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#NaturalId
#Column(name = "VALUE")
private String value;
}
Hope it solves the issue!
Enumerations could be simpler:
enum UserType {
PENDING,
REGISTERED,
BANNED,
ACTIVE
}
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#javax.persistence.Enumerated
private UserType userType;
}
If you really need separated table and #OneToOne relation, you can use #Formula from Hibernate:
#Formula("(select ut.value from user_type ut where ut.ID = USER_TYPE)")
private String userType;
For this really special requirement you could use SecondaryTable annotation.
That is, you don't need UserType entity, but declare attribute userType as String in User entity with column mapping to the secondary table "USER_TYPE".
First of all, I suggest you use ManyToOne relation. and Not CascadeType.ALL if you are not planning update or delete on USER_TYPE table.
If you do not need adding new UserTypes frequently use enum for it. It will just work as you want.
Second solution: As long as fetch = FetchType.EAGER you can add A transient field and return value of UserType in getter.

Define hibernate entity from 3 tables

I'm working on a legacy application and I was asked to integrate Hibernate (this is my first time working with it). This app has 3 tables (among others) as follows:
Table SITE Table PARAMS Table TRANS
========== ============== ===============
pk: id (INT) pk: p_id (INT) pk: t_id (INT)
lang_id (INT) name (CHAR) fk: p_id (INT)
value (INT) fk: lang_id (INT)
text (CHAR)
Then I need to get the parameters from PARAMS along with their translations, stored in TRANS, according to the language defined for the application, which is stored in SITE.
I've been struggling trying to understand how to make a join of the tables to get the data for the entity. I tried using #JoinTable, but I couldn't figure out how to make a 3-way join, so I started trying with #SecondaryTables without luck.
I defined this entity to map the requested data (I know this won't work as it is now) and I'm trying to figure out the proper way to make the join.
#Entity
#Table(name = "params")
#SecondaryTables(
{
#SecondaryTable(name = "trans", pkJoinColumns = #PrimaryKeyJoinColumn(name = "p_id")),
})
public class Tparam implements Serializable
{
#Id
#GeneratedValue
#Column(name = "p_id")
private int id;
#Column(name = "name")
private String Name;
#Column(name = "text")
private String visibleText
...
}
Any help is appreciated!
For reference, this SQL query gives me what I want:
SELECT * FROM params, lang, site WHERE params.p_id = lang.p_id AND lang.lang_id = site.lang_id;
You need to define 3 entities :
1-Param
2-Lang
3-Site
#Entity
#Table(name = "params")
public class Tparam implements Serializable
{
#Id
#GeneratedValue
#Column(name = "p_id")
private int id;
#Column(name = "name")
private String Name;
#Column(name = "text")
private String visibleText
...
}
#Entity
#Table(name = "lang")
public class Lang implements Serializable
{
#Id
#GeneratedValue
#Column(name = "lang_id")
private int id;
#ManyToOne
#JoinColumn(name = "p_id")
private Tparam param;
...
}
#Entity
#Table(name = "site")
public class Site implements Serializable
{
#Id
#GeneratedValue
#Column(name = "site_id")
private int id;
#ManyToOne
#JoinColumn(name = "lang_id")
private Lang lang
...
}
Then, when you need the data you can use Criteria (or Query) on Site entity to fetch data. Each record of site contains one Lang and each of them contains one Tparam.

hibernate one to many mapping with annotations - getting same values in the list

I am Linking User table with the Application Access. Here one User can have access to many applications.
I have done the mapping successfully with the below piece of code.
User entity object:
#Entity
#Table(name = "USER_TBL", uniqueConstraints = { #UniqueConstraint(columnNames = "USER_NAME") })
public class User implements Serializable {
.....
#Id
#GeneratedValue
#Column(name = "USER_ID", unique = true, nullable = false)
private Integer userId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<UserAppAssociation> userAssociatedApplications = new ArrayList<UserAppAssociation>();
Getter and setter for userAssociatedApplications
}
Application access object:
#Entity
#Table(name="APPLICATION_ASSOC_TBL")
public class UserAppAssociation implements Serializable{
#Id
#Column(name="user_id", unique=true, nullable=false)
private Integer userId;
#Column(name = "application_id")
private Integer appId;
#Column(name = "user_type_id")
private Integer userTypeId;
...
#ManyToOne
#JoinColumn(name="USER_ID",insertable=false,updatable=false)
private User user;
..
getters and setters
}
Issue:
I am getting the same values in the Application List ('userAssociatedApplications'). Though i have different values in the application access table, I get the same values in the list. The first row value is repeated in the list.
DB:
I have 'User' table and the mapping is with application access
User table: USER_TBL
Columns
user_id name phone
Application access table : APPLICATION_ASSOC_TBL
Columns
User_id application_id and User_type
Note - no primary key in this table
Sample data:
User_id application_id User_type
1 1 1
1 2 1
1 3 1
Issue: I am getting the first value 1,1,1 in the list thrice.
Expected: List should be with 3 different values
Kindly help. I am not sure whether i am missing anyting in the annotation mapping.
Looks like a problem with this
#Id
#Column(name="user_id", unique=true, nullable=false)
private Integer userId;
#ManyToOne
#JoinColumn(name="USER_ID",insertable=false,updatable=false)
private User user;
Try to use this mapping. Please, refer this as a guide for names and don't use unnecessary annotations
#Entity
#Table(name = "xxx_users", uniqueConstraints = { #UniqueConstraint(columnNames = "f_name") })
public class User {
#Id
#GeneratedValue
#Column(name = "f_id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<UserAppAssociation> applications = new ArrayList<UserAppAssociation>();
}
#Entity
#Table(name="xxx_user_applications")
public class UserAppAssociation {
#Id
#GeneratedValue
#Column(name = "f_id")
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="fk_user")
private User user;
}

How to model a friendship relationship in hibernate?

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;
...
}

Categories

Resources