JPQL ManyToMany query with count field - java

I have a Many to Many relationship between two entities, User and Movies in a spring boot application with spring security.
I want to made a REST API that finds all the movies and includes a new field that shows if the logged user did watched the movie or no.
I can't find an easy way to do it, I only found a solution creating a new DTO object in the query. I show my code next.
The entities are the following:
Movies:
#Entity
#Table(name = "movies")
public class Movies implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Size(max = 100)
#Column(name = "name", length = 100, nullable = false)
private String name;
#Column(name = "jhi_year")
private Long year;
#Size(max = 100)
#Column(name = "category", length = 100)
private String category;
#ManyToMany(mappedBy = "movies")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<User> users = new HashSet<>();
User:
#Entity
#Table(name = "user")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Size(max = 50)
#Column(name = "login", length = 50)
private String login;
#Size(max = 250)
#Column(name = "bio", length = 250)
private String bio;
#ManyToMany
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "user_movies",
joinColumns = #JoinColumn(name="user_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="movies_id", referencedColumnName="id"))
What I did is a #Repository class with a JPQL #query like this one:
#Query("select new com.test.service.dto.MoviesDTO(movies.id, movies.name, movies.year, movies.category, " +
" count(users.id)) from Movies movies left join movies.users users on users.login = ?#{principal.username} group by movies.id ")
Page<MoviesDTO> findAll(Pageable pageable);
This works fine, but is there a simpler way to do the this? It would be perfect to find a method to do this adding a new object in the entity class, avoiding making a new DTO object in the query.
I know that it is almost same issue than this one posted by myself some time ago, which remains a mystery to me.
Many, many thanks!

Thanks to jasarez for the answer. I changed the DOT for a projection and it works like a charm!
The repository:
#Query("select movies.id as id, movies.name as name, movies.year as year, movies.category as category, count(users.id) as moviesCount " +
"from Movies movies left join movies.users users on users.login = ?#{principal.username} group by movies.id")
Page<MovieWithUserData> findAllWithUserData(Pageable pageable);
and the projection:
public interface MovieWithUserData {
Long getId();
String getName();
String getCategory();
Integer getYear();
Long getMoviesCount();
}

Related

Spring Data findAll with filtered nested objects

I am currently developing a Spring Boot 2.5.5 app which needs to integrate some games. Each Game entity contains multiple GameProfile entities. Since the app and games are multilingual, we store all the generic fields in the GameProfile entity and all the others which are tied to the current language, are stored in an extra relation called GameProfileTranslation which references a Language entity.
Following are the three entities:
#Entity
#Table(name = "GAME_PROFILE")
#Getter
#Setter
public class GameProfile {
#Id
#Column(name = "GAME_PROFILE_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "game_profile_seq")
#SequenceGenerator(name = "game_profile_seq", allocationSize = 1, sequenceName="game_profile_sequence")
private Long id;
#Column(name = "IMAGE")
private String image;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GAME_ID")
private Game game;
#Column(name = "DIFFICULTY")
private GameDifficulty difficulty;
#OneToMany(mappedBy = "gameProfile", fetch = FetchType.LAZY)
private Set<GameProfileTranslation> translations = new HashSet<>();
}
#Entity
#Table(name = "GAME_PROFILE_TRANSLATION")
#Getter
#Setter
public class GameProfileTranslation {
#Id
#Column(name = "GAME_PROFILE_TRANSLATION_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "game_profile_translation_seq")
#SequenceGenerator(name = "game_profile_translation_seq", allocationSize = 1, sequenceName="game_profile_translation_sequence")
private Long id;
#Column(name = "TITLE")
private String title;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GAME_PROFILE_ID")
private GameProfile gameProfile;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "LANGUAGE_ID")
private Language language;
}
#Entity
#Table(name = "LANGUAGE")
#Getter
#Setter
#NoArgsConstructor
public class Language {
#Id
#Column(name = "LANGUAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "CODE")
private String code;
#Column(name = "VALUE")
private String value;
#OneToMany(mappedBy = "language", fetch = FetchType.LAZY)
private Set<User> users = new HashSet<>();
#OneToMany(mappedBy = "language", fetch = FetchType.LAZY)
private Set<GameProfileTranslation> gameProfileTranslations = new HashSet<>();
}
For the past two days I have been trying to create a repository method which returns all the GameProfile entities based on a languageId and a gameId given as #RequestParam to my Controller. To be more precise, I want to find all the GameProfiles and filter the nested translations object based on the language given.
I have tried two approaches, which are presented below. Both of these methods actually return all the GameProfile entities but the nested translations object contains all the GameProfileTranslation entities in the database. It is like the translations cannot be filtered by the language.
If I copy & paste the native query below in a Query Tool in pgAdmin I get the result I desire. However, using it through Spring Data returns all the translations.
My two approaches:
#Query(
nativeQuery = true,
value = "SELECT * " +
"FROM game_profile AS gp " +
" INNER JOIN game_profile_translation AS gpt ON gp.game_profile_id = gpt.game_profile_id " +
" INNER JOIN language AS l ON gpt.language_id = l.language_id " +
"WHERE l.language_id = :languageId AND gp.game_id = :gameId ")
List<GameProfile> findAllByGameIdAndLanguageId(#Param("gameId") Long gameId, #Param("languageId") Long languageId);
and
#Query(value = "SELECT gp " +
"FROM GameProfile AS gp JOIN FETCH gp.translations AS gpt " +
"WHERE gp.game.id = :gameId AND gpt.language.id = :languageId ")
List<GameProfile> findAllByGameIdAndLanguageId(#Param("gameId") Long gameId, #Param("languageId") Long languageId);
TLDR: Both of these methods return all the GameProfile entites without filtering the nested translations object. To be more exact, the translations field contains all the translations available in the database regardless of the languageId.
Is there a way to return a List<GameProfile> entities with the translations object filtered by the languageId?
What I have tried:
Filter child object in Spring Data Query
Filtering out nested objects in JPA query
Filter child object in Spring Data Query

Get recently updated results from Database when joining multiple tables using JPA in a Spring application

I am new to Spring and JPA and I am trying to write a job in Spring which runs every 3 hours and retrieve the records from Oracle Database.
I would like to only read the new/updated content from the past 3 hours (ideally from the last job run).
I have seen few examples in https://spring.io/blog/2011/02/10/getting-started-with-spring-data-jpa/ where we can create queries and retrieve the data based on our requirements, but in my current use case, I am not using queries instead using the java classes with the annotations and using Join columns between different tables. There are chances that only one of the sub table is updated or all the tables are updated with new content. I need to get the results if at least one of the table is updated/inserted with new content.
Campus is the main table and retrieves the data from Group and Region, I need to fetch the data if any new data is updated in Campus table or even any group/region is added/modified.
I am using JDK7 as of now.
Is there a way to accomplish the above requirement?
Below are the sample Java classes for reference.
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_CAMPUS")
public class Campus implements Auditable {
#Id
#Column(name = "ID)
#SequenceGenerator(name = "SIMPLE_ID", sequenceName = "SIMPLE_ID")
#GeneratedValue(generator = "SIMPLE_ID", strategy = GenerationType.AUTO)
private Long id;
#Column(name = "CAMPUS_NAME")
private String campusName;
#Column(name = "CAMPUS_ID", nullable = false)
private Long campusId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GROUP_ID")
private GroupType groupType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "REGION_ID")
private Region region;
....
...
}
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_GROUP_TYPE")
public class GroupType implements Auditable {
#Id
#Column(name = "GROUP_TYPE_ID")
#SequenceGenerator(name = "GROUP_TYPE_SEQUENCE", sequenceName = "GROUP_TYPE_ID")
#GeneratedValue(generator = "GROUP_TYPE_SEQUENCE", strategy = GenerationType.AUTO)
protected Long id;
#Column(name = "GROUP_TYPE_NAME", nullable = false)
protected String groupTypeName;
....
...
}
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_REGION")
public class Region implements Auditable {
#Id
#Column(name = "region_id")
#SequenceGenerator(name = "REGION_ID", sequenceName = "REGION_ID")
#GeneratedValue(generator = "REGION_ID", strategy = GenerationType.AUTO)
private Long id;
#Column(name = "REGION_NAME", nullable = false)
private String name;
...
..
}
Any help is Appreciated.

Spring CrudRepository - 'not equals' condition for child table(which is as List)

I've two entity with #OneToMany relationship
First Entity
#Entity
#Table(name = SchemaConstant.RESCHEDULE_TABLE_NAME)
public class RescheduleRequestEntity extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "RESCHEDULE_ID_GEN")
#SequenceGenerator(
name = "RESCHEDULE_ID_GEN",
allocationSize = 1,
sequenceName = SchemaConstant.RESCHEDULE_SEQUENCE_NAME)
private Long id;
private String adviseNo;
private LocalDate adviseDate;
private Long customerId;
#Enumerated(value = EnumType.STRING)
private AdviceStatus status;
#OneToMany(mappedBy = "reschedule", fetch = FetchType.LAZY)
private List<RescheduleDetailEntity> accountDetails;
}
Second Entity
#Entity
#Table(name = "RESCHEDULE_DETAILS")
public class RescheduleDetailEntity extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "RESCHEDULE_DETAILS_ID_GEN")
#SequenceGenerator(
name = "RESCHEDULE_DETAILS_ID_GEN",
allocationSize = 1,
sequenceName = "S_RESCHEDULE_DETAILS")
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ACCOUNT_ID", nullable = false)
private AccountEntity account;
#Enumerated(value = EnumType.STRING)
private AdviceStatus status;
#Enumerated(value = EnumType.STRING)
private TenureType tenureType;
private Integer tenure;
#ManyToOne
#JoinColumn(name = "ADVISE_ID")
private RescheduleDetailEntity reschedule;
}
AND Enum
public enum AdviceStatus {
OPEN,
ACTIVE,
CLOSE
}
I want to fetch data with condition Like
SELECT *
FROM RESCHEDULEREQUESTENTITY R, RESCHEDULEDETAILENTITY D
WHERE R.ID = :PID
AND D.ADVISEID = R.ID
AND D.STATUS <> "CLOSE"
"Data fetch from RescheduleRequestEntity with data from RescheduleDetailEntity where RescheduleDetailEntity.status is not equal "CLOSE" where "Status" is Enum type".
I create a JPA Repository class like following for fetch data
#Repository
public interface RescheduleRequestRepository
extends JpaRepository<RescheduleRequestEntity, Long>, JpaSpecificationExecutor {
Optional<RescheduleRequestEntity> findByAdviseNo(String adviceNo);
Optional<RescheduleDetailEntity> findByIdAndAccountDetails_StatusNot(
Long adviceId, AdviceStatus status);
}
but it's not fetch data with my desired condition,it's not ignore data which have Status "CLOSE"
You can make it a custom query using #Query annotation.

Passing Queries from Postgres to JPA

I'm newbie
I'm trying pass this Postgres query to JPA/JPQL
SELECT
DISTINCT(srv.code) AS Serv_Cod,
srv.id AS Serv_id,
srv.description AS Serv_Desc
FROM db.Category AS cat
join db.Classification AS cla ON cat.id = cla.cat_id
join db.Service AS srv ON srv.id = cla.srv_id
WHERE cat.id = 10
ORDER BY srv.id;
Now I want to write the same Query, I have the Entities with the same name Table.
Classification
#Entity
#Table(name = "Classification", schema = "db")
#Audited
public class Classification implements Identifiable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "srv_id", nullable = true)
private Service service;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cat_id", nullable = true)
private Category category;
....
}
Service
#Entity
#Table(name = "Service", schema = "db")
#Audited
public class Service implements Identifiable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "code", nullable = false)
private String code;
#Column(name = "description", nullable = false)
private String description;
....
}
I was reading, but I'm very confused...
I don't know how to write the ON for the JOIN, and establish the DISTINCT for a Column/Field.
Long myID = 25L;
this.em.createQuery("SELECT NEW SomeDto(srv.id, srv.code, srv.description)"
+ " FROM Classification cla"
+ "JOIN cla·cat_id cat"
+ "JOIN cla·srv_id srv"
+ "WHERE cat.id = :id"
,BaseObjectDto.class).setParameter("id", myID).getResultList();
Thank you for you valuable Help.
The query is very simple. When you have ToOne relationships you can navigate to the related entity. There is no need to JOIN ON.
Even with ToMany there is no need for the ON because this is already defined in the mapping.
So the query will look like:
SELECT NEW SomeDto(cla.service.id, cla.service.code, cla.service.description)
FROM Classification cla
WHERE category.id = :id

update all fields of an entity in jpa

I have tow entity User and Project that they have "one to many" relationship and I want to find the User then find the specific Project that belong to User and then update it, but I can't.
framework Struts2 + Hibernate .
#Entity (name = "User")
#Table (name = "users")
public class User implements Serializable{
#Id
#Column (name = "user_id", columnDefinition = "number")
#SequenceGenerator(name = "seq", sequenceName = "gen")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "seq")
private int id;
#Basic
#Column(name = "user_name", columnDefinition = "nvarchar2(20)")
private String userName;
#Basic
#Column(name = "password", columnDefinition ="nvarchar2(20)")
private String password;
#Basic
#Column(name = "create_date",columnDefinition = "date")
private Date creation_date;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<Project> projectses;
public List<Project> getProjectses() {
return projectses;
}
public void setProjectses(List<Project> projectses) {
this.projectses = projectses;
}
and Project entity
#Entity(name = "Project")
#Table(name = "project")
public class Project implements Serializable {
#Id
#Column(name = "project_id" , columnDefinition = "number")
#SequenceGenerator(name = "projectSeq", sequenceName = "projectGen")
#GeneratedValue(strategy = GenerationType.AUTO,generator = "projectSeq")
private int projectId;
#Basic
#Column(name = "project_name" , columnDefinition = "nvarchar2(20)")
private String projectName;
#Basic
#Column(name = "project_description" , columnDefinition = nvarchar2(20)")
private String projectDescription;
#Basic
#Column(name = "start_date",columnDefinition = "date")
private Date startDate;
#Basic
#Column(name = "due_date",columnDefinition = "date")
private Date dueDate;
#Basic
#Column(name = "project_status",columnDefinition = "nvarchar2(20)")
private String projectStatus;
#Basic
#Column(name = "project_amount",columnDefinition = "number(8)")
private int projectAmount;
Three corrections come in mind, that may make your code behave well:
Use EntityManager#find() instead of HQL to lookup entity by id;
Use only one of #Basic or #Column (I'd prefer #Column), there is no need to use both for a single element;
If error is "value is too long for column", maybe it is time to check if some of Project elements are longer than 20 chars defined in columnDefinition?
Check length of string elements in Project entities, and if there are some longer than 20 characters, say, 500, modify columnDefinition for those elements, e. g.:
#Column(name = "project_description", columnDefinition = "nvarchar2(500)")
private String projectDescription;
I also suggest to drop tables after such modifications (to allow JPA create new according to new definitions) or manually modify their definitions in DB.
User u = (User)entityManager.createQuery(SELECT u FROM User u JOIN FETCH u.Project where u.id = :id).setParameter("id",your_userId).uniqueResult();
Get The user object , you will get it with the set of projects associated with that user
update the data you want : -
List<Project> userProjects = u.getProjectses();
for(int i = 0 ; i < userProjects.size() ; i++){
Project p = userProjects.get(i);
entityManager.getTransaction().begin();
p.setProjectName("test");
entityManager.merge(p);
entityManager.getTransaction().commit();
}

Categories