How can I cast to UUID in querydsl - java

I am trying to write this in querydsl
with params (id) as ((select
cast('03d47b00-9389-456a-bf1b-acfa1c37c4ed' as uuid) as courseid)
union all
(select
cast('03d47b00-9389-456a-bf1b-acfa1c37c4ed' as uuid) as courseid)
union all
(select
cast('03d47b00-9389-456a-bf1b-acfa1c37c4ed' as uuid) as courseid)
)
select * from student join params on params.id = student.course_id
I am having trouble with casting to uuid
private void test() {
PathBuilder<String> builder = new PathBuilder<>(String.class, "courseid");
QCourseEntity courseIdCte = new QCourseEntity("sub");
final JPASQLQuery<StudentEntity> query = getJPASQLQuery()
.with(courseIdCte, union(builder, courseIds.stream().map(id -> id.toString()).collect(Collectors.toList())))
}
private static <String> Union<String> union(PathBuilder<String> pathBuilder, List<String> ids) {
return SQLExpressions.unionAll(
ids.stream()
.map(id -> SQLExpressions.select(Expressions.constantAs(id, pathBuilder)))
.collect(Collectors.toList()));
}
Above query is generating
with sub as ((select
? as courseid)
union
all (select
? as courseid)
union
all (select
? as courseid)
I want it to cast to uuid. Also I don't want to use in clause as it can
return results less than number of ids provided if there are duplicate ids
Also, I am using union call because querydsl does not support
below syntax
with params (tag) as (
values ('03d47b00-9389-456a-bf1b-acfa1c37c4ed'::uuid), ('03d47b00-9389-456a-bf1b-acfa1c37c4ed'::uuid),
('03d47b00-9389-456a-bf1b-acfa1c37c4ed'::uuid)
)

Related

What should LIMIT have for attributes in Spring JPA? SQL

I'm trying to have limits with nativeQuery = true but Spring JPA can't find the query for this.
My error is:
Caused by: java.lang.IllegalStateException: Using named parameters for method public abstract void se.danielmartensson.repositories.DataRepository.deleteByJobNameOrderByDateTimeLimit(java.lang.String,long,long) but parameter 'Optional[jobName]' not found in annotated query 'DELETE FROM Data data ORDER BY data.dateTime WHERE data.jobName =: jobName LIMIT firstIndex = :firstIndex, selectedSamples = :selectedSamples'!
So I'm guessing that LIMIT should have different attributes, rather than firstIndex and selectedSamples ? What can it be then?
Where is my query from my Repository in Spring Boot
#Query(value = "SELECT * FROM Data data ORDER BY data.dateTime WHERE data.jobName = :jobName LIMIT firstIndex = :firstIndex, selectedSamples = :selectedSamples", nativeQuery = true)
List<Data> findByJobNameOrderByDateTimeLimit(#Param("jobName") String jobName, #Param("firstIndex") long firstIndex, #Param("selectedSamples") long selectedSamples);
#Modifying
#Query(value = "DELETE FROM Data data ORDER BY data.dateTime WHERE data.jobName =: jobName LIMIT firstIndex = :firstIndex, selectedSamples = :selectedSamples", nativeQuery = true)
void deleteByJobNameOrderByDateTimeLimit(#Param("jobName") String jobName, #Param("firstIndex") long firstIndex, #Param("selectedSamples") long selectedSamples);
I have created a sample project that uses H2. You must change limit and offset part in which database you are using. For the delete operation, there is delete from table where id in (select id from table where .. order by .. limit .. offset ..) is defined.
Note: Lombok is used for getter, setter and toString. It is not required.
DataRepository.java
public interface DataRepository extends JpaRepository<Data, Integer> {
#Query(value = "SELECT * FROM Data data WHERE data.jobName = :jobName ORDER BY data.dateTime limit :selectedSamples offset :firstIndex"
, nativeQuery = true)
List<Data>findByJobNameOrderByDateTimeLimit(#Param("jobName") String jobName, #Param("firstIndex") Integer firstIndex, #Param("selectedSamples") Integer selectedSamples);
#Transactional
#Modifying
#Query(value = "DELETE FROM Data data WHERE data.id in (select id from Data d where d.jobName = :jobName order by d.dateTime limit :selectedSamples offset :firstIndex)"
, nativeQuery = true)
void deleteByJobNameOrderByDateTime(#Param("jobName") String jobName, #Param("firstIndex") Integer firstIndex, #Param("selectedSamples") Integer selectedSamples);
}
Data.java
#lombok.Data
#Entity
#Table
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "jobName")
private String jobName;
#Column(name = "dateTime")
private LocalDateTime dateTime;
public Data() {
}
public Data(String jobName, LocalDateTime dateTime) {
this.jobName = jobName;
this.dateTime = dateTime;
}
}
Test
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
#SpringBootTest
class DemoApplicationTests {
#Autowired
private DataRepository dataRepository;
#Test
void test1() {
// insert dummy records
dataRepository.save(new Data("job1", LocalDateTime.now().minusMinutes(1)));
dataRepository.save(new Data("job1", LocalDateTime.now().minusMinutes(2)));
dataRepository.save(new Data("job1", LocalDateTime.now().minusMinutes(3)));
dataRepository.save(new Data("job1", LocalDateTime.now().minusMinutes(4)));
// get records
List<Data> dataList = dataRepository.findByJobNameOrderByDateTimeLimit("job1", 0, 4);
for (Data data : dataList) {
System.out.println(data);
}
// delete
dataRepository.deleteByJobNameOrderByDateTime("job1", 1, 2);
// get records
dataList = dataRepository.findByJobNameOrderByDateTimeLimit("job1", 0, 4);
for (Data data : dataList) {
System.out.println(data);
}
}
}
Output
Hibernate: SELECT * FROM Data data WHERE data.jobName = ? ORDER BY data.dateTime limit ? offset ?
Data(id=4, jobName=job1, dateTime=2021-01-08T05:25:31.830)
Data(id=3, jobName=job1, dateTime=2021-01-08T05:26:31.829)
Data(id=2, jobName=job1, dateTime=2021-01-08T05:27:31.827)
Data(id=1, jobName=job1, dateTime=2021-01-08T05:28:31.756)
Hibernate: DELETE FROM Data data WHERE data.id in (select id from Data d where d.jobName = ? order by d.dateTime limit ? offset ?)
Hibernate: SELECT * FROM Data data WHERE data.jobName = ? ORDER BY data.dateTime limit ? offset ?
Data(id=4, jobName=job1, dateTime=2021-01-08T05:25:31.830)
Data(id=1, jobName=job1, dateTime=2021-01-08T05:28:31.756)
Mysql
For mysql delete operation is successful with below script.
Reference: MySQL DELETE FROM with subquery as condition - Answer
#Transactional
#Modifying
#Query(value = "DELETE FROM Data WHERE id in (select id from (select id from Data where jobName = :jobName order by dateTime limit :selectedSamples offset :firstIndex) x)"
, nativeQuery = true)
void deleteByJobNameOrderByDateTime(#Param("jobName") String jobName, #Param("firstIndex") Integer firstIndex, #Param("selectedSamples") Integer selectedSamples);
If you just want to get the row which jobName and firstIndex and selectedSamples match the parameters. you should use correct SQL syntax and put them in where.
SELECT * FROM Data data
WHERE data.jobName = :jobName AND firstIndex = :firstIndex AND selectedSamples = :selectedSamples
ORDER BY data.dateTime
the correct syntax is like this :
select *
from [table]
where 'condiction'
order by [column]
limit [int]
LIMIT however is used to set the max tuples SQL return, for example if the query return 10K rows but you only want to look at first 5 rows, you can use limit 5 to tell SQL only return first 5 fetch.
Also in different DBMS there might need to use different syntax or method to achieve the same thing, like in ORACLE we got no limit clause, instead we got FETCH or simply using ROWNUM.

Jpa criteria api - create class that connect other class

Let's say I have the following SQL:
select * from table1
inner join table2 on table1.id = table2.table1id;
inner join table3 on table2.id = table3.table2id;
inner join table4 on table3.id = table4.table3id;
and I have Java entities like table1, table2, table3 and table4.
I want to map this query using criteria API. In order to do that I created class Table5 which contains all of fields of all classes.
Then I created a repository with the method:
public List<Table5> getAllTable5 () {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Table5> query = cb.createQuery(Table5.class);
Root<Table1> root = query.from(Table1.class);
Join<Table1, Table2> table1Table2Join= root.join(Table1_.TABLE2);
Join<Table2, Table3> table2Table3Join= root.join(Table2_.TABLE3);
Join<Table3, Table4> table3Table4Join= root.join(Table3_.TABLE4);
query.multiselect(
root.get // all fields
);
TypedQuery<Table5> typedQuery = em.createQuery(query);
return typedQuery.getResultList();
}
Is it possible to create class like:
class Table5 {
private Table1 table1;
private Table2 table2;
private Table3 table3;
private Table4 table4;
// getters setters constructors
}
If yes, how should getAllTable5 method look like?
You can either select every table entity, or select just the first one and rely on join fetching.
public List<Table5> getAllTable5 () {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Table1> query = cb.createQuery(Table1.class);
Root<Table1> root = query.from(Table1.class);
query.select(root);
TypedQuery<Table1> typedQuery = em.createQuery(query);
EntityGraph entityGraph = em.createEntityGraph();
// Apply subgraphs for the associations to fetch
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
List<Table1> list = typedQuery.getResultList();
List<Table5> table5s = new ArrayList<>(list.size());
for (Table1 t : list) {
table5s.add(new Table5(t, t.getTable2(), , t.getTable2().getTable3(), , t.getTable2().getTable3().getTable4());
}
return table5s;
}

Spring Predicate JpaSpecificationExecutor IN (Select) Expression

I have the following Query which returns me one row:
SELECT * FROM activity_entity
WHERE activity_id IN (SELECT activity_id FROM category_entity WHERE activity_type = 9999)
AND activity_id = 'AA924EDC-7D55-2555-6569-7D54BDF4F71F'
AND category_id = '45CF9A18-1718-482A-A1C6-CA77E23D29B1'
AND internal_module_id = '311';
And the following Spring Predicate:
public List<ActivityEntity> getRepositoryDataPrincipalActivity(AccessRepVisitLogInDto inDto,AccessRepEntity accessRep) {
List<ActivityEntity> activityL = activityRepository.findAll(new Specification<ActivityEntity>() {
#Override
public Predicate toPredicate(Root<ActivityEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("activityId"), accessRep.getActivityId()));
predicates.add(cb.equal(root.get("categoryId"), inDto.getCategory_id()));
predicates.add(cb.equal(root.get("internalModuleId"), inDto.getInternalModuleId()));
return cb.and(predicates.toArray(new Predicate[0]));
}
});
return activityL;
}
On my predicate I want to include the IN (SELECT) statement:
activity_id IN (SELECT activity_id FROM category_entity WHERE activity_type = 9999)
It is a validation that I need to add to my predicate so that it returns correctly the row that I need.
Subquery<Long> subquery = query.subquery(Long.class);
Root<EntityCategory> category = subquery.from(EntityCategory.class);
Predicate subPredicate = cb.equal(category.get("activityType"), 9999);
subquery.select(category.get("activityId")).where(subPredicate);
predicates.add(root.get("id").in(subquery));

Union subselects querydsl

I need optimize a dynamic query. This an example, but it has more filters (BooleanExpression)
QTable qtable = QTable.table;
BooleanExpression pSubQueryFilter = qtable.field4.in(List<Long>).and((date != null) ? qtable.field5.goe(date) : null);
BooleanExpression pSubQuery =( ((qtable.field1.eq(string1)) .and(qtable .field2.eq(string2)).and(qtable .field3.eq(Boolean.FALSE)))
.or( (qtable.field1.eq(string2)).and(qtable .field2.eq(string1).and(qtable .field3.eq(Boolean.TRUE))))
).and(pSubQueryFilter);
List<T> list = createQuery(pSubQuery ).list(path);
This generates this select, but spend more than 30s in DB because OR clauses. This fields (field1 and field2) have already index in DB.
SELECT * FROM table WHERE
(field1= 'string1' AND field2='string2' AND field3=0)
OR (field1= 'string2' AND field2='string1' AND field3=1 )
AND field4 in (1,2,3,4,5,...) AND field5 > =SYSDATE-365
ORDER BY field5 DESC, id DESC
I optimized this select to 1s in SQL with this result:
SELECT * FROM table WHERE
id IN ( (SELECT id FROM table WHERE field1= 'string1' AND field2='string2' AND field3=0)
UNION (SELECT id FROM table WHERE field1= 'string2' AND field2='string1' AND field3=1 ) )
AND field4 in (1,2,3,4,5,...) AND field5 > =SYSDATE-365
ORDER BY field5 DESC, id DESC
But in QueryDsl didn't get improve. I did it this way, because Union didn't work.
BooleanExpression psubq1 = qtable .field1.eq(string1).and(qtable .field2.eq(string2)).and(qtable .field3.eq(Boolean.FALSE));
BooleanExpression psubq2 = qtable .field1.eq(string2).and(qtable .field2.eq(string1)).and(qtable .field3.eq(Boolean.TRUE));
Expression<?>[] args = { qtable .id};
List<Long> resultids = tableRepository.findAllIds(psubq1, args);
resultids.addAll(tableRepository.findAllIds(psubq2, args));
pSubQuery = qtable .id.in(resultids).and(pSubQueryFilter);
Exists any way to do the last sql with Querydsl with one call to DB?
Thank you
There exist a static method union in com.querydsl.sql.SQLExpressions to do this. As parameters use subqueries created with SQLExpressions::select.
import com.querydsl.core.types.dsl.*;
import com.querydsl.sql.DatePart;
import com.querydsl.sql.SQLExpressions;
import com.querydsl.sql.oracle.OracleGrammar;
import com.querydsl.sql.oracle.OracleQuery;
...
OracleQuery query = new OracleQuery<>(con);
query.select(table).from(table).where(
table.id.in(
SQLExpressions.union(
SQLExpressions.select(table.id).from(table)
.where(table.field1.eq("string1")
.and(table.field2.eq("string2"))
.and(table.field3.eq(Expressions.FALSE))),
SQLExpressions.select(table.id)
.from(table)
.where(table.field1.eq("string2")
.and(table.field2.eq("string1"))
.and(table.field3.eq(Expressions.TRUE))))
)
.and(table.field4.in(1,2,3,4,5))
.and(table.field5.goe(
SQLExpressions.dateadd(DatePart.day, OracleGrammar.sysdate, -365))))
.orderBy(table.field5.desc(), table.id.desc());;

Translate SQL Left Join query to EclipseLink JPA query

I am trying to write a JPA NamedQuery. I have a working SQL query as follows:
SELECT MIN(t1.Id + 1) AS nextID
FROM
MyTable t1 LEFT JOIN MyTable t2
ON
t1.Id + 1 = t2.Id
WHERE
t2.Id IS NULL
I am not able to translate this query to JPQL syntax.
I think doing such a custom join is not possible with standard JPQL. I was looking for a possibility to do it some time ago and found that Hibernate offers a proprietary extension #JoinFormula to achieve this, cf. Hibernate #JoinFormula. But I couldn't find an equivalent for EclipseLink.
You might be able use a #NamedNativeQuery together with an #SqlResultSetMapping to map your SQL statement to a JPA entity, something like this:
#NamedNativeQuery( name = "customJoin",
query = "SELECT MIN(t1.Id + 1) AS nextID FROM MyTable t1 LEFT JOIN MyTable t2 ON t1.Id + 1 = t2.Id WHERE t2.Id IS NULL",
resultSetMapping = "customJoinMapping" )
#SqlResultSetMapping( name = "customJoinMapping",
entities = #EntityResult( entityClass = MyTable.class, fields = #FieldResult( name = "id", column = "nextID" ) ) )
#Entity
#Access( AccessType.Field )
public class MyTable {
private int id;
public int getId() {
return this.id;
}
public void setId( int id ) {
this.id = id;
}
}
UPDATE
Much later, I found a way to customize relationship joins in EclipseLink here. The feature to use is a DescriptorCustomizer, which can be placed on a JPA entity with #Customizer.
In the customize() method, one can express additional join criteria using the EclipseLink Expression API. For example, the following code fragment would produce the additional join criteria [...] AND TargetEntity.someAttribute IN ('V', 'W', 'U') (where someRelationship points to a TargetEntity).
OneToManyMapping mapping = (OneToManyMapping) descriptor.getMappingForAttributeName( "someRelationship" );
Expression origExp = mapping.buildSelectionCriteria();
ExpressionBuilder expBuilder = origExp.getBuilder();
Expression constantExp = expBuilder.get( "someAttribute" ).in( new String[] { "V", "W", "U" } );
Expression newExp = origExp.and( constantExp );
mapping.setSelectionCriteria( newExp );

Categories