How do I define a custom JOIN condition in a query method of a JpaRepository - spring

We have an ArticleEntity that has several Prices, and the Prices are only valid in a certain time range, and there is only one valid price at a time.
In our entities this is modeled:
#Entity
public class ArticleEntity {
...
#OneToMany(mappedBy = "article", fetch = FetchType.LAZY)
private Collection<PriceEntity> prices;
}
Now, in our ArticleRepository I am trying to add custom query so that only valid prices are retrieved:
public interface ArtikelRepository extends JpaRepository<ArtikelEntity, Integer> {
#Query(value = "select a, p from ArtikelEntity a left join fetch PriceEntity p on p.article = a " +
"and (p.start is null or p.start <= :date) " +
"and (p.end is null or p.end >= :date) ")
Collection<ArtikelEntity> findAllWithPriceOnDate(#Param("date") LocalDate date);
}
Unfortunately this does not work, as Hibernate/JPA is requesting all prices afterwards again, instead of populating the price list with just the result of this join query.
Can I tell Hibernate/JPA to use the data from the custom query? And how?

It's not really possible unless you use Hibernate filters. I would recommend you to look into a DTO solution though, because even with filters, you could run into a situation where you change the collection and all of a sudden Hibernate flushes the state i.e. removes elements which are filtered out.
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(ArtikelEntity.class)
public interface ArtikelDto {
#IdMapping
Long getId();
String getName();
#Mapping("prices[(start is null or start <= :date) " +
"and (end is null or end >= :date)]")
Set<PriceDto> getValidPrices();
#EntityView(PriceEntity.class)
interface PriceDto {
#IdMapping
Long getId();
BigDecimal getPrice();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ArtikelDto a = entityViewManager.find(entityManager, ArtikelDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<ArtikelDto> findAll(#OptionalParam("date") LocalDate date, Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

Related

Can I select specific fields from entity with EntityGraph?

I have an entity, which has multiple(lets say more than 5) fields in it. I want to list only 2 of the fields in entity. I managed to do it with Entity Manager and JPA Query. In the code below, I added how I did it with entity manager, but it may not be optimal solution. What I want to ask is, can I do that with using the EntityGraph?
List<Object[]> test = entityManager.createQuery("SELECT c.a, c.b FROM TestClass c WHERE c.id = :id", Object[].class)
.setParameter("id", id)
.getResultList();
TestClassResult testClassResult = new TestClassResult();
for (Object[] row : test) {
testClassResult.setA((BigDecimal) row[0]);
testClassResult.setB((BigDecimal) row[1]);
}
As far as I know, an implementation is allowed to fetch only what you specify when registering an entity graph as fetch graph (different from a load graph), but at least Hibernate does not support this.
Anyway, DTO projections are usually the way to in such a case anyway and I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(TestClass.class)
public interface TestClassResult {
#IdMapping
Integer getId();
BigDecimal getA();
BigDecimal getB();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
TestClassResult a = entityViewManager.find(entityManager, TestClassResult.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<TestClassResult> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
as far as I know, entity graphs define which attributes to fetch from the database so you can fetch the 2 attributes eagerly and the other 3 lazily, "Thorben Janssen" has a good article on his website about graphs, another way to exclusively fetch selected attributes is to use DTO Projections, he also does have a good article touching the subject.

How to get a List in one of the fields of DTO JPA Repository

I have a DTO and one of its field is a list. So, there is a main table and another table with one to many relations.
Code:
public class DTO {
id;
List<String> name;
}
public interface Repository extends JpaRepository<Table1, Long> {
#Query("SELECT new abc.com.DTO (" +
" r.id," +
" name"+
" )" +
" FROM Table1 r" +
" join Table2 c on c.xyz.id = r.abc.id"+
" WHERE r.type = :type"
)
List<DTO> find(#Param("type") final String type);
}
say I have in table T1 a row against which we have 3 rows in table 2.
I want dto to get me object obj1 with a list of all 3 names for table2
Currently it returns me 3 separate dtos with 3 rows of table2
As it is stated in the JPA specification (see section 4.8 SELECT Clause):
The SELECT clause can contain one or more of the following elements: an identification variable that ranges over an abstract schema type, a single-valued path expression, a scalar expression, an aggregate expression, a constructor expression.
Note that the SELECT clause must be specified to return only single-valued expressions. So, the query like below is therefore not valid:
SELECT o.lineItems FROM Order AS o
This is not possible with JPA directly as constructor expressions always use flat results. Spring Data Projections should support this, but in an inefficient way i.e. it selects entities and just provides a "view" of these entities through an interface.
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Table1.class)
public interface DTO {
#IdMapping
Long getId();
#Mapping("Table2[xyz.id = VIEW(abc.id)].name")
Set<String> getNames();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
DTO a = entityViewManager.find(entityManager, DTO.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
public interface Repository extends JpaRepository<Table1, Long> {
List<DTO> findByType(#Param("type") final String type);
}

Dynamic JPA query

I have two entities Questions and UserAnswers. I need to make an api in spring boot which returns all the columns from both the entities based on some conditions.
Conditions are:
I will be give a comparator eg: >, <, =, >=, <=
A column name eg: last_answered_at, last_seen_at
A value of the above column eg: 28-09-2020 06:00:18
I will need to return an inner join of the two entities and filter based on the above conditions.
Sample sql query based on above conditions will be like:
SELECT q,ua from questions q INNER JOIN
user_answers ua on q.id = ua.question_id
WHERE ua.last_answered_at > 28-09-2020 06:00:18
The problem I am facing is that the column name and the comparator for the query needs to be dynamic.
Is there an efficient way to do this using spring boot and JPA as I do not want to make jpa query methods for all possible combinations of columns and operators as it can be a very large number and there will be extensive use of if else?
I have developed a library called spring-dynamic-jpa to make it easier to implement dynamic queries with JPA.
You can use it to write the query templates. The query template will be built into different query strings before execution depending on your parameters when you invoke the method.
This sounds like a clear custom implementation of a repository method. Firstly, I will make some assumptions about the implementation of your entities. Afterwards, I will present an idea on how to solve your challenge.
I assume that the entities look basically like this (getters, setters, equals, hachCode... ignored).
#Entity
#Table(name = "questions")
public class Question {
#Id
#GeneratedValue
private Long id;
private LocalDateTime lastAnsweredAt;
private LocalDateTime lastSeenAt;
// other attributes you mentioned...
#OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserAnswer> userAnswers = new ArrayList();
// Add and remove methods added to keep bidirectional relationship synchronised
public void addUserAnswer(UserAnswer userAnswer) {
userAnswers.add(userAnswer);
userAnswer.setQuestion(this);
}
public void removeUserAnswer(UserAnswer userAnswer) {
userAnswers.remove(userAnswer);
userAnswer.setQuestion(null);
}
}
#Entity
#Table(name = "user_answers")
public class UserAnswer {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "task_release_id")
private Question question;
}
I will write the code with the knowledge about the JPA of Hibernate. For other JPAs, it might work similarly or the same.
Hibernate often needs the name of attributes as a String. To circumvent the issue of undetected mistakes (especially when refactoring), I suggest the module hibernate-jpamodelgen (see the class names suffixed with an underscore). You can also use it to pass the names of the attributes as arguments to your repository method.
Repository methods try to communicate with the database. In JPA, there are different ways of implementing database requests: JPQL as a query language and the Criteria API (easier to refactor, less error prone). As I am a fan of the Criteria API, I will use the Criteria API together with the modelgen to tell the ORM Hibernate to talk to the database to retrieve the relevant objects.
public class QuestionRepositoryCustomImpl implements QuestionRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Question> dynamicFind(String comparator, String attribute, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Question> cq = cb.createQuery(Question.class);
// Root gets constructed for first, main class in the request (see return of method)
Root<Question> root = cq.from(Question.class);
// Join happens based on respective attribute within root
root.join(Question_.USER_ANSWER);
// The following ifs are not the nicest solution.
// The ifs check what comparator String contains and adds respective where clause to query
// This .where() is like WHERE in SQL
if("==".equals(comparator)) {
cq.where(cb.equal(root.get(attribute), value));
}
if(">".equals(comparator)) {
cq.where(cb.gt(root.get(attribute), value));
}
if(">=".equals(comparator)) {
cq.where(cb.ge(root.get(attribute), value));
}
if("<".equals(comparator)) {
cq.where(cb.lt(root.get(attribute), value));
}
if("<=".equals(comparator)) {
cq.where(cb.le(root.get(attribute), value));
}
// Finally, query gets created and result collected and returned as List
// Hint for READ_ONLY is added as lists are often just for read and performance is better.
return entityManager.createQuery(cq).setHint(QueryHints.READ_ONLY, true).getResultList();
}
}

How to avoid unwanted queries hibernate query data when import data from entity to DTO

I have some entities below
#Entity
#Table("processitem")
public class Processitem {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="task")
public Task task;
#ManyToOne
#JoinColumn(name="user")
public User user;
//... and some more relationship to other
}
#Entity
#Table(name="task")
public class Task {
...
#OneToMany(mappedBy="task",cascade = CascadeType.ALL,orphanRemoval = true)
private Set<Processitem> processitem;
...
}
Now I import a List to List (I use a loop for to import data from entity to DTO), (around more 200 records) the hibernate execute a lots queries and performance is not good. Is there any solution to avoid that ? I tried using Entity Graph but it still doesn't improve (some time 2 queries is better 1 query with left join)
This is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Task.class)
interface TaskDto {
#IdMapping
Long getId();
String getName();
#Mapping(fetch = MULTISET)
Set<ProcessitemDto> getProcessitem();
}
#EntityView(Processitem.class)
public interface ProcessitemDto {
#IdMapping
Long getId();
#Mapping("user.name")
String getUserName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
TaskDto task = entityViewManager.find(entityManager, TaskDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
You can make use of the various fetching strategies provided by Blaze-Persistence Entity-Views but the best one is usually the MULTISET fetch strategy. Here you can read more about it: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#anchor-fetch-strategies

Spring data jpa query to return with multiple properties

I have to fetch Users by id and state.The method takes a set of usersIds as input.
I need a jpa query like below:
public Set<Users> fetchUsersByIdsContainingStateId(Set<Integer> userIds, Integer stateId)
Is there a query like the above in Spring Data JPA or I need to resort to named query
you can use spring data query:
public interface UserRepository extends CrudRepository<User, Long> {
Set<User> findUserByIdInAndStageId(Set<Long> userIds , Long stageId);
#Query("select u from User u where u.id in (:userIds) and u.stage.id=:stageId")
Set<User> findUserByUserIdsAndStageId(#Param("userIds") Set<Long> userIds , #Param("stageId") Long stageId);
}
for method findUserByIdInAndStageId spring data generates something
like
-- whereuser0_.id in (? , ? , ?...)) and stage1_.id=?
method findUserByUserIdsAndStageId do the same as findUserByIdInAndStageId , but in Query annotation you should write your query.
second search method findUserByUserIdsAndStageId named just for example , but I'll change it into something more readable like findUserWithStage(......)
PC don't use for domain model plural name like Users , use singular User

Resources