Directionality in JPQL joins for Spring Boot JPA? - spring-boot

Spring Boot here. I have the following two JPA entities:
#Entity
#Table(name = "accounts")
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "account_id")
private Long id;
// lots of stuff
#OneToOne(fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
#JoinColumn(name = "profile_id", referencedColumnName = "profile_id")
private Profile profile; // CAN be null
// Getters, setters & ctors
}
#Entity
#Table(name = "profiles")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "account_id")
private Long id;
// doesn't really matter lots of stuff in here
// Getters, setters & ctors
}
It is possible for some Accounts to have Profiles, and some will not (their Profiles will be null). I would like to create a CrudRepository impl that essentially does this query:
SELECT *
FROM profiles p
INNER JOIN accounts a
WHERE a.profile_id = null
Essentially, get me all the Profiles that are "orphaned" and are associated with any accounts here.
I'm confused as to whether I need an CrudRepository<Long,Account> impl or a CrudRepository<Long,Profile> impl and on what that impl would look like. My best pseudo-attempt thus far looks like:
public interface ProfileRepository extends CrudRepository<Profile, Long> {
#Query("FROM Account act WHERE act.profile = null")
public Set<Profile> findOrphanedProfiles();
}
Can anyone help fill in the gaps for me?

First, JPQL NULL check syntax is IS NULL, not == NULL (see JPQL docs - the link is for ObjectWeb, but applies to any JPA implementation)
Second, if you want to check for orphaned records, you definitely don't want to join in the table they're orphaned from.
Your last attempt at it,
public interface ProfileRepository extends CrudRepository<Profile, Long> {
#Query("FROM Account act WHERE act.profile = null")
public Set<Profile> findOrphanedProfiles();
}
was actually pretty close, just replace == null with is null and you should be all set.
EDIT: if you're looking for profiles that don't have accounts associated with them, EXISTS query is what you're looking for:
public interface ProfileRepository extends CrudRepository<Profile, Long> {
#Query("FROM Profile p WHERE NOT EXISTS (FROM Account a WHERE a.profile = p)")
public Set<Profile> findDisassociatedProfiles();
}
EDIT: if your Profiles maintain a list of associated accounts as a property (it's not included in the code you posted, but maybe it was omitted), you can query for disassociated profiles even shorter:
FROM Profile p WHERE p.accounts IS EMPTY

Related

spring JPA query to find events by locationId and categoryId

This is my events entity.
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Events {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long eventId;
#NotBlank(message = "Please Add Event name ")
#Length(max =100 ,min =2)
private String eventName ;
private String eventDescription;
// Each event is going to be mapped to a Location
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(
name = "location_id",
referencedColumnName = "locationId"
)
#NotNull
private Location location ;
#Temporal(TemporalType.DATE)
Date eventStartDate;
#Temporal(TemporalType.DATE)
Date eventEndDate;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(
name = "category_id",
referencedColumnName = "categoryId"
)
#NotNull
private Categories categories;
}
In my controller ,i have access to locationId and categoryId as request params .
I am not getting how to define my eventsRepository to access by locationId and categoryId. What changes should i make to this repo for things to work .
#Repository
public interface EventsRepository extends JpaRepository<Events,Long> {
public Events findByCateoryAndLocation()
}
I think a few adjustments you need to get rid of the issue. The query builder uses actual column names, so if your column name is locationId, then use 'findByLocationId(Integer locationId)' as a prototype. And please make sure entity names suit table names.
#Repository
public interface EventRepository extends JpaRepository<Event, Integer>
{
Event findByLocationIdAndCategoryId(Integer locationId, Integer categoryId);
}
This is off-topic, but I would like to mention that please do not use Lombok in entity classes. Getter, setter, and construction generators are ok, but hascode and string generators would be dangerous if you use lazy initialization. You may not get benefits from lazy loadings.
You have 2 ways to get your jpa-query working:
Modify your JPA-Query:
#Repository
public interface EventsRepository extends JpaRepository<Events,Long>
{
public Events findByCateories_IdAndLocation_id(Long categoriesId, long locationId)
}
Use a custom query - annotate your jpa with #Query and use a native query
There is one additional point from my side.
Naming of your classes. You are using plural which conflicts with the business logic - especially to the DB-relations(see Events to Categories). I would use singular (Event, Category)
This is exactly what I did to solve this with the help of native query.
#Query(
value = "SELECT * FROM events where category_id = ?1 AND location_id = ?2",
nativeQuery = true
)
public List<Events> findByCategoryIdAndLocationIdIn(Long CategoryId , Long LocationId);

Can Spring Data JPA Enforce ManyToOne Relationship via #JoinColumn

I have a Spring Boot application using Spring Data REST and Spring Data JPA. I have two domain entities: Student and Classroom, where many students can belong to the same classroom.
Student:
#Data
#Entity
#Table(name = "STUDENT")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STUDENT_ID")
private Integer studentId; // This Id has been setup as auto generated in DB
#Column(name = "ROOM_ID")
private Integer roomId;
#ManyToOne
#JoinColumn(name = "ROOM_ID", nullable = false, updatable = false, insertable = false)
private Classroom classroom;
}
Classroom:
#Data
#Entity
#Table(name = "CLASSROOM")
public class Classroom {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ROOM_ID")
private Integer roomId; // This Id has been setup as auto generated in DB
#OneToMany(mappedBy = "classroom")
private List<Student> studentList;
.....// other fields related to a classroom
}
And the Student repository:
public interface StudentRepository extends CrudRepository<Student , Integer>{
List<Student> findByClassroom(#Param("room") Classroom room);
}
And the Classroom repository:
public interface ClassroomRepository extends CrudRepository<Classroom , Integer>{
}
And I have a SpringApplication main file, but no controller.
There is already one classroom with room id=1 in the CLASSROOM table. When I gave the following request to POST to http://localhost:8080/students, a new student record was created in the Student table, which I expected it to fail because there isn't a classroom with id=100 exists in the CLASSROOM.
So my question is that: can Spring Data JPA enforce a manyToOne relationship or this foreign key enforcement has to be done on the database side (the not-null ROOM_ID column in the Student table is NOT defined as foreign key by our DBA for a legitimate consideration). If it has to be done on the database side, what is the point to define the manyToOne relationship in entity files?
Also, I know that I have redundant classroom fields in the Student entity, I just don't know which one to keep in the Student entity (the roomId or the "classroom" field), because when I create a student, I want to give only the roomId of a classroom in the request. Thanks!
{
"roomId": 100 // I expect this request to fail because no roomId=100 in the CLASSROOM table.
}
what is the point to define the manyToOne relationship in entity files
Because is an Object Relational Mapping tool that allows you define entity graphs.
You are currently passing roomId which in your Entity is just another field so you needs to remove that.
#Entity
#Table(name = "STUDENT")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STUDENT_ID")
private Integer studentId; // This Id has been setup as auto generated in DB
#ManyToOne
#JoinColumn(name = "ROOM_ID", nullable = false)
private Classroom classroom;
}
In Spring Data Rest you then defined an association by passing the self link of the referenced entity.
Your request then needs to look like the below:
{
"classroom" : "http://localhost:8080/classrooms/1"
}
Also removing the ID as you are POSTing a new record and, as you note, the ID is auto-generated in the database.
See also:
https://www.baeldung.com/spring-data-rest-relationships

How to retrieve data from many to many relationship in HQL, with Spring and JPA,when the joinTable is not an entity?

I have a similar case as this question [a link] (How do I do with HQL, many to many?)
I want to have the number of users (entity 1) for each libelle of role (entity 2). I defined a relation many to many between User and Role.
I am using Spring MVC, Hibernate, MySQL and JPA.
Entity 1: User
#Entity(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
#ManyToMany(mappedBy = "user")
private List<Role> role;
Entity 2: Role
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int ID;
private String libelle;
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = { #JoinColumn(name = "ID_role") },
inverseJoinColumns = { #JoinColumn(name = "id_user") }
)
private List<User> user;
JPA repository
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "select new map( count(u.id) as numberOfUsers,r.libelle as roleLibelle ) FROM user u join role r where r.ID =: ???? group by r.libelle")
List<Object> countByRoleList();
I am trying to figure out what =:id proposed in the question that i mentioned, have to be in my case. Instead of the "????" i tried ID, id , ID_role. All what i get is the error
"Named parameter not bound : ".
How can i solve that?
I suppose the type of your parameter is long. it's name is abc and it refer to an id.
Follow these steps:
Remove the space in front of your param .
so you will have (=:abc) not (=: abc).
Your query depends on an external parameter, uses the #param annotation for the specified param.
so you will have
".....countByRoleList(#Param("abc") long id );"
A code exemple
#Repository
public interface UserRepository extends JpaRepository<User,Long> {
#Query(value = "select new map( count(u.id) as numberOfUsers,r.libelle as roleLibelle ) FROM user u join role r where r.ID =:abc group by r.libelle")
List<Object> countByRoleList(#Param("abc") long id);
Note: abc is a provided param . It can come from an HTML page, a function. you can also provide it manually ...

spring data jpa findAll generated sql do not use join [duplicate]

I have created two entities Book and Book_Category with one-to-many relationship. When I issued BookCategoryRepository.findAll(), I expected hibernate to use 'INNER JOIN' query. But it just issued query to take data from Book_Category.
What I am missing? What should I do to make hibernate issue JOIN query?
Book.java
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "book_category_id")
private BookCategory bookCategory;
}
BookCategory.java
#Entity
#Table(name = "book_category")
public class BookCategory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#OneToMany(mappedBy = "bookCategory", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Book> books;
}
BookCategoryRepository.java
public interface BookCategoryRepository extends JpaRepository<BookCategory, Integer> {
}
bookCategoryRepository.findAll()
Hibernate uses by default a second query to retriev the child collection. One reason for this is a proper limit query. Otherwise, there would be more rows in the result set, than entities for the 1 side, if at least one has more than 1 child.
There exists an annotation to change this behaviour in hibernate which is ignored by the Spring Data Jpa Repositories. The annotation is #Fetch(FetchMode.JOIN). You might consider How does the FetchMode work in Spring Data JPA if you really need this behaviour.

Spring Data JPA won't handle Set<Enum> correctly

I'm trying out Spring Boot (latest version, using Hibernate 4.3.7) and I have a problem with my User entity. Here it is (most important part of it):
#Entity
#Table("usr")
public class User {
public static enum Role {
UNVERIFIED, BLOCKED, ADMIN
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column
#ElementCollection
private Set<Role> roles = new HashSet<Role>();
(rest of properties, getters and setters etc)
}
I am also using Spring Boot JPA repositories to save my entities:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
}
The problem is that when I add some Roles to roles set, Hibernate won't save it. It will create reference table, but it will only insert data to User table.
I tried to work this problem out, so I created pure Java + Hibernate project and copied my User class into it. Guess what? It worked!
Fun fact is that when I use pure Hibernate on my second project, created roles table looks different that the one created in my Spring Boot project.
On my clean Hibernate project I have table like:
User_roles:
User_Id bigInt(20)
roles int(11)
While using Spring JPA, I got
user_roles (notice lower case)
User (no "_id" part)
roles
What's going on? What I am doing wrong? Is it related to Spring Boot configuration? Thanks.
The following should match your existing tables.
#Entity
#Table("usr")
public class User {
public static enum Role {
UNVERIFIED, BLOCKED, ADMIN
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ElementCollection
#CollectionTable(name = "User_roles", joinColumns = #JoinColumn(name = "User_Id")
private Set<Role> roles = new HashSet<Role>();
(rest of properties, getters and setters etc)
}
My Solution:
Role.java
public enum Role {
USER, ADMIN
}
User.java
#Entity
#Table("usr")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "id"))
#Column(name = "roles", nullable = false)
#Enumerated(EnumType.STRING)
private Set roles;
(rest of properties, getters and setters etc)
}

Resources