Creating a custom spring JPA query with Date attribute - java

This is my first Java Spring project ever. I'm using PostgreSQL to store a WorkedDay entity as follows:
#Entity
#Table
public class WorkedDay {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
#JsonFormat(pattern = "yyyy-MM-dd")
private Date weekDay;
#Column
private Long employeeId;
#ManyToOne
#PrimaryKeyJoinColumn(name = "employeeId", referencedColumnName = "id")
private Employee employee;
#OneToMany
private List<WorkedHours> workedHours = new ArrayList<>();
}
All "WorkedDays" are stored in a PostgreSQL table using a WorkedDayRepository class that extends CrudRepository. I'm also creating a report service which should return a list of WorkedDays in a given month.
public class WorkedDayRepositoryImpl implements WorkedDayRepositoryCustom {
public List<WorkedDay> getReportByMonthValue(int monthValue) {
//service code implementation here
}
}
I'm currently facing problems creating this custom query, since I need to retrieve from the table all Date weekDay attributes with a specific month, passed as argument.
I'm inexperienced with Spring JPA. Is there a better(or simpler) way to do this? I tried to use Specifications and Querydsl but failed.

This should work
#Repository
public interface WorkedDayRepository extends CrudRepository<WorkedDay> {
List<WorkedDay> findByWeekDay_Month(int month)
}

You could try #Query in combination with the MONTH Function.
#Repository
public interface WorkedDayRepository extends CrudRepository<WorkedDay> {
#Query("select w from WorkedDay w where MONTH(w.weekDay) = ?1")
List<WorkedDay> findByWeekDay(int month)
}
Keep in mind that not all databases might support MONTH(). Otherwise you could work with SUBSTRING(w.weekDay,6,7)

Related

SQL native query to get a list of objects where date expiring in X

I have some "Card" object:
#Entity
#Table(name = "card", schema = "public")
#Data
#EqualsAndHashCode(callSuper = false)
public class Card {
#Id
#GeneratedValue
private Long id;
#Column(name="name")
private String name;
#Column(name="scope_time_limit")
private LocalDateTime expireDate;
}
and I want to prepare some native query where I can get the List of expiring cards based on the days I specify. I have this method:
List<Card> getExpiringCards(Integer numberOfDaysToExpiringCard) {
return cardRepository.getExpiringCardsByDay(numberOfDaysToExpiringAgreement);
}
What is the most optimal query? I was thinking about native query.
Thanks in advance.
You lack some details about what are you using and how are you accessing your DB etc. However, I wanted to show an example using Spring Data just for the rough idea.
Your repository:
public interface CardRepository extends JpaRepository<Card, Integer> {
List<Card> findByExpireDateAfter(LocalDateTime expireDate);
}
Somewhere in your service or wherever you use it:
#Autowired
CardRepository cardRepository;
List<Card> getCardsWillBeExpiredInDays(int days) {
LocalDateTime someDaysLater = LocalDateTime.now().plusDays(days);
return cardRepository.findByCreatedAfter(someDaysLater);
}

Spring Data JPA Query By Example with related entities

I seem to be unable to add QueryByExample probes that match related entities.
#Entity
#Data
public class ArtistEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#ManyToMany(fetch = FetchType.EAGER)
private Set<GenreEntity> genreList = new HashSet<>();
#Version
private Long version;
}
#Entity
#Data
public class GenreEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Version
private Long version;
}
#Repository
public interface ArtistRepository extends JpaRepository<ArtistEntity, Long> {
}
When I try the following query, according to Hibernate's logs, my probe isn't running any conditions against the Genre
GenreEntity genreEntity = new GenreEntity();
genreEntity.setName("Heavy Metal");
ArtistEntity artistEntity = new ArtistEntity();
Set<GenreEntity> genreEntitySet = new HashSet<>();
genreEntitySet.add(genreEntity);
artistEntity.setGenreList(genreEntitySet);
Example<ArtistEntity> example = Example.of(artistEntity);
Pageable pagination = PageRequest.of(0, 10);
artistRepository.findAll(example, pagination);
I also tried looking on the Spring Data JPA documentation regarding QBE, but I didn't find anything specifically mentioning this limitation, which brought me to assume it's an unexpected behaviour.
Currently, you cannot do this with Query By Example.
The spring document states that this only works with SingularAttribute.
Currently, only SingularAttribute properties can be used for property
matching. https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example.running
You want to search by a property that is a Set<GenreEntity> (genreList), which is a PluralAttribute. It is not possible to search by this field.
It will be ignored when building a query, as can be seen here: https://github.com/spring-projects/spring-data-jpa/blob/master/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java#L127
You can use Specification.
Advanced Spring Data JPA - Specifications and Querydsl. https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
For this you need to extend from interface JpaSpecificationExecutor:
public interface ArtistRepository extends JpaRepository<ArtistEntity>, JpaSpecificationExecutor<ArtistEntity> {
}
And you also need to implement your custom Specification<T>.
And then you can use findAll(Specification<T> spec, Pageable pageable).
You may just use this library which supports nested fields and much more: https://github.com/turkraft/spring-filter
It will let you run search queries such as:
/search?filter= average(ratings) > 4.5 and brand.name in ('audi', 'land rover') and (year > 2018 or km < 50000) and color : 'white' and accidents is empty

Spring Data/Hibernate Generates two queries instead of a JOIN

Context: I have two tables: Questionnaire and Question Section. A Questionnaire can have many Question Sections. Questionnaires and Question Sections both have Start and End Dates to determine if they are active records.
Here are my entities as written:
#Entity
#Data
public class Questionnaire {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
private String name;
private Date startDate;
private Date endDate;
private String description;
#OneToMany(cascade = CascadeType.All,
fetch = FetchType.LAZY,
mappedBy = "questionnaire")
#JsonManagedReference
private List<QuestionSection> questionSections = new ArrayList<QuestionSection>();
}
#Entity
#Data
public class QuestionSection {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
private String name;
private String description;
private int sectionLevel;
private Date startDate;
private Date endDate;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "QUESTIONNAIRE_ID", nullable = false)
#JsonBackReference
private Questionnaire questionnaire;
}
Here is my Spring Data Repository with a single declared method:
public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {
Questionnaire findByNameAndEndDateIsNull(String name);
// Previous goal query, but worked all the way back to the above simple query
// Questionnaire findByIdAndQuestionSectionsEndDateIsNull(UUID id);
}
The above derived query generates two queries shown below:
-- For brevity
select questionnaire.id as id
questionnaire.description as description
questionnaire.end_date as end_date
questionnaire.start_date as start_date
from questionnaire
where questionnaire.name='Foo' and (questionnaire.end_date is null)
select questionsection.questionnaire_id as questionnaire id
...rest of fields here...
from question_section
where questionsection.questionnaire_id = id from above query
Then Spring Data or Hibernate is combining those two above queries into one data object representative of the questionnaire object and returning that.
My problem with this is that I would have expected One query to run with a Join between the two tables, not two and then combine the results in memory. I'm pretty experienced with Spring Data and ORMs in general and have not been able to find any documentation as to why this is happening. Honestly I wouldn't care except that my original intention was to query at the parent entity and 'filter' out children that have end dates (not active). This derived query (commented out above) exhibited the same behavior which ultimately resulted in the data set that was returned containing the end dated question sections.
I know there's 100 other ways I could solve this problem (which is fine) so this is more of an educational interest for me at this point if anyone has any insight into this behavior. I could be missing something really simple.
You should be able to do this using the Entity Graph feature introduced in JPA 2.1.
https://www.baeldung.com/jpa-entity-graph
Spring Data offers support for Entity Graphs via the #NamedEntityGraph and #EntityGraph annotations:
https://www.baeldung.com/spring-data-jpa-named-entity-graphs
So in your code:
Entity:
#Entity
#NamedEntityGraph(name = "Questionnaire.questionSections",
attributeNodes = #NamedAttributeNode("questionSections ")
)
public class Questionnaire{
//...
}
Repository:
public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {
#NamedEntityGraph("Questionnaire.questionSections")
Questionnaire findByNameAndEndDateIsNull(String name);
}
public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {
#EntityGraph(attributePaths = { "questionSections" })
Questionnaire findByNameAndEndDateIsNull(String name);
}

Error in filtering by child object in Spring Data JPA Query

My code structure looks like the following.
Article:
#Entity
public class NewsArticle{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
[Other class properties such as title, publisher, publishedDate, etc.]
#OneToMany(mappedBy = "article")
private Set<UserReadNewsArticle> userReadNewsArticles = new HashSet<>();
[Getters and Setters]
}
Article read by User:
#Entity
public class UserReadNewsArticle {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private Long readAccountId;
private Long readArticleId;
#JsonIgnore
#ManyToOne
private Account account;
#JsonIgnore
#ManyToOne
private NewsArticle article;
[Getters and Setters]
}
Account:
#Entity
public class Account {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
[Other class properties]
#OneToMany(mappedBy = "account")
private Set<UserReadNewsArticle> userReadNewsArticles = new HashSet<>();
[Getters and Setters]
}
I want to have a query method in my NewsArticleRepository to get all the Read News Articles for a user.
public interface NewsArticleRepository extends PagingAndSortingRepository<NewsArticle, Long>{
Collection<NewsArticle> findByUserReadNewsArticlesReadAccountId(Long readAccountId);
}
This method works great. But how can I write a Spring Data JPA Query/Method to get the "Unread News Articles for a user". What I have tried is the following.
Collection<NewsArticle> findByUserReadNewsArticlesReadAccountIdNot(Long readAccountId);
This one does return a list of articles which have been read by other users. But my requirement is to get all the unread news articles. I have gone through Spring Data JPA Documentation but failed to come up with an easier soultion. How can I overcome this issue? Or am I doing something wrong?
You could achieve your result by using a JPQL query with also a subquery:
public interface NewsArticleRepository extends PagingAndSortingRepository<NewsArticle, Long> {
#Query("SELECT n FROM NewsArticle n WHERE n NOT IN "
+ "(SELECT ur.article FROM UserReadNewsArticle ur JOIN ur.account a WHERE a.id = :readAccountId)")
Collection<NewsArticle> findByUserReadNewsArticlesReadAccountIdNotIn(#Param("readAccountId") Long readAccountId);
}
http://localhost:8080/newsArticles/search/findByUserReadNewsArticlesReadAccountIdNotIn?readAccountId=1
So first get the read articels from the current user and then exlude them from the whole article list.
I don't think that spring data is able to get you the same, since a subquery is definitetly needed. If I'm wrong, somebody can correct me.

Best architectural choice to merge 2 Spring Data JPA repository classes

I am pretty new in Spring Data JPA and I have the following doubt about the best way to implement the following situation:
So basically I have the following 2 model classes:
Room (representing a room of an accomodation):
#Entity
#Table(name = "room")
public class Room implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "id_accomodation_fk", nullable = false)
private Accomodation accomodation;
#ManyToOne
#JoinColumn(name = "id_room_tipology_fk", nullable = false)
private RoomTipology roomTipology;
#Column(name = "room_number")
private String number;
#Column(name = "room_name")
private String name;
#Column(name = "room_description")
#Type(type="text")
private String description;
#Column(name = "max_people")
private Integer maxPeople;
#Column(name = "is_enabled")
private Boolean isEnabled;
public Room() {
}
// GETTER AND SETTER METHODS
}
And RoomTipology that represent a tipology of room (something like: single room, double bed room, etcetc):
#Entity
#Table(name = "room_tipology")
public class RoomTipology implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "tipology_name")
private String name;
#Column(name = "tipology_description")
private String description;
#Column(name = "time_stamp")
private Date timeStamp;
#OneToMany(mappedBy = "roomTipology")
private List<Room> rooms;
#OneToOne(mappedBy = "roomTipology")
private RoomRate roomRate;
// GETTER AND SETTER METHODS
}
Ok, using Spring Data JPA I will have 2 different repository classes (one for the Room entity class and another one for the RoomTipology entity class, something like this:
#Repository
#Transactional(propagation = Propagation.MANDATORY)
public interface RoomDAO extends JpaRepository<Room, Long> {
//#Query("FROM Room WHERE accomodation = :id")
List<Room> findByAccomodation(Accomodation accomodation);
}
#Repository
#Transactional(propagation = Propagation.MANDATORY)
public interface RoomTipologyDAO extends JpaRepository<RoomTipologyDAO , Long> {
// METHOD RELATED TO THE ACCESS TO ROOM TIPOLOGY ENTITIES
}
Ok, I have the following architectural doubt:
I have 2 little repositories classes that access to something that are semantically similar (the room concept and the room tipology concept are both related to the room).
Furthermore, as you can see in the code of the RoomTipology entity class there is the following field:
#OneToMany(mappedBy = "roomTipology")
private List<Room> rooms;
that is mapped by the #OneToMany annotation (because starting from a specific room tipology I want to access to all the room of this accomodation of this tipology: all the single bed room or all the double bed room and so on...).
So, following this architectural style, I will have the method that return the List associated to a room tipology into the RoomTipologyDAO repository class and not into the RoomTipology repository class..it works fine but it is semantically bad because I will have a method of RoomTipologyDAO that doesn't return something related to RoomTipology instance but a list of Room object.
Is it not nasty?
So what is the best way to create an architecture that uses Spring Data JPA in this case?
I can't not do something like:
public interface RoomDAO extends JpaRepository<Room, Long> extends JpaRepository<RoomTipology, Long> {
........................................................
........................................................
........................................................
}
because Java doesn't support multiple heredity, but I think that the best choice should obtain something like this.
Maybe can I create something like a RoomMetaDAO class that have the RoomDAO and the RoomTipologyDAO as field? Can it work?
What do you think could be the best architectural choice for my situation?
You are absolutely correct in being sceptical about this.
The mistake is to assume that you should have one repository per entity. Instead you should look into the concept of aggregate roots from domain driven design.
An aggregate root is an entity that is used to manipulate a bunch of entities that can only accessed and modified through the aggregate root.
You want one repository per such aggregate root, which would be in your case the Room.
This is explained in much more detail in this article by Oliver Gierke, lead of the Spring Data project.

Categories