I'm using JPA Predicate to generate Specification for later Repository use.
code segment
public Specification<Request> multipleCriteria(List<QueryCriteria> filters) {
log.info("filters[{}]", filters);
return (request, query, cb) -> {
Predicate condition = null;
for (QueryCriteria c : filters) {
String k = c.getKey();
String v = c.getValue();
Predicate tmpCondition = null;
if (k.startsWith(JSON_PATH_SIGN)) {
// json prop
Expression<Boolean> jsonExists = cb.function(
"JSON_EXISTS",
Boolean.class,
request.get(JSON_REQUEST),
new HibernateInlineExpression(cb, ServiceHelper.toJsonPathExp(k, v)));
if (jsonExists instanceof Predicate) {
log.info("jsonExists instanceof Predicate: {}", jsonExists.getClass().getCanonicalName());
} else {
log.info("jsonExists: {}", jsonExists.getClass().getCanonicalName());
}
tmpCondition = cb.isTrue(jsonExists);
...
HibernateInlineExperssion is from http://www.istvandevai.com/2018/02/using-oracle-json-functions-via-jpa-and.html
and then I found JPA generated the wrong SQL:
Hibernate:
select
request0_.ID as id1_5_,
request0_.luby as luby2_5_,
request0_.ludt as ludt3_5_,
request0_.CREATED_DATE as created_date4_5_,
request0_.REQUEST_JSON as request_json5_5_,
request0_.REQUEST_NUMBER as request_number6_5_,
request0_.REQUEST_TYPE as request_type7_5_,
request0_.SCHEME_ID as scheme_id8_5_,
request0_.SCHEME_VERSION_ID as scheme_version_id9_5_,
request0_.STATUS_CODE as status_code10_5_
from
DSME.REQUEST request0_
where
JSON_EXISTS(request0_.REQUEST_JSON,'$.client_contact_email?(@ == "test@abc.com")')=1
and request0_.SCHEME_ID=11
and request0_.REQUEST_TYPE=?
and request0_.STATUS_CODE=?
order by
request0_.ID asc fetch first ? rows only
So exception thrown
18:05:44.550 [http-nio-8055-exec-2] [] WARN o.h.e.jdbc.spi.SqlExceptionHelper:logExceptions 137 - SQL Error: 933, SQLState: 42000
18:05:44.550 [http-nio-8055-exec-2] [] ERROR o.h.e.jdbc.spi.SqlExceptionHelper:logExceptions 142 - ORA-00933: SQL command not properly ended
As I used isTrue
to check the expression, it appended =1
at the end of the WHERE clause: JSON_EXISTS(request0_.REQUEST_JSON,'$.client_contact_email?(@ == "test@abc.com")')=1
I traced Hibernate's code (hibernate-core-5.6.11.Final) and found in org.hibernate.query.criteria.internal.CriteriaBuilderImpl:isTrue:
@Override
public Predicate disjunction() {
return new CompoundPredicate( this, Predicate.BooleanOperator.OR );
}
@Override
public Predicate isTrue(Expression<Boolean> expression) {
if ( CompoundPredicate.class.isInstance( expression ) ) {
final CompoundPredicate predicate = (CompoundPredicate) expression;
if ( predicate.getExpressions().size() == 0 ) {
return new BooleanStaticAssertionPredicate(
this,
predicate.getOperator() == Predicate.BooleanOperator.AND
);
}
return predicate;
}
else if ( Predicate.class.isInstance( expression ) ) {
return (Predicate) expression;
}
return new BooleanAssertionPredicate( this, expression, Boolean.TRUE );
}
As jsonExists
is ParameterizedFunctionExpression
instance, so finally isTrue
returns BooleanAssertionPredicate
instance.
And in BooleanAssertionPredicate.java, I found
@Override
public String render(boolean isNegated, RenderingContext renderingContext) {
final String operator = isNegated ? " <> " : " = ";
final String assertionLiteral = assertedValue ? "true" : "false";
return ( (Renderable) expression ).render( renderingContext )
+ operator
+ assertionLiteral;
}
That is to say, if I:
- use
cb.function()
to generateExpression
(which returnParameterizedFunctionExpression
instance) - use
cb.isTrue
to generatePredicate
then the generated SQL would exist where JSON_EXISTS(...)=1
error.
I have already checked profile, yes, I'm using oracle dialect:
# application.properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
...
hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
Actually all I want is to use CriteriaBuilder/Criteria Queries to call Oracle's JSON function, just like this article mentioned: http://www.istvandevai.com/2018/02/using-oracle-json-functions-via-jpa-and.html
BTW, for JSON_VALUE
, this solution works perfectly. However, when it comes to JSON_EXISTS
, which returns Boolean.class
, it failed...
It would be greately appreciated if anyone could give me some hints...
Many thx~