0

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?(@ == "[email protected]")')=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?(@ == "[email protected]")')=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:

  1. use cb.function() to generate Expression (which return ParameterizedFunctionExpression instance)
  2. use cb.isTrue to generate Predicate

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~

3
  • Have you tried using "tmpCondition = cb.and(jsonExists, cb.conjunction());" instead of isTrue? Commented Mar 9, 2023 at 16:17
  • Thanks for Chris's response. However still no luck... 09:15:12.183 [http-nio-8055-exec-1] [] ERROR o.h.hql.internal.ast.ErrorTracker:reportError 62 - <AST>:1:118: unexpected AST node: function (JSON_EXISTS) And read from api doc: Create a conjunction (with zero conjuncts). A conjunction with zero conjuncts is true. if only use cojunction, say, tmpCondition = cb.conjunction(), or cb.and(cb.conjunction()), the SQL is 1=1 Commented Mar 9, 2023 at 22:35
  • Seems similar to stackoverflow.com/questions/26283195/… . I'd file a bug with Hibernate so they don't require such workarounds all the time if this solution works here too. Commented Mar 10, 2023 at 15:53

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.