Lets say I have a class like this:
#Entity
public class Employee{
private Long Id;
private String jobTitle;
private String firstName;
... getters and setters
}
Is it possible to do single query and return multiple sets of data? Say I have a method signature in my repository that looks like this:
public EmployeeQueryResult getEmployeeQuery(Long currentUserId, String jobTitle, List<String> names);
and I want to use this method to get the current employee by id, all employees that have a specific job title, and all employees that have a name:
public class EmployeeQueryResults{
private Employee currentEmployee;
private List<Employee> employeesWithJobTitle;
private List<Employee> employeesWithName;
...
}
I'm asking if it is possible to use queryDSL to basically make 3 separate queries and union them together so that I can use paging on the unioned list.
I'm asking if it is possible to use queryDSL to basically make 3 separate queries and union them together so that I can use paging on the unioned list.
No, that's not possible with Querydsl. You can write the query in such a way that it will return Employee instances that match at least one of the constraints, but the result will be a single Employee list.
Related
I have a SpringBoot 2.6.11 application with JPA 2.2.
I have an entity like this:
#Data
#Entity
#Table(name = "entity")
public class Entity implements Serializable {
....
#Convert(converter = ListConverter.class)
private List<String> referenceCode;
....
}
I have this Converter:
#Converter(autoApply = true)
public class ListConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> attribute) {
return String.join(";", attribute);
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
return new ArrayList<>(Arrays.asList(dbData.split(";")));
}
}
And when I insert or extract this element all working fine. But now I wanna query that element and I don't know how to do it. If I do something like that:
public List<Entity> findByReferenceCode(String reference);
It doesn't work, if I do:
#Query("select e from Entity e where e.referenceCode IN ?1")
public List<Entity> findByReferenceCode(List<String> reference);
Still doesn't work..
The only way I found is by the nativeQuery but is really an extrema ratio. Ho can I solve this?
Thank you
To really do what you want here, you need to use an #ElementCollection. The reason being that there is no reliable way for JPA to query a single column and treat it as a collection. Reliably querying a collection requires a second table (which is what #ElementCollection does). You can continue to use the #Converter, but your queries will have to be customized to handle the disparity between the entity attribute type (list) and the actual database column type (string).
If you are okay with the limitations of the #Converter then it's fine (I have used them this way) but if you truly need to query the attribute like a collection (e.g. search for multiple independent items, perform counts, aggregations, etc) and you want those queries to be generated by a JPA layer, then you will have to use #ElementCollection and let it create a second table.
It is a legacy database with two tables, Order and OrderLine. From business view, one order can have multiple OrderLines. Both of the tables has one column with name 'OrderNumber', there is not any referrence between the two table and it is not possible to add the foreign key reference now. In SQL, if we want a order with related orderlines, we can use join based on OrderNumber column.
Now we are using EntityFramework Core. Is there any way to config the one-to-many relationship and make the Order's OrderLines list filled automatically? Thanks.
public class Order {
public int Id;
public string OrderNumber;
public List<OrderLine> OrderLines;
...
}
public class OrderLine {
public int Id;
public string OrderNumber;
...
}
I have a transaction class which stores the each transaction of a customer,Following are the fields in this class.
class Transaction{
#Id
private String id;
private Date date;
private String customerId;
private double openBalance;
private double transctionAmount;
private double finalAmount;
}
I need to fetch only the last inserted record of a customer (let say for customerId = cust123).
I defined following function in repository.
public interface TranscationRepository extends MongoRepository<Transaction, String> {
Optional<Transaction> findTopByCustomerIdOrderByIdDesc(String id);
}
This method giving last entry not by customerId but overall. I tried few modifications to it but did not get success.
I know I can findAllByCustomer but I don't want to pull huge list of transaction which is of no use in this use case. What is correct signature in spring mongo to get last inserted record by a field? I am ok to use custom #Query also.
Thank you.
there is already a similar post. Since this is already older, I hope something has changed since then (How does the FetchMode work in Spring Data JPA)
I would like to run all jpa repository#findById in one select, if the relationship is marked with EAGER. However, spring data ignores the EAGER specification and the FETCH.JOIN annotation from hibernate.
Is there a generic solution that all findById queries are executed in one select?
I wouldn't want to write a separate JPL or EntityGraph for each query. Does anyone know a generic solution?
JpaReposistory
The easiest option would be to write a JpaRepository<T, Id>. This is still a custom repository. However, you do not have to write so much code. You mainly have to write a repository interface for each relevant class and annotate the findById(Long id) method with a graph. The advantage is that if you edit your entity, the repository method will not need any changes because you define the entity graph within the entity class itself.
#Entity
#NamedEntityGraph(name = "Department.detail",
attributeNodes = #NamedAttributeNode("employees"))
public class Department {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(fetch = FetchType.LAZY)
private List<Employee> employees;
// ...
}
public interface DepartmentRepository extends JpaRepository<Department, Long> {
#EntityGraph(value = "Department.detail", type = EntityGraphType.LOAD)
List<Department> findById(Long id);
}
As Spring data ignores the #Fetch(Fetchmode.JOIN) annotation or the information fetch = FetchType.EAGER, you cannot influence the join how you want it to be within the entity itself.
JPQL Query Where You Need It
Another option can be considered as a bad software engineering style: You can call the database queries directly where you need them. This means that you execute the code which you would usually write in the repository.
public ClassWithQueryResults {
#PersistenceContext
private EntityManager entityManager;
public void methodWhereYouNeedYourResults() {
TypedQuery<Department> query = entityManager.createQuery(
"SELECT DISTINCT d FROM Department d LEFT JOIN d.employees e",
Department.class);
List<Department> departments = query.getResultList();
// ...
}
}
Repository With JPQL, Generics and Reflection
Taking the previously suggested idea, you can create a custom repository which is valid for all your entities. The first step would be to create an attribute in your entity class in which you store the attribute which should be fetched.
public class Department extends AbstractEntity {
public static void String ATTRIBUTE_TO_FETCH = "employees";
...
}
With some tweaking, this can be extended to an array/list of all the fields which should be fetched. As this attribute is directly in your entity classes, the chance for any mistakes and future effort is low. Obviously, this attribute should have the same name in all your entities.
The next step would be to create the repository. I provide an example with the findAll() method. You have to pass it only the class name of the entities you want to have and the generics and reflection do the rest. (Consider what you want to do with the exceptions.)
public <T> List<T> findAll(Class<T> tClass)
throws NoSuchFieldException, IllegalAccessException {
String className = tClass.getSimpleName();
String attributeToFetch = (String)
tClass.getDeclaredField("ATTRIBUTE_TO_FETCH").get(null);
String queryString = String.format("SELECT DISTINCT p FROM %s p LEFT JOIN p.%s c",
className, attributeToFetch);
TypedQuery<T> query = entityManager.createQuery(queryString, tClass);
return query.getResultList();
}
Depending on how you want to implement this, the modification/generation of a query through simple manipulation of a String can offer the possibility of SQL injection attacks.
I need help to get the data from another document I have the following class.
#Data
#Document(collection = "tmVersion")
public class TmVersion {
#Id
private String id;
private String cVrVersionId;
#DBRef
private TaApplicationVersion taApplicationVersion;
}
and
#Data
#Document(collection = "taApplicationVersion")
public class TaApplicationVersion {
#Id
private String id;
private String dVrAppName;
private String dVrAppCode;
}
This is my repository in which I map what I want to be shown but in taApplicationVersion I need to show all this object also how is it done?
#Query(value="{}", fields="{'cVrVersionId': 1, 'taApplicationVersion.dVrAppName': 2,
'dVrVersionNumber': 3}")
Page<TmVersion> getAllVersionWithOutFile(Pageable pageable)
Couple of things to mention here.
If you want this kind of join between tables, then you need to rethink your choice of Mongodb as database. No Sql Databases thrive on the fact that there is very less coupling between tables(collections). So if you are using #DBRef, it negates that. Mongodb themselves do not recommend using #DBRef.
This cannot be achieved with the method like you have in the repository. You need to use Projections. Here is the documentation for that.
Create a Porjection interface like this. Here you can control which fields you need to include in the Main class(TmVersion)
#ProjectedPayload
public interface TmVersionProjection {
#Value("#{#taApplicationVersionRepository.findById(target.taApplicationVersion.id)}")
public TaApplicationVersion getTaApplicationVersion();
public String getId();
public String getcVrVersionId();
}
Change the TmVersionRepository like this
public interface TmVersionRepository extends MongoRepository<TmVersion, String> {
#Query(value="{}")
Page<TmVersionProjection> getAllVersionWithOutFile(Pageable pageable);
}
Create a new Repository for TaApplicationVersion. You can add #Query on top of this method and control which fields from subclass needs to be returned.
public interface TaApplicationVersionRepository extends MongoRepository<TaApplicationVersion, String> {
TaApplicationVersion findById(String id);
}