I have a problem involving querydsl and DTO:s
I have some query object:
QPerson person = QPerson.person;
QExamCode examCode = QExamCode.examCode;
QExamGrade examGrade = QExamGrade.examGrade;
QProgram gradeProgram = examGrade.program;
From them I try to query and list instances of a DTO class (that is not an entity) that is called CompletedCreditsSummary.
CompletedCreditsSummary has a constructor which takes: Long,Long,Float.
JPQLQuery query = new JPAQuery(manager);
query = query.from(person, examCode, examGrade);
query = query.where(person.studies.examGrades.contains(examGrade).and(examGrade.examCode.eq(examCode)).and(examGrade.passed.isTrue()));
I am able to do this (Without group by and with CompletedCreditsSummary requiering all the parameters it needs to be able to create person and program objects, in this case simplified to person.id and program.id)
ConstructorExpression.create(CompletedCreditsSummary.class,person.id,program.id,examCode.credits);
return query.list(completedCreditsSummaryExpression);
This works. But when I want to add this to the query:
query.groupBy(person, examGrade.program);
and create CompletedCreditssummary with examCode.credits.sum() i.e.
ConstructorExpression.create(CompletedCreditsSummary.class,person.id,gradeProgram.id,examCode.credits.sum());
instead of
ConstructorExpression.create(CompletedCreditsSummary.class,person.id,gradeProgram.id,examCode.credits);
I get a: java.lang.IllegalArgumentException: argument type mismatch.
The question here is what the difference between examCode.credits (NumberPath) and examcode.credits.sum() (NumberExpression) and what I could do to solve my problem.
As I am learning querydsl by trial and error there is probably something fundamental that I have overlooked. Would really appreciate any help!
Regards Rasmus
EDIT: Preferably I would something like this to work (with group by and CompletedCreditsSummary constructor taking Person,Program,Float.):
ConstructorExpression<Person> personExpression = ConstructorExpression.create(Person.class,person.id);
ConstructorExpression<Program> programExpression = ConstructorExpression.create(Program.class,gradeProgram.id);
ConstructorExpression<CompletedCreditsSummary> completedCreditsSummaryExpression = ConstructorExpression.create(CompletedCreditsSummary.class,personExpression,programExpression,examCode.credits.sum());
EDIT: Got it to work by having the CompletedCreditsSummary Constructor accepting: Long,Long,Number. That is I changed Float to Number. This is not an ideal solution but at least it works.
Try something like this
JPAQuery query = new JPAQuery(manager);
query.from(person, examCode, examGrade)
.where(
person.studies.examGrades.contains(examGrade),
examGrade.examCode.eq(examCode),
examGrade.passed.isTrue())
.groupBy(person, examGrade.program)
.list(ConstructorExpression.create(
CompletedCreditsSummary.class,
person, examGrade.program, examCode.credits.sum()));
You need to make sure that the argument for ConstructorExpression after the class are compatible with the arguments to the constructor you want to invoke. Replacing entities with ids caused your problems.
Related
I have the following code, which selects everything in AEntity.
Box<AEntity> a = boxStore.boxFor(AEntity.class);
return new ObjectBoxLiveData<AEntity>(a.query().build());
AEntity has a ToMany relationship with BEntity:
#Backlink(to = "aEntity")
private ToMany<BEntity> bEntities;
I would like to select everything in AEntity as shown above while checking a property of BEntity. The ideal code would look something like this:
Box<AEntity> a = boxStore.boxFor(AEntity.class);
return new ObjectBoxLiveData<AEntity>(a.query().notEqual(BEntity_.bproperty, "-1").build());
Basically I am saying: "I want everything from AEntity as long as bproperty isn't "-1".
Of course, this doesn't work but is there a way I could achieve this behavior?
Did you try adding a link query for the related entity BEntity? Something like:
queryBuilderA = a.query();
queryBuilderA.link(AEntity_.relation).notEqual(BEntity_.bproperty, -1);
queryBuilderA.build();
https://docs.objectbox.io/queries#add-query-conditions-for-related-entities-links
I'm using jOOQ's MockDataProvider to mock calls to the database. I've figured out how to return a single record using the information here: https://blog.jooq.org/2013/02/20/easy-mocking-of-your-database/
However, I want to return a list of results not just a single record for my query. How do I do that?
I can use the following (from the above link) to return a single result:
return new MockResult[] {
new MockResult(1, result)
};
However, I cannot figure out how to add multiple results, all of the constructors for MockResult only take a single result. Any hints? Am I missing something obvious?
For example if I query all bicycles that are road bikes:
SELECT * FROM bicycles WHERE type = "road";
how do I return a list of 10 bicycles instead of just one?
I can use the following (from the above link) to return a single result
But that's already it. You return a single result with several records. The result you pass to that MockResult constructor could look like this:
var result = ctx.newResult(BICYCLES.COL1, BICYCLES.COL2);
result.add(ctx.newRecord(BICYCLES.COL1, BICYCLES.COL2).values(1, 2));
result.add(ctx.newRecord(BICYCLES.COL1, BICYCLES.COL2).values(3, 4));
...
I'm currently using QueryDSL 3.7.2 with Spring-Data-JPA. I'm building a predicate, then passing it to a repository that extends QueryDslPredicateExecutor and queries a PostgreSQL 9.3 database.
I had everything working correctly, but have run into two scenarios that are causing me issues. I'm not sure if I'm approaching this correctly, and I can't seem to find any examples that quite match the scenarios.
Scenario 1:
For this, I have an entity with a list of children. I wanted an "AND" on two of the child properties, so I used a JPASubQuery:
final QChild any = QParent.parent.children.any();
final JPASubQuery subQuery = new JPASubQuery();
final QChild qChild = QChild.child;
subQuery.from(qChild)
.where(qChild.code.eq(codeValue)
.and(qChild.date.isNull()));
predicateBuilder.and(any.in(subQuery.list(qChild)));
So basically I want to fetch any Parent objects where the Child has a code of codeValue and a null date. This worked perfectly when the Child had a surrogate key in the database (an ID column). This generated the following query when passed to the repository:
select
count(parent0_.parent_id) as col_0_0_
from parent_tab parent0_
where exists (
select 1
from child_tab child1_
where parent0_.parent_id=child1_.parent_id
and (
child1_.child_id
in (
select child2_.child_id
from child_tab child2_
where child2_.status=? and (child2_.date is null)
)
)
)
The problem arises when we change the surrogate key to a natural key with serveral fields (code, status, parent_id and name). The following query is then generated:
select
count(parent0_.parent_id) as col_0_0_
from parent_tab parent0_
where exists (
select 1
from child_tab child1_
where parent0_.parent_id=child1_.parent_id
and (
(child1_.code, child1_.status, child1_.parent_id, child1_.name)
in (
select (child2_.code, child2_.status, child2_.parent_id, child2_.name)
from child_tab child2_
where child2_.status=? and (child2_.date is null)
)
)
)
This isn't valid, and throws the following exception:
ERROR: subquery has too few columns
From what I can gather, any().in(subQuery.list(qChild)) is the part that's causing the problem. From all the examples I've found, this is what's being done - but it's also with a surrogate key. Is there something that I'm missing here?
Scenario 2:
The second scenario is dealing with a PostgreSQL only feature - the pg_trgm extension. This extension is used for fuzzy searching, and allows us to use two specific commands - "similarity(x, y)" and "x % y". The former will return a real number representing how close a match the parameters are, and the second will return a boolean if the values are above 0.3 (by default). Originally I was using the former, like so:
final NumberExpression<Double> nameExpression = NumberTemplate.create(Double.class, "similarity({0}, {1})", QParent.parent.name ConstantImpl.create(name));
predicateBuilder.or(nameExpression.goe(0.3));
This worked perfectly, but unfortunately "similarity(x, y)" doesn't use trigram indexes, so I wanted to change to the "%" operator, which does. I thought it should be as easy as the following:
final BooleanExpression nameExpression = BooleanTemplate.create("{0} % {1}", QParent.parent.name, ConstantImpl.create(name));
predicateBuilder.or(nameExpression.isTrue());
Unfortunately this doesn't work, and throws the following exception:
java.lang.IllegalArgumentException: Parameter value [true] did not match expected type [java.lang.String (n/a)]
at com.mysema.query.jpa.impl.JPAUtil.setConstants(JPAUtil.java:55) [querydsl-jpa-3.7.2.jar:]
at com.mysema.query.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:130) [querydsl-jpa-3.7.2.jar:]
at com.mysema.query.jpa.impl.AbstractJPAQuery.count(AbstractJPAQuery.java:81) [querydsl-jpa-3.7.2.jar:]
at org.springframework.data.jpa.repository.support.QueryDslJpaRepository.findAll(QueryDslJpaRepository.java:141) [spring-data-jpa-1.9.2.RELEASE.jar:]
The problem seems to be that it's expecting a String instead of a Boolean for the JavaType in the AbstractJPAQuery. Excluding isTrue() results in the following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: % near line 3, column 19
The query is the following:
select count(parent)
from com.test.Parent parent
where parent.name % ?1
I solved this by using a custom dialect, and registering the following function:
registerFunction("sim", new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "?1 % ?2"));
Then I can use the following:
final BooleanExpression nameExpression = BooleanTemplate.create("sim({0}, {1})", QParent.parent.name, ConstantImpl.create(name));
predicateBuilder.or(nameExpression.isTrue());
When expanding on this, I wanted to also check the name of the child entities. This led to the following:
final BooleanExpression childExpression = BooleanTemplate.create("sim({0}, {1})", QParent.parent.children.any().name, ConstantImpl.create(name));
predicateBuilder.or(childExpression.isTrue());
This, however, doesn't work, and throws the following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: exists near line 3, column 7
The query is:
select count(parent)
from com.test.Parent parent
where exists (select 1
from parent.children as parent_children_4652c
where sim(parent_children_4652c.name, ?1)) = ?2
The exception seems to point to the "exists", but I'm not sure why. As a result, I tried to create a sub-query (like scenario 1):
final BooleanExpression childExpression = BooleanTemplate.create("sim({0}, {1})", QChild.child.name, ConstantImpl.create(name));
final QChild child = QChild.child;
final JPASubQuery subQuery = new JPASubQuery();
subQuery.from(child)
.where(childExpression.isTrue());
predicateBuilder.or(QParent.parent.children.any().in(subQuery.list(child)));
This, however, runs into the same problem as scenario 1, where the child entity has a composite key, and the any().in() doesn't seem correct.
So there's a few questions here:
Is there a way to use the % operator without registering a custom function in the dialect?
Am I querying the children correctly with the sim() function?
What's the correct way to craft a sub-query with a composite key?
Any additional pointers or help would be greatly appreciated too.
Figured out how to do both (though I haven't answered all questions from Scenario 2). My problem was thinking that I wanted the entity returned. A good night's sleep made me see it clearly.
Instead of returning qChild from the subquery list, I should have been returning its parent ID. Then I simply needed to check if the parent ID was in that list:
final String code = "code";
final String name = "name";
final JPASubQuery subQuery = new JPASubQuery();
final QParent parent = QParent.parent;
final QChild child = QChild.child;
subQuery.from(child)
.where(child.id.code.eq(code)
.and(child.id.name.eq(name)));
predicateBuilder.or(parent.id.in(subQuery.list(child.id.parentId)));
For the second scenario, I kept the custom Dialect with the registerFunction for my trigram operator, and used the following subquery:
final String name = "name";
final QParent parent = QParent.parent;
final QChild child = QChild.child;
final BooleanExpression newExpression = BooleanTemplate.create("sim({0}, {1})",
child.id.name, ConstantImpl.create(name));
final JPASubQuery subQuery = new JPASubQuery();
subQuery.from(child)
.where(newExpression.isTrue());
predicateBuilder.or(parent.id.in(subQuery.list(child.id.parentId)));
Everything is working correctly, though I still wonder if there's a way to simply use the trigram operator without registering a custom function.
I am currently in the process of learning the Java Spring Framework, and I am having difficulty understanding why the following query is failing to return any results from the database.
I am ultimately trying to create a where method in my OffersDAO class that allows my to query on a specific field, for a specific value.
public List<Offer> where(String field, String value){
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("field", field);
params.addValue("value", value);
String sql = "select * from offers where :field = :value";
return jdbc.query(sql, params, new RowMapper<Offer>(){
public Offer mapRow(ResultSet rs, int arg1) throws SQLException {
Offer offer = new Offer();
offer.setId(rs.getInt("id"));
offer.setName(rs.getString("name"));
offer.setText(rs.getString("text"));
offer.setEmail(rs.getString("email"));
return offer;
}
});
}
I am able to successfully query the database for results when I specify the field explicitly, as follows:
String sql = "select * from offers where name = :value";
Obviously there is something wrong with specifying the field name dynamically. My guess is it is most likely due to the fact that the field key is being inserted as a mysql string (with ''), when in fact mysql expects a column name for the :field placeholder.
My questions are as follows:
Is there a way to accomplish what I am attempting to do above, using the jdbc NamedParameterJdbcTemplate class?
If I cannot accomplish the above, by what means can I?
Thank you
Edit: No exceptions are thrown. In the case when I am attempting to supply the column name, a empty result set is returned.
You can't specify the field name in a parameter - only the field value. Since you know the DB schema when you're writing the code, this shouldn't be much of a problem.
What about include all possible fields in the filter but restricting their usage by field name param. Like this:
select * from offers where
('name'=:field and name = :value)
OR
('field2'=:field and field2 = :value)
OR
('field3'=:field and field3 = :value)
I don't know how You can implement it with spring (I mean use variable column names) but I can suggest to use the following principle.
Keep your query like template:
String sql = "select * from offers where ##field = :value";
And every time before execution replace ##value parameter with the column You want.
And then You are gone.
So what I'm doing is creating a subquery that gets a list of ID values, then the main query gets all the necessary values and adds ordering.
What I have is this:
ReportQuery querySub = new ReportQuery(Predmet.class, generatedExpression);
querySub.addAttribute("m_id");
DatabaseRow row = new DatabaseRow();
querySub.prepareCall(getSession(), row);
// This part is the problem
String sql = querySub.getTranslatedSQLString(getSession(), row);
The problem with this code is that it doesn't return TranslatedSQLString, it returns the same result as querySub.getSQLString(). Now in all the example code I saw, they either instanced row as a new object or didn't bother to write from where they got the reference but whatever the case, this doesn't work (TopLink version issue?). I'm guessing I need to populate the DatabaseRow object myself, but I can't find any example online.
I didn't manage to find any way to do this by using getTranslatedSQLString. I suppose the DatabaseRow needs to be populated, but I have yet to find the proper way. For now, I'm using "bruteforce" substitution, I "memorize" all of my parameters and do a find/replace on each "?" sign in the query.
you need get session like this:
JpaHelper.getDatabaseSession(getEntityManager().getEntityManagerFactory());
The Database that you need is:
TypedQuery<T> typedQuery = getEntityManager().createQuery(cq);
DatabaseQuery databaseQuery = typedQuery.unwrap(JpaQuery.class).getDatabaseQuery();
So the final example is:
Session session = JpaHelper.getDatabaseSession(getEntityManager().getEntityManagerFactory());
DatabaseQuery databaseQuery = null;
if(typedQuery instanceof EJBQueryImpl)
databaseQuery = ((EJBQueryImpl<T>)typedQuery).getDatabaseQuery();
else
databaseQuery = typedQuery.unwrap(JpaQuery.class).getDatabaseQuery();
sql = databaseQuery.getTranslatedSQLString(session, databaseQuery.getTranslationRow());
That work for me.