JPA Query for #JoinTable - java

Scenario:
I have two entities User and Program
#Entity
#Table(name = "user")
public class UserEntity implements Serializable{
#Id
public Long id;
public String firstName;
public String email;
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(
name = "user_program",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "id")
},
inverseJoinColumns = {
#JoinColumn(name = "program_id", referencedColumnName = "id")
}
)
public ProgramEntity program;
}
JPQL:
SELECT
Program.name,
COUNT(user.id) AS user_count
FROM UserEntity AS user
INNER JOIN ProgramEntity AS program on ________ GROUP BY Program.name
I tried to get the number of users in each program but I couldn't get the result due to the JoinTable (intermediate table) is not an entity. Can anyone suggest a JPQ to connect the join table?

You can join using the entity object, 'ON' is not necessary. For you example,
SELECT prg.name, COUNT(user.id) AS user_count FROM UserEntity AS user INNER JOIN user.program AS prg GROUP BY prg.name

Related

JPA #JoinTable with composite (2 column) primary keys

In a spring-boot app, I've got the following entity definition:
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#JoinTable(name = "userrole",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Role> roles;`
I'm using Spring-data-jpa,Hibernate with H2 as the database.
The trouble is that spring-data-jpa, hibernate always generate/creates the join table (DDL) 'userrole' with a single column primary key. e.g. 'username'.
Hence, if records such as {'username', 'user_role'} and {'username', 'admin_role'} is inserted in the join table ('userrole'), the next insert fails with an error due to the 'duplicate' primary key.
I've tried using both columns in the above definition, as well as the following variation:
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name = "username"),
#JoinColumn(name = "role") })
private List<Role> roles;`
But that they resulted in the same or worse problems, e.g. and in the latter, even table creation fails because only a single column is used as primary key for the jointable. Role is simply another table with 2 columns 'role' and 'description', basically a role catalog.
How do we specify to JPA that the #JoinTable should use both 'username' and 'role' columns as composite primary keys?
edit:
I tried using an independent table/entity as suggested, thanks #Kamil Bęben
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#OneToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
mappedBy = "username",
orphanRemoval = true
)
#ElementCollection
private List<UserRole> roles;
UserRole is defined as such
#Data
#NoArgsConstructor
#Entity
#Table(name = "userrole")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userrole_seq")
Long id;
#Column(nullable = false, name = "username", length = 100)
private String username;
#Column(nullable = false, name = "role", length = 50)
private String role;
the repository for that user-roles join table is defined as
#Repository
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
UserRole findByUsernameAndRole(String username, String role);
List<UserRole> findByUsername(String username);
List<UserRole> findByRole(String role);
}
Admittedly, ugly, but that it works. And that somehow, it seemed to use the correct findByUsername() method to retrieve the roles as is relevant to the user, probably related to the 'mappedBy' clause. 'black magic'! There's lots more that I'd still need to find my way around JPA, Spring, Spring-data
edit2:
further update:
the original #JoinTable works as well.
But that the relations need to be specified as #ManyToMany
#ManyToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.MERGE
)
#JoinTable(name = "usersroles",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
private List<Role> roles = new ArrayList<Role>();
This creates 2 column primary keys as expected for the 'users-roles' table
Thanks to #Roman
If Role only has two columns, eg user_id and role, the way to map this in jpa would be as following
#ElementCollection
#CollectionTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "role")
List<String> roles = new ArrayList<>();
Otherwise, jpa really requires each entity's identifier and join columns to be separate columns, so Role entity would have to have columns like id, user_id and role_name. Could look like this .:
class Role {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id");
User user;
String roleName;
// Other fields
}
And in the User entity
#OneToMany(mappedBy = "user") // user is Field's name, not a column
List<Role> roles = new ArrayList<>();
Further reading

Spring Boot JPA how to retrieve data from a ManyToMany table?

I am learning Spring boot.
Now I have ManyToMany relationship with a User entity and a Team entity.
So an user can have several teams and a team can have several users.
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id", nullable =false, updatable = false)
private Long id;
#ManyToMany
#JoinTable(
name = "team_have",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn(name = "teamId"))
List<Team> haveTeams;
This is the Team entity:
#Entity
#Table(name="TEAM")
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long teamId;
#OneToMany(cascade = CascadeType.ALL, mappedBy="team")
private List<Task> tasks;
This is my repository:
public interface TeamRepository extends CrudRepository<Team, Long>{
List<Team>findByName(String name);
}
How can I find all teams which belonged to one user?
User already have mapped with List<Team>, you need to use UserRepository instead of TeamRepository.
public interface UserRepository extends CrudRepository<User, Long>{
User findByName(String name);
}
Here, you will get one user and that user have all teams which he belongs to. (Assuming username is unique)
OR
If you are having bidirectional Many-to-Many (Team also mapped Lis<User>) like following
#Entity
#Table(name="TEAM")
public class Team {
....
#ManyToMany(cascade = CascadeType.ALL, mappedBy="haveTeams")
private List<User> users;
Then you can define query method like following to get all teams for one user,
public interface TeamRepository extends CrudRepository<Team, Long>{
List<Team> findByUsers(User user);
}
The best way to build the connection between them would be to use a bi-directional relationship, like this:
User Entity
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
/* #Column(name = "id", nullable = false, updatable = false) You don't need this,
the name is taken from the name of the variable and it cannot be null since you
have #GeneratedValue */
private Long id;
#ManyToMany(fetch = LAZY, cascade = CascadeType.PERSIST)
#JoinTable(
name = "user_team", // I would name this user_team since the user can have many teams, and vice versa
joinColumns = #JoinColumn(name = "user_id" , referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "team_id", referencedColumnName = "id"))
Set<Team> teams = new HashSet<>();
}
Team Entity
#Entity
#Table(name="team")
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long teamId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "team")
private List<Task> tasks;
#ManyToMany(mappedBy = "teams", fetch = LAZY, cascade = CascadeType.PERSIST)
private Set<User> users = new HashSet<>();
}
Team Repo
public interface TeamRepository extends CrudRepository<Team, Long>{
#Query(value = "SELECT * FROM team t1 INNER JOIN user_team t2 ON t1.id = t2.team_id WHERE t2.user_id = ?1 ")
List<Team> findAllByUserId(long userId);
}
Example of use:
List<Team> teams = teamRepo.findAllByUserId(1);
A great tutorial:
https://attacomsian.com/blog/spring-data-jpa-many-to-many-mapping

JPQL Query error with Spring JPA and Hibernate

I am trying to fetch all the Skills relating to a specific Person.
Getting the following error when trying to fetch data from MySQL DB using JPQL in my Spring Boot application:
org.hibernate.QueryException: could not resolve property: person_skill of: com.skilltrack.app.Domain.Skill [SELECT s FROM com.skilltrack.app.Domain.Skill s JOIN s.person_skill ps WHERE ps.fk_person = ?1 ]
Here is my code:
Person.java
#Data
#EqualsAndHashCode(exclude = "personCourses")
#Entity(name = "person")
#Table(name = "person")
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer person_id;
#NotBlank
private String name;
#NotBlank
private String surname;
#NotBlank
private String email;
#NotBlank
private String password;
#NotBlank
private String personType; //admin or user
#JsonIgnore
#ManyToMany // this mapping is referred to in the Course class
#JoinTable(name = "person_course",
joinColumns = #JoinColumn(name = "fk_person", referencedColumnName = "person_id"),
inverseJoinColumns = #JoinColumn(name = "fk_course", referencedColumnName = "course_id"))
private List<Course> personCourses;
#JsonIgnore
#ManyToMany // this mapping is referred to in the Skill class
#JoinTable(name = "person_skill",
joinColumns = #JoinColumn(name = "fk_person", referencedColumnName = "person_id"),
inverseJoinColumns = #JoinColumn(name = "fk_skill", referencedColumnName = "skill_id"))
private List<Skill> personSkills;
Course.java
#Data
#EqualsAndHashCode(exclude = "skills")
#Entity(name = "course")
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer course_id;
#NotBlank
private String name;
#NotBlank
private String description;
#JsonIgnore
#ManyToMany(mappedBy = "courses") // mapping defined in Skill already
private List<Skill> skills;
#NotBlank
private Boolean completed; // 0 no 1 yes
#JsonIgnore
#ManyToMany(mappedBy = "personCourses")
private List<Person> coursePerson;
Skill.java
#Data
#EqualsAndHashCode(exclude = "courses")
#Entity(name = "skill")
#Table(name = "skill")
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer skill_id;
#NotBlank
private String name;
#NotBlank
private String description;
#JsonIgnore
#ManyToMany(cascade = CascadeType.ALL) // this mapping is referred to in the Course class
#JoinTable(name = "course_skills",
joinColumns = #JoinColumn(name = "fk_skill", referencedColumnName = "skill_id"),
inverseJoinColumns = #JoinColumn(name = "fk_course", referencedColumnName = "course_id"))
private List<Course> courses;
#JsonIgnore
#ManyToMany(mappedBy = "personSkills")
private List<Person> skill_person;
PersonRepository.java
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
#Query(value =
"SELECT s " +
"FROM skill s JOIN s.person_skill ps " +
"WHERE ps.fk_person = ?1 ")
List<Skill> getPersonSkills(Integer personID);
}
The issue is with the JPQL statement:
"SELECT s FROM skill s JOIN s.person_skill ps WHERE ps.fk_person = ?1 "
I have tried the following variations:
"SELECT s FROM skill s INNER JOIN s.person_skill ps WHERE ps.fk_person = ?1"
"SELECT s FROM skill s JOIN FETCH s.person p WHERE ps.fk_person = ?1"
"SELECT s FROM skill s JOIN s.person p JOIN s.person_skill ps WHERE ps.fk_person = ?1"
"SELECT s FROM skill s JOIN person p JOIN person_skill ps WHERE ps.fk_person = ?1"
How should I change my JPQL query to return a list of Skills relating to a specific Person?
Change your wrongly typed entity name from person_skill to skill_person defined in Skill.java
You have this error org.hibernate.QueryException because you have wrong name of field in JPQL.
Skill entity has field with name: skill_person.
You have another name of field in your JPQL query: person_skill.
It is possible to acieve the result without writing a custom query.
Spring builds endpoints like "skills" and "persons" by default, when used, they show other endpoints like "http://localhost:8080/skills/{id}/skill_person"
Playing around with those will lead you to find any combination of results.
This is possible since the many to many mappings are defined bidirectionally the way thy are in the current project and spring is awesome.

How I can Join this 3 Tables with JPA

I want to join these 3 Tables.
Here you see my Person Entity
#Entity
#Table(name = "Person", schema = "public")
public class PatientEntity {
#Id
#Column(name = "id")
private Long id;
#Column(name = "lastname")
private String name;
#OneToMany
#JoinTable(name = "person_contact", joinColumns = { #JoinColumn(name = "person_id") }, inverseJoinColumns = { #JoinColumn(referencedColumnName = "id") })
#Column(name = "contact")
private Set<ContactEntity> contacts;
//Getter Setters
And here is my contact entity:
#Entity
#Table(name="contact",schema="public")
public class ContactEntity {
#Id
#Column(name="id")
private Long id;
#Column(name="phone")
private String phone;
//Getter Setters
I just read the Persons from the Table with findById with a Spring JPARepository, but there is no Contact mapped. There is no error during my HTTP request, but instead of a Contact there is null and this error message:
com.sun.jdi.InvocationException occurred invoking method.
The business case is, that every Person can have one or more contact. Is it possible to make it with JPA Annotations or do I need to map it by myself with a JPQL? Or should I create an Entity for the middle table? (person_contact)
The Database is a PostgreSQL Database.
There is this notification too in the Log:
ERROR: column contacts0_.contacts_id does not exist
Perhaps you meant to reference the column "contacts0_.contact_id".
Position: 306
Your #JoinTable has incorrect #JoinColumn specifications and corresponds to the following ddl.
create table person_contact (person_id bigint not null, contacts_id bigint not null, primary key (person_id, contacts_id))
To map your db structure, use following (note removed #Column annotation)
#OneToMany
#JoinTable(name = "person_contact", joinColumns =
{
#JoinColumn(name = "person_id", referencedColumnName = "id"),
},
inverseJoinColumns = {
#JoinColumn(name = "contact_id", referencedColumnName = "id")
})
private Set<ContactEntity> contacts;
I also encourage you to read https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/ and reconsider a db structure without a join table (depending on your load and the effort to make this db change)

What hibernate / jpa annotation is required

I am trying to create a new User(entity1) - it has reference to a Group (entity2) via a link table Member (entity3)
A user has a Set of groups as a class variable.
When i create my user object i want to say this user will be a member of group n (there are pre defined users that are linked to by id (1,2,3,4,5,6...) each group has some associated data in the table.
Whenever I create my user object as follows;
User user = new User();
user.setActive(1);
user.setCrby("me");
user.setUsername("username");
user.setCrdate("2016-06-20 12:42:53.610");
user.setCrwsref("...");
user.setModby("...");
user.setModdate("2016-06-20 12:42:53.610");
user.setModswref("..");
user.setBackground("Y");
user.setPassword("password");
user.setFullName("me");
Group group = new Group();
group.setId(1);
Group group2 = new Group();
group2.setId(2);
Set<Group> sets = new HashSet<Group>();
sets.add(group);
sets.add(group2);
user.setGroups(sets);
userDao.addUser(user);
I keep getting errors telling me that certain columns cannot be null. What I actually want to happen here is not to be doing an insert in to the group table but associating a user to a line in the group table. Is there a particular way I can prevent the columns in the group table being modified? I think I need to modify the mappings between the link table - this is how much pojos link right now
User
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Group> groups = new HashSet<Group>(0);
Member link table
#Entity
#Table(name = "member")
public class Member implements Serializable
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Id
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String memberUsername;
Group
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
What is happening is there is no association to the link Member table so ideally should User have a set of member objects rather than a set of groups?
Thanks - this was quite hard to explain so sorry if it is hard to understand
This is a typical case for the #ManyToMany annotation. See for example:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
The relationship from User to Group is essentially ManyToMany. You could model this is using the #ManyToMany annotation however one drawback with this approach is you cannot save additional information about the group in the join table such as 'date_joined'.
See: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#ManyToMany
Using this approach you would not need the Join entity Member and the relationship on User would look like:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "member_id", referencedColumnName = "id")}, inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
private Set<Group> groups = new HashSet<Group>(0);
The alternative to using #ManyToMany is to use a Join entity Member(ship) as you have done. This would allow you to save additional data about the relationship (by defining additional field mappings in the Join entity).
In this case the mappings would look like:
User:
public class User{
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
//if required, you can 'hide' the join entity from client code by
//encapsulating add remove operations etc.
public void addToGroup(Group group){
Membership membershup = new Membership();
membership.setUser(this);
membership.setGroup(group);
memberships.add(membership);
)
public Set<Groupp> getGroups(){
//iterate memberships and build collection of groups
}
}
Membership:
public class Membership{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private Member member;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
}
Group:
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
}

Categories