Is there a way in JPA to convert random select clause values using createNativeQuery to a domain object. The domain object is not managed
I have the following sql query
select name, count(*) as cnt, sum(average_events)/count(*) as avg_events from (complex subquery)
I want to convert the values a,b,c into a domain object with three instance variables a, b and c. This domain object is not managed by JPA and hence does not have #Entity and no corresponding table.
Currently I am doing the following, which returns a list of objects.
Query query = objectManager.getEntityManager().createNativeQuery(sqlStr);
List resultList = query.getResultList();
Use this syntax:
SELECT new foo.MyCustomObject(a, b, c) FROM ...
Where MyCustomObject is any class with matching consdtructor:
public class MyCustomObject {
private final String name;
private final int cnt;
private final float avg;
public MyCustomObject(String name, int cnt, float avg) {
this.name = name;
this.cnt = cnt;
this.avg = avg;
}
//...getters
}
Scala bonus: equivalent class:
class MyCustomObject(name: String, cnt: Int, avg: Float)
//no, actually that's it
Just pass in the "resultClass" to the createNativeQuery call. Certainly works in DataNucleus JPA.
Related
The title maybe is not properly written but here is what, more or less, I want to achieve.
I would like to be able to write dynamic queries with use of Query by Example that would join multiple tables and create (projection?) DTO for me.
This DTO would have fields that are mapped to different columns in joined tables. Consider following:
Tables:
CREATE TABLE address
(
id SERIAL,
address_code VARCHAR(255) NOT NULL,
street_name VARCHAR(255),
building_number VARCHAR(255)
);
CREATE TABLE account
(
id SERIAL,
account_number BIGINT UNIQUE
);
CREATE TABLE customer
(
id SERIAL,
name VARCHAR(255)
)
I would like to be able to create a query which result would be:
address.address_code, account.account_number, customer.name
so basically the result would be a custom DTO. I also mentioned that I would like to have this backed up with Query by Example because I will to dynamically append WHERE clauses so I thought that if I created a DTO like:
public record CustomQueryResultDTO(String addressCode, BigInteger accountNumber, String name) {}
I could simply query just like it is in Spring R2DBC documentation.
The problem here is that I am not sure what should be a viable solution for such problem because on one hand I would like to reuse ReactiveQueryByExampleExecutor but that would mean that I have to create something like:
#Repository
public interface CustomQueryResultRepository extends ReactiveCrudRepository<CustomQueryResultDTO, Integer>, ReactiveQueryByExampleExecutor<CustomQueryResultDTO> {
}
Which kind of seems to me not a way to go as I do not have a corresponding table for CustomQueryResultDTO therefore there is really no mapping for this repository interface - or am I overthinking this and it is actually a way to go?
I think you are potentially overthinking it.
You can do it in a number of ways (note Java 17 text blocks):
Via R2DBC JPA-like #Query
Create a normal ReactiveCrudRepository but collect into a projection (DTOP)
// Repository
#Repository
public interface UserRefreshTokenRepository extends ReactiveCrudRepository<UserRefreshToken, Integer> {
#Query(
"""
select *
from user.user_refresh_tokens t
join user.user_infos c on c.user_id = t.user_id
where c.username = :username
"""
)
Flux<UserRefreshTokenDtop> findAllByUsername(String username);
}
// Entity
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#ToString(exclude = {"refreshToken"})
#Table(schema = "user", name = "user_refresh_tokens")
public class UserRefreshToken {
#Id private Integer id;
private String userId;
private String username; # will be joined
private String ipAddr;
private OffsetDateTime createdAt;
private String refreshToken;
private OffsetDateTime refreshTokenIat;
private OffsetDateTime refreshTokenExp;
}
// DTO projection
public interface UserRefreshTokenDtop {
Integer getId();
String getUserId();
String getUsername(); # will be joined
String getIpAddr();
OffsetDateTime getRefreshTokenIat();
OffsetDateTime getRefreshTokenExp();
}
Via DatabaseClient
This one also uses TransactionalOperator to ensure query atomicity
private final DatabaseClient client;
private final TransactionalOperator operator;
#Override
public void deleteAllUsedExpiredAttempts(Duration resetInterval) {
// language=PostgreSQL
String allUsedExpiredAttempts = """
select t.id failed_id, c.id disable_id, t.username
from user.failed_sign_attempts t
join user.disable_sign_attempts c on c.username = t.username
where c.is_used = true
and :now >= c.expires_at + interval '%d seconds'
""";
// POTENTIAL SQL injection - half-arsed but %d ensures that only Number is allowed
client
.sql(String.format(allUsedExpiredAttempts, resetInterval.getSeconds()))
.bind("now", Instant.now())
.fetch()
.all()
.flatMap(this::deleteFailed)
.flatMap(this::deleteDisabled)
.as(operator::transactional)
.subscribe(v1 -> log.debug("Successfully reset {} user(s)", v1));
}
Via R2dbcEntityTemplate
I don't have a working example but it is pain in the ass to join via the .join() operator
If you are interested check the docs for R2dbcEntityTemplate
13.4.3. Fluent API > Methods for the Criteria Class
https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html
I have an array field in my first entity Class as below:
class Entity1{
private Integer col1;
private String col2;
private Integer[] col3Arr;
}
I have another entity class as below:
class Entity2{
private Integer col1;
private String col2;
private Integer col3;
}
I am fetching records by joining multiple other entities along with which I have to join Entity1 if col3Arr contains a value col3 from Entity 2
With PSQL, I could easily achieve this by following statement
//Other part of query
join Entity2 e2 on (//conditions from other joined tables//)
join Entity1 e1 on e2.col3=ANY(e1.col3Arr)
What is the HQL equivalent of ANY?
Or is there any other way in HQL to check if an array contains a specific value?
For mapping the arrays you will need a custom type. You can use the hibernate-types project for this: https://vladmihalcea.com/how-to-map-java-and-sql-arrays-with-jpa-and-hibernate/
Did you try to use e2.col3 = FUNCTION('ANY', e1.col3Arr) yet? If that doesn't work, I would suggest you create a custom SQLFunction that renders the SQL you desire e.g.
public class ArrayAny implements SQLFunction {
#Override
public boolean hasArguments() {
return true;
}
#Override
public boolean hasParenthesesIfNoArguments() {
return true;
}
#Override
public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException {
return firstArgumentType;
}
#Override
public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory) throws QueryException {
return "any(" + args.get(0) + ")";
}
}
You will have to register the function within the Dialect.
According to the hibernate documentation:
When discussing arrays, it is important to understand the distinction between SQL array types and Java arrays that are mapped as part of the application’s domain model.
Not all databases implement the SQL-99 ARRAY type and, for this reason, Hibernate doesn’t support native database array types.
So, there is no equivalent of PSQL ANY in HQL.
Right now I have an entity object and a DTO. The repository returns a list of objects arrays when I do a simple example like: findById(). Is there a way to easily map the return type to be a custom DTO object rather than always return entity objects?
Example is below:
#Query("Select f.id, f.name from Food f where f.id = :id")
public List<Object[]> findById(#Param("id") String id);
My DTO object looks like:
FoodDto{
private String id;
private String name;
}
Right now I've only ever been able to get repositories to return a List< Object[] > type.
Try this.
#Query("Select new package.FoodDto(f.id, f.name) from Food f where f.id = :id")
public List<FoodDto> findById(#Param("id") String id);
Assuming class FoodDto is in package, if not you need to set the full package.
Also I assume the FoodDto have a constructor that match
public FoodDto(int id, String name){
//Variable assignation
}
I never tried in spring-jpa but that works in JPQL so I assume it will work XD
Consider a stored procedure GetEmployees which has a SELECT statement like
SELECT EMP_ID, EMP_NAME, EMP_EMAIL
FROM EMPLOYEE
This stored procedure will have its results mapped to a complex type GetEmployees_Result
class GetEmployees_Result {
public int EMP_ID;
public string EMP_NAME;
public string EMP_EMAIL;
}
Is it possible to map the result of the function import to a different complex type like the one below:
class GetEmployeesResult {
public int Id;
public string Name;
public string Email;
}
It is a standard feature. You have go to the mapping of the function import and change the result type to the custom type.
I'm getting started with JPA and I want to know the best way to achieve something like this:
I need to implement a service that returns a list of scripts, and each script has a list of parameters. I simplified the query, but it is something like this:
(SELECT
p.DESC
FROM
INPUT_PARAMETERS p
INNER JOIN SCRIPT_PARAMS sp ON p.PARAM_ID = sc.PARAM_I
INNER JOIN SCRIPT s ON s.SCRIPT_ID = sc.SCRIPT_ID
WHERE
s.NAME = 'name')
UNION
(SELECT
p.DESC
FROM
OUPUT_PARAMETERS p
INNER JOIN SCRIPT_PARAMS sp ON p.PARAM_ID = sc.PARAM_I
INNER JOIN SCRIPT s ON s.SCRIPT_ID = sc.SCRIPT_ID
WHERE
s.NAME = 'name')
And I want to return a list of POJO objects that are something like:
public class Script {
private String name;
private List<String> params;
public Script(){}
public String getName()
{
return name;
}
public void setName(String pName)
{
name = pName;
}
public List<String> getParams()
{
return params;
}
public void setParams(List<String> pParams)
{
params = pParams;
}
}
I want to know what is the best way to load the POJO object from a query. Is it best to build a JPQL query, or can I use a Native Named Query, and do I need to get a object[] and construct my POJOs manually, or can I use JPA to load the objects from the query?
You can only construct instances of Script manually. When querying with JPQL, reason is that constructor expression cannot take List as argument. Additionally JPQL does not have unions.
Also with SQL query you cannot construct Script directly, because result can be only scalars or entity.