HQL: Data from multiple entities with Many to Many relationship - java

I have 3 entities with Many to many relationship. For example Class, Student and Subject.
There are two relationship tables/entities ClassStudent and StudentSubject. There tables take care of the relationship. ClassStudent class looks something like this:
public class ClassStudent{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
#NotNull
private Long classId;
#Column(nullable = false)
#NotNull
private Long studentId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "classId", updatable = false, insertable = false)
private Class class
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "studentId", updatable = false, insertable = false)
private Student student;
}
StudentSubject class is also on the same pattern.
I am trying to write a query which each Class, it's Students and every Student's Subjects. Something Like
[
{
"class":{
"id":1,
"name":Maths
},
"studentDetails":[ {
"Student":{
"id":1
"name":"First Name"
},
"Subjects": [{
{
"id":1,
"code":xxx
},
{
"id":2,
"code":yy
}
}]
}]
I tried writing a basic query but it did not work at all.
SELECT c, cs.student, ss.subject FROM ClassStudent cs, Class c, Student s, StudentSubject ss, Subject subject"
+ " LEFT JOIN cs.student student"
+ " LEFT JOIN ss.subject subject"

Related

Hibernate Entity Mapping

Good night everyone,
I want to model a database that has the following entities and their perspective relationships:
But everytime I run the Java project to create the model at database, what I create is something like this:
There is another way to map this relationship? I'm mapping like that:
Article entity:
#Entity
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private Boolean featured;
#Column(nullable = false)
private String title;
#Column(nullable = false)
private String url;
#Column(name = "image_url", nullable = false)
private String imageUrl;
#Column(name = "news_site", nullable = false)
private String newsSite;
#Column(nullable = false)
private String summary;
#Column(name = "published_at", nullable = false)
private String publishedAt;
#OneToMany
#JoinColumn(name = "launches_id")
private List<Launches> launches;
#OneToMany
#JoinColumn(name = "events_id")
private List<Events> events;
}
Launches entity
#Entity
public class Launches {
#Id
private String id;
private String provider;
}
Events entity:
#Entity
public class Events {
#Id
private Long id;
private String provider;
}
And I want to map this JSON, with this same launcher and events appearing in other articles:
{
"id": 4278,
"title": "GAO warns of more JWST delays",
"url": "https://spacenews.com/gao-warns-of-more-jwst-delays/",
"imageUrl": "https://spacenews.com/wp-content/uploads/2019/08/jwst-assembled.jpg",
"newsSite": "SpaceNews",
"summary": "",
"publishedAt": "2020-01-28T23:25:02.000Z",
"updatedAt": "2021-05-18T13:46:00.284Z",
"featured": false,
"launches": [
{
"id": "d0fa4bb2-80ea-4808-af08-7785dde53bf6",
"provider": "Launch Library 2"
}
],
"events": []
},
{
"id": 4304,
"title": "Government watchdog warns of another JWST launch delay",
"url": "https://spaceflightnow.com/2020/01/30/government-watchdog-warns-of-another-jwst-launch-delay/",
"imageUrl": "https://mk0spaceflightnoa02a.kinstacdn.com/wp-content/uploads/2020/01/48936479373_2d8a120c8e_k.jpg",
"newsSite": "Spaceflight Now",
"summary": "",
"publishedAt": "2020-01-30T04:08:00.000Z",
"updatedAt": "2021-05-18T13:46:01.640Z",
"featured": false,
"launches": [
{
"id": "d0fa4bb2-80ea-4808-af08-7785dde53bf6",
"provider": "Launch Library 2"
}
],
"events": []
}
According to your diagram, it should be:
#ManyToOne
#JoinColumn(name = "launches_id")
private Launches launches;
#ManyToOne
#JoinColumn(name = "events_id")
private Events events;
...and not #OneToMany ;) (Can there be an "Article" (with id=x) having launchers_id=y AND launchers_id=z? No, vice versa!:)
...for the #OneToMany, you should find the join columns "on the other side" (of relationship(s)).
According to your JSON, it is OneToMany. But then, we have to draw/expect:
#Entity
class Article {
//... id, columns, blah
#OneToMany
#JoinColumn(name = "article_id") // Launches "owns the relationship"/column
private List<Launches> launches;
#OneToMany
#JoinColumn(name = "article_id") // Events...!
private List<Events> events;
}
Generally, (when you expose your db model via json,) ensure:
no "circles" (in bi-directional associations). (#JsonManagedReference, #JsonBackReference, #JsonIgnoreProperties, ... )
not to expose data, that you don't want to expose. (#JsonIgnoreProperties, ...)
Regarding Hibernate-ManyToOne, please refer to https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
Regarding spring-data-jpa, best to:
gs-data-jpa
gs-data-rest
spring-boot-ref, data-jpa
reference-doc, data-jpa
reference-doc, data-rest

Column 'foreign_key_id' cannot be null

I have a rest api exposed via #RepositoryRestResource from spring-data-rest-api. When I try to give the json payload from Postman to create a User linked to an Organization, it complains that Column 'organizationId' cannot be null, when I clearly provided it in the json.
{
"firstName": "Test",
"lastName": "User",
"email": "user#example.com",
"phoneNumber": "+12019582790",
"organizationId": "22bf93a5-e620-4aaf-8333-ad67391fb235",
"password": "example123",
"role": "admin",
}
Each user belongs to an organization, so it's a many to one relationship. I want Java to map the Organization that the User belongs to into the User as an Organization object.
User.java:
#Entity
#Table(name="user")
public class User {
#ManyToOne(targetEntity = Organization.class, fetch = FetchType.EAGER)
#JoinColumn(name = "organizationId", nullable = false)
private Organization organization;
}
Organization.java:
#Entity
#Table(name = "organization")
public class Organization {
#Id
#GeneratedValue(generator = "id-generator")
#Type(type = "uuid-char")
#Column(name="organizationId")
private UUID organizationId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "organization")
private Set<User> users;
}
Any help is greatly appreciated.
This is the appoach I ended up going with. Still would like to know why the #ManyToOne annotation isn't saving the organizationId as a foreign key in the User table by itself.
User.java:
#Type(type = "uuid-char")
#Column
private UUID organizationId;
#ManyToOne
#JoinColumn(name = "organizationId", insertable = false, updatable = false)
private Organization organization;

#OneToMany column is not present in JSON

I have the following entities:
Book.java
#Entity #Data
public class Book {
#Id
private Long id;
#Column(unique = true, nullable = false)
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "book")
#JsonManagedReference
private List<Note> notes;
}
Note.java
#Entity #Data
public class Note {
#Id
private Long id;
#Column(nullable = false)
private String title;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "book_id", nullable = false)
#JsonBackReference
private Book book;
}
When I call my BookRestController, it returns a JSON containing all the properties I need:
{
"id": 15,
"title": "A fé explicada",
"author": "Leo J. Trese",
"notes": [{
"id": 10,
"title": "Sobre o pecado mortal"
}]
}
But when I call NoteRestController, the Book attribute is missing:
{
"id": 10,
"title": "Sobre o pecado mortal"
// missing "book" property here...
}
What am I doing wrong?
I'm using #OneToMany and #ManyToOne annotations to declare that it's a 1-N relationship; #JsonBackReference and #JsonManagedReference have the simple purpose to avoid infinite recursion.
Of course Book is omitted, that is what literally #JsonBackReference is for
#JsonBackReference is the back part of reference – it will be omitted from serialization.
(https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion)
The solution is as simple as it sounds: Instead of returning a Note entity in your REST controller (which I try to avoid as far as possible anyway to keep entities as entities that don't have any use in the REST context anyway) you can create a explicit transport object called e. g. NoteDTO which contains a reference to a book (that omits the notes of the book to not have infinite recursion):
public class NoteDTO {
private Long id;
private String title;
private BookReferenceDTO book;
// getters and setters
}
public class BookReferenceDTO {
private Long id;
private String title;
// getters and setters
}

How to fetch all many-to-many relations in minimum count of queries?

How can I fetch all many-to-many relations using a minimal number of queries?
I mean without any n+1 queries.
3 - it's normal
I have an entity:
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
}
#Entity
#Table(name = "stations")
public class Station {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
}
#Entity
#Table(name = "songs")
public class Song {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
#ManyToMany
#JoinTable(
name = "songs_stations",
joinColumns = {
#JoinColumn(
name = "song_id",
referencedColumnName = "id"
)
},
inverseJoinColumns = {
#JoinColumn(
name = "station_id",
referencedColumnName = "id"
)
}
)
private List<Station> stations;
#ManyToMany
#JoinTable(
name = "songs_tags",
joinColumns = {
#JoinColumn(
name = "song_id",
referencedColumnName = "id"
)
},
inverseJoinColumns = {
#JoinColumn(
name = "tag_id",
referencedColumnName = "id"
)
}
)
private List<Tag> tags;
}
And a repository:
public interface SongRepository extends CrudRepository<Song, Long> {
#Query("SELECT s FROM Song s LEFT JOIN FETCH s.tags LEFT JOIN FETCH s.stations")
public List<Song> completeFindAllSongs();
}
so, I can't use eager loading in completeFindAllSongs() cause of cannot simultaneously fetch multiple bags
Can't use #NamedEntityGraph
And I can't load data manually, because I don't have access to songs_tags table
Please don't advise to use #LazyCollection
What should I do?
so, i can't use eager loading in completeFindAllSongs() cause of cannot simultaneously fetch multiple bags
This error should go away if you change your entities to use "Set" instead of "List" for OneToMany and ManyToMany relations.
Edit: So to answer to your question is: You only need 1 Query. Just use Sets and eager fetch whatever you want.
Keep in mind that by join fetching multiple collections you are creating full Cartesian product between the collection rows which may have a huge negative performance impact both on the database and application side.
You may want to consider using batch size to initialize the collections in batches.

Many-To-Many extra columns Spring JPA

Has passed a week and I'm struggling with a problem and it seems that I'm not able to find any answer to it.
I have this structure:
Album model:
#Entity
#Table(name = DatabaseConstants.ALBUM_TABLE_NAME)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Album {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 100)
private String name;
#Column(nullable = false)
private int imageVersion = 1;
#Column(length = 255)
private String description;
#Column(nullable = false)
private boolean single = false;
#Column(nullable = false)
private Long createdAt;
#Column(nullable = true)
private Long deletedAt;
// Relations
#OneToMany(fetch = FetchType.LAZY)
private List<AlbumView> albumViews;
// Getters and Setters
}
AlbumView model:
#Entity
#Table(name = DatabaseConstants.ALBUM_VIEW_RELATION_TABLE_NAME)
public class AlbumView {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private boolean bigger;
#Column(nullable = false)
private int position;
#Column(nullable = false)
private Long createdAt;
#Column(nullable = true)
private Long deletedAt;
// Relations
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "album_id")
private Album album;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "view_id")
private View view;
// Getters and Setters
}
View model:
#Entity
#Table(name = DatabaseConstants.VIEW_TABLE_NAME)
public class View {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private Long createdAt;
#Column(nullable = true)
private Long deletedAt;
// Relations
#OneToMany(fetch = FetchType.LAZY)
private List<AlbumView> albumViewList;
// Getters and Setters
}
I need to search a list of albums by an view. I that the query that I need is something like (Using #Query annotation from Spring JPA):
SELECT a, av.bigger, av.position FROM Album a, AlbumView av WHERE av.view.id = ?
But I can't map those two values (bigger, position) because they aren't on the Album model. I need to build an response like:
[
{
id: 1,
name: 'Test Album',
imageVersion: 1,
description: 'Description one',
single: true,
bigger: true,
position: 1
},
{
id: 2,
name: 'Test Album 2',
imageVersion: 1,
description: 'Description two',
single: true,
bigger: false,
position: 2
}
]
As far as I read, I can't use #Transient to help me here (because apparently JPA ignores it and the #Query don't fill those attributes) and I don't know other way to do it.
Thanks.
EDIT 1
I tried the #bonifacio suggestion using the following code in the Repository class of Spring JPA:
#Query("SELECT av.album.id, av.album.name, av.album.imageVersion, av.bigger, av.position FROM AlbumView av WHERE av.view.id = ?1")
List<AlbumView> findByViewId(Long id);
But I got the following response:
[
[
1,
"Test Best Hits",
1,
true,
1
]
]
The values is exactly that, but it is considering that is an array, and not an object like its supposed..
One way to do this is by changing the query to select the AlbumView entity, which contains all the desired fields from both AlbumView and Album entity, and also can use the View entity in the WHERE clause of your query.
In this case, one of the possible solutions would be using the following query:
SELECT av FROM AlbumView av WHERE av.view.id = ?1
Also, I want to note that in your first example you were trying to fetch three different objects per row, like the example bellow:
SELECT a, av.bigger, av.position FROM Album a, AlbumView av WHERE av.view.id = ?
The reason that it does not work is because when you select more than one column (or object), you are not automatically fetching the desired fields in just one line, since Hibernate will convert the result in a Object array (Object[]) for each line of your query's result. So, unless you really need to select multiple objects in one row, it's always recommended to return only one field, or entity on each SELECT you make.

Categories

Resources