JPA - Create or use Foreign Key - java

Okay I have 2 entities -:
Teacher
Department
One teacher can belong to one department and one department can have many teachers.
So the requirement is that Teacher entity contains a mappedBy attribute that maps the relation.
Now the problem that I am facing is that if a Department does not exist then it is created and if not a query fetches the department and that is used in the the teacher object.
Now the problem is that if the Department exists I have to use merge() if not I have to use persist() since the department will be newly inserted. Is there anything I can do that allows for dynamic use of merge or persist ?
The code is as follows-:
Department.java
#Entity
#Table (name = "DEPARTMENTS")
#NamedQueries({
#NamedQuery(name = "Department.findById",
query = "SELECT d FROM "
+ "Department d "
+ "WHERE d.deptId = :id")
})
public class Department extends STSEntity{
private static final long serialVersionUID = 1L;
#Column (name = "DEPT_ID", length = 10,
unique = true, nullable = false)
private String deptId;
#OneToMany (targetEntity = Teacher.class,
mappedBy = "dept")
private Collection<Teacher> teachers = new ArrayList<>();
Teacher.java
#Entity
#Table (name = "TEACHER_DATA")
#NamedQueries ( {
#NamedQuery(name = "Teacher.getByDept",
query = "SELECT t "
+ "FROM Teacher t "
+ "WHERE t.dept = :dept")
})
public class Teacher extends STSEntity {
private static final long serialVersionUID = 1L;
#Column (name = "TEACHER_ID", unique = true, nullable = false, length = 10)
private String teacherId;
#ManyToOne (targetEntity = Department.class,
fetch = FetchType.EAGER,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
#JoinColumn (name = "DEPT_ID", nullable = false)
private Department dept;
Anyway I can use both ? Any help will be appreciated
Basically if the foreign key exists use it or otherwise create it.

Related

JPA/Hibernate: invalid COUNT query alias for JOINED inheritance

I'm using Spring boot 2.3.12.RELEASE and Hibernate 5.4.32.Final. I'm using JOINED inheritance strategy for several classes with parent entity mapped like this:
#Entity
#Table(name = "dos_t_requests")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Request {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_requests")
#SequenceGenerator(name = "sq_requests", sequenceName = "dos_sq_requests", allocationSize = 1)
private long id;
#Column(name = "req_num")
private String number;
#Column(name = "created_at")
private LocalDateTime createdAt;
#Column(name = "edited_at")
private LocalDateTime editedAt;
#Column(name = "executed_at")
private LocalDateTime executedAt;
#Column(name = "issued_at")
private LocalDateTime issuedAt;
#Lob
#Column(name = "message")
private String message;
...
}
And here is one of child entities:
#Entity
#Table(name = "dos_t_stud_reqs")
#PrimaryKeyJoinColumn(name = "id")
public class StudentRequest extends Request {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stud_id")
private Student student;
...
}
I need to count the total number of rows of child entities to paginate the results. So I have a method that does all the work:
public Page<RequestPartInfo> getAll(DbUser dbUser, int page, int pageSize) {
...
String cntQuery;
String entQuery;
if (dbUser.getDbType() == DbUserType.STUDENT) {
cntQuery = String.join("\n",
"select count(r.id)",
"from StudentRequest r",
"where r.student.id = :userId");
entQuery = String.join("\n",
"from StudentRequest r",
"join fetch r.document d",
"join fetch d.recipient rc",
"join fetch r.curator c",
"join fetch r.executor e",
"join fetch r.issueType it",
"join fetch r.status st",
"where r.student.id = :userId",
"order by r.createdAt desc");
}
...
long count = em.createQuery(cntQuery, Long.class).setParameter("userId", dbUser.getId()).getSingleResult();
List<RequestPartInfo> requests = em.createQuery(entQuery, Request.class).setParameter("userId", dbUser.getId())
.setFirstResult(page * pageSize).setMaxResults(pageSize).getResultList()
.stream().map(this::mapPartInfo).collect(Collectors.toList());
return PagingUtil.createPage(requests, page, pageSize, count);
}
When I call this method, Hibernate generates an incorrect database query:
select count(studentreq0_.id) as col_0_0_ from oksana.dos_t_stud_reqs studentreq0_ where studentreq0_.id=studentreq0_1_.id and studentreq0_.stud_id=?
The problem with this request is that the WHERE section uses an alias that is not present in the FROM section. So I get following error: java.sql.SQLSyntaxErrorException: ORA-00904: "STUDENTREQ0_1_"."ID": invalid identifier.
I tried to search for a solution to the problem on the internet and found a similar problem here. But it says that this bug has been fixed in Hibernate 5.4.19.

Not able to map fields of different entities with many to many relationship

DB Schema - 2 tables (user and role) have many to many relationship and are bridged by an intermediate table (user_role) in postgres. I want to fetch all the roles and the name of a person who created it. Name is available in the users table but all the other details are in roles table. Roles table has a field created_by (User_id of the person who created the role).
I am trying to build a GET Request to view all the roles of a given id with the name of the person who created it
Entity class Users1.java
#Data
#Entity
#Table(name = "user")
public class Users1 {
#Id
#Column(name = "user_id")
private Long user_id;
#Column(name = "org_id")
private Long org_id;
#Column(name = "boss")
private Boolean boss;
#Column(name = "firstname")
private String firstname;
#Column(name = "created_by")
private Long created_by;
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_on")
private Date created_on;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {CascadeType.ALL})
#JoinTable(name = "user_role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "created_by")})
private List<Role> roles = new ArrayList<Role>();
// Getters and Setters
Entity class Role.java
#Data
#Entity
#Table(name = "role")
public class Role implements Serializable {
private static final long serialVersionUID = -2343243243242432341L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private Long role_id;
#Column(name = "org_id")
private Long org_id;
#Column(name = "role_name")
private String role_name;
#Column(name = "created_by")
private Long created_by;
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_on")
private Date created_on;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {CascadeType.ALL},
mappedBy = "roles")
private List<Users1> users = new ArrayList<>();
// Getters and Setters
roles.repository class
#Repository
#Transactional
public interface RolesRepository extends CrudRepository<Role,Long> {
#Query(value ="select r.*,u.firstname as firstname
from user u
join user_role ur on u.user_id = ur.user_id
join role r on ur.role_id = r.role_id
where(true = (select u.boss from user u where u.user_id = ?2) and r.org_id = ?1)
or
(false = (select u.boss from user u where u.user_id = ?2) and r.created_by =?2)",
nativeQuery=true)
public Iterable<Role> findAllById(Long org_id,Long created_by );
#Query("from Users1 u where u.user_id=?2 and u.org_id = ?1")
public Users1 findUserbyOrgId(Long org_id, Long created_by);
}
roles.Controller class :
public ResponseEntity<Iterable<Role>> getAllRoles(#RequestParam("org_id") Long org_id,
#RequestParam("created_by") Long created_by ) throws Exception {
if( null != rolesRepository.findUserbyOrg(org_id, created_by)) {
Iterable<Role> Roles = rolesRepository.findAllById(org_id, created_by); }
GET Response from postman:
[
{
"roleId": 3,
"org_id": 2,
"roleName": "manager",
"createdBy": 5,
"createdOn": 1591716178419,
}
]
I'm getting everything except the firstname. I'm not sure how to fetch that in my GET API. Any help would be really appreciated.
Not a direct answer to your question, but #ManyToMany is generally not encouraged in the real field due to the following reasons.
The joining table may need to have more information (but can't)
Since the joining table is hidden, query result is hard to anticipate.
The recommended approach is to
decompose two #ManyToMany classes to two #OneToMany classes +
one #ManyToOne class (joining table)
elevate the joining table to an entity class.
There are many practices of such cases in stackoverflow and youtube. I think it will ultimately save you more time to switch to this approach.

#Formula to count object from a #OneToMany association in spring data jpa

In a java rest api, with spring boot, and spring-dta-jpa, db is postgresql
I have a Book entity that contains bookCopies. These bookCopies can be available or not.
I'm trying to retrieve the book with the numbers of copies available.
The books can be searched by title or author name.
I manage to get the info by adding a #Transient field in my book entity, with the #Transient annotation on a getNbCopiesAvailable() method, but I'm asked to do it with #Formula annotation, and I can't figure out how to do it.
I'm actually getting an error :
org.postgresql.util.PSQLException: ERREUR: plus d'une ligne renvoyée par une sous-requête utilisée comme une expression
// which means that several lines are sent by the sub-query
Here are my entities:
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String isbn;
#NotNull
private String title;
#JsonIgnore
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
private List<BookCopy> copyList = new ArrayList<>();
#NotNull
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "book_author",
joinColumns = #JoinColumn(name = "book_id"),
inverseJoinColumns = #JoinColumn(name = "author_id"))
private Set<Author> authors = new HashSet<>();
#Formula("(SELECT COUNT(bc.id) FROM book b left join book_copy bc on bc.book_id = b.id WHERE bc.available = 'true' GROUP BY b.id)")
private Long nbCopiesAvailable;
#Entity
#Getter
#Setter
#Builder
#AllArgsConstructor
public class BookCopy {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String barcode;
private String editor;
private boolean available = true;
#ManyToOne(fetch = FetchType.LAZY)
private Book book;
Here is my repository with the query to retrieve books by title and author :
#Query("select b, a from Book b join b.authors a where b.title like %:title% and concat(a.firstName, ' ', a.lastName) like %:author%")
List<Book> findByTitleAndAuthor(#Param("title")String title,#Param("author") String author);
The
#Formula("(SELECT COUNT(bc.id) FROM book b left join book_copy bc on bc.book_id = b.id WHERE bc.available = 'true' GROUP BY b.id)")
was hard to write without having syntax errors, and it's strange to me to mix native sql and jpql. But if I try to write the #Formula in jpql it doesn't work at all.
I checked this topic that was the closest to my problem (#Formula count on ManyToMany), but it still doesn't work.
You need to reference the book in the where clause:
#Formula("(SELECT COUNT(bc.id) FROM book b " +
"left join book_copy bc on bc.book_id = b.id " +
"WHERE bc.available = 'true' " +
"and b.id = id " + // This is the important condititon
"GROUP BY b.id)")
private Long nbCopiesAvailable;
Otherwise your query is returning all books.

How to create JPA query that simultaneously searches in main table rows and in child rows with relation OneToMany

I have two entities. One of them is a child of the other one with a relation with OneToMany. Is it possible to implement search criteria that looks up simultaneously in both the main entity and all the child entities?
Example:
I have a Company with many employees. If I search with some text, I want to retrieve all the companies, which title contains that text or its employee's names contain that text.
Here are the example entities:
#Entity
public class Company extends AbstractEntity {
#Column(nullable = false, unique = true)
private String uuid;
#Column(nullable = false)
private String companyName;
#OneToMany(mappedBy = “company”, cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
protected Set<Employee> employees = new HashSet<>();
}
#Entity
public class Employee extends AbstractEntity {
#Column(nullable = false, unique = true)
private String uuid;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = “company_id”, nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Company company;
}
Here is the example query, that does not work :(
#Query(value = “SELECT c from Company c WHERE c.companyName LIKE CONCAT('%',:text,'%') or (SELECT e from c.employees e WHERE e.firstName LIKE CONCAT('%',:text,'%') OR e.lastName LIKE CONCAT('%',:text,'%'))”)
List<Company> findByText( #Param(“text” String text)
I think all you need is a left join here:
#Query(value = “SELECT c from Company c left outer join c.employees e
WHERE c.companyName LIKE CONCAT('%',:text,'%')
or e.firstName LIKE CONCAT('%',:text,'%')
or e.lastName LIKE CONCAT('%',:text,'%')”)

JPA multi fetch with #OrderColumn return multiple null value

Have 3 entities
Group
#Entity
#Table(name = "`group`")
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_group")
private Long id;
#OneToMany(mappedBy = "group",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#OrderColumn(name = "id_student")
private List<Student> students = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "id_faculty")
private Faculty faculty;
.....getters/setters
}
Student
#Entity
#Table(name = "student")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)
public class Student implements Serializable {
#Id
#Column(name = "id_student")
private Long id;
......
#OneToMany(mappedBy = "student",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private List<Rating> ratings = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "id_okr")
private OKR okr;
#ManyToOne
#JoinColumn(name = "id_group")
private Group group;
.....getters/setters
}
Rating
#Entity
#Table(name = "rating")
public class Rating implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_rating")
private Long id;
#Temporal(TemporalType.DATE)
#Column
private Date date;
#ManyToOne
#JoinColumn(name = "id_student")
private Student student;
#ManyToOne
#JoinColumn(name = "id_paragraph")
private Paragraph paragraph;
.....getters/setters
}
JPA Query
#Query(value = "SELECT g FROM Group g INNER JOIN FETCH g.students s LEFT JOIN FETCH s.ratings r WHERE g.id = :id AND s.group.id = g.id AND (r.student.id = s.id AND r.date BETWEEN :startMonth and :endMonth OR r IS NULL) GROUP BY s.id")
Group findGroupByStudentGroupId(#Param("id") Long id ,#Param("startMonth") Date startMonth, #Param("endMonth") Date endMonth );
I have the student with id 8000 and after extracting query the result list contains 8001 elements which contain 8 students that I have and 7993 null values. If I remove annotation #OrderColumn I have MultiBagException(cannot simultaneously fetch). If add #OrderColumn to rating #OneToMany association in the entity Student I have null values in Rating collection and in Student collection.
For me, the logic of #OrderColumn which return null values as biggest id in collection seems very strange. Is any way how to solve it?
Hibernate version 5.1.0 Final
Spring Data JPA 1.8.2
As you are trying to fetch students with particular group and ratings, you can have your query fetch from student entity and have join on Ratings entity. You don't have to explicitly add join for group entity as it is already in many to one mapping with student.
You can Change your query as follows:
#Query(value = "SELECT s FROM Student s LEFT JOIN s.ratings r WHERE s.group.id = :id AND (r.date BETWEEN :startMonth and :endMonth OR r IS NULL) GROUP BY s.id")
List<Student> findGroupByStudentGroupId(#Param("id") Long id ,#Param("startMonth") Date startMonth, #Param("endMonth") Date endMonth );

Categories

Resources