Не работает запрос, сгенерированный плагином в JmixDataRepository

Jmix version: 2.7.1
Jmix Studio Plugin Version: 2.7.0-251
IntelliJ version: IntelliJ IDEA 2025.1.1.1 (Community Edition)

	@Query("select (count(o) > 0) from opt_CalcJournal o where o.versionInput.id = :versionInputId and o.id <> :id")
	boolean existsByVersionInputIdAndIdNot(@Param("versionInputId") UUID versionInputId, @Param("id") UUID id);

Прилагаю исходный код (частикчно) (4.4 КБ)

Стектрейс ошибки:

2025-12-19 16:20:00.969 ERROR [http-nio-8080-exec-3] --- o.s.t.s.TransactionSynchronizationUtils.invokeAfterCompletion : TransactionSynchronization.afterCompletion threw exception
io.jmix.data.impl.jpql.JpqlSyntaxException: Errors found for input jpql:[select (count(o) > 0) from opt_CalcJournal o where o.versionInput.id = :versionInputId and o.id <> :id]
CommonErrorNode [<unexpected: [@3,8:12='count',<11>,1:8], resync=(count(>]
CommonErrorNode [<mismatched token: [@6,15:15=')',<35>,1:15], resync=) > 0) from opt_CalcJournal o>]
	at io.jmix.data.impl.jpql.Parser.checkTreeForExceptions(Parser.java:112)
	at io.jmix.data.impl.jpql.Parser.parse(Parser.java:40)
	at io.jmix.data.impl.jpql.QueryTree.<init>(QueryTree.java:50)
	at io.jmix.data.impl.jpql.QueryTree.<init>(QueryTree.java:40)
	at io.jmix.data.impl.jpql.QueryParserAstBased.getTree(QueryParserAstBased.java:74)
	at io.jmix.data.impl.jpql.QueryParserAstBased.getQueryPaths(QueryParserAstBased.java:195)
	at io.jmix.data.accesscontext.LoadValuesAccessContext.getEntityClasses(LoadValuesAccessContext.java:51)
	at io.jmix.securitydata.constraint.LoadValuesConstraint.applyTo(LoadValuesConstraint.java:58)
	at io.jmix.securitydata.constraint.LoadValuesConstraint.applyTo(LoadValuesConstraint.java:34)
	at io.jmix.core.AccessManager.lambda$applyConstraints$2(AccessManager.java:77)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
	at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
	at io.jmix.core.AccessManager.applyConstraints(AccessManager.java:76)
	at io.jmix.data.impl.DataStoreCrudValuesListener.beforeValueLoad(DataStoreCrudValuesListener.java:43)
	at io.jmix.core.datastore.DataStoreBeforeValueLoadEvent.sendTo(DataStoreBeforeValueLoadEvent.java:66)
	at io.jmix.core.datastore.AbstractDataStore.fireEvent(AbstractDataStore.java:353)
	at io.jmix.core.datastore.AbstractDataStore.loadValues(AbstractDataStore.java:254)
	at io.jmix.core.impl.UnconstrainedDataManagerImpl.loadValues(UnconstrainedDataManagerImpl.java:277)
	at io.jmix.core.impl.repository.query.JmixScalarQuery.processAccordingToReturnType(JmixScalarQuery.java:135)
	at io.jmix.core.impl.repository.query.JmixScalarQuery.execute(JmixScalarQuery.java:95)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at io.jmix.core.impl.repository.support.method_metadata.CrudMethodMetadataAccessingPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataAccessingPostProcessor.java:77)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
	at jdk.proxy2/jdk.proxy2.$Proxy428.existsByVersionInputIdAndIdNot(Unknown Source)
	at ru.dgtserv.mes.optimus.listener.CalcJournalEventListener.onCalcJournalChangedAfterCommit(CalcJournalEventListener.java:38)

Добрый день!

Конструкция вида select number > 0 ...
не считается валидной с точки зрения грамматики Jmix EclipseLink (посмотреть грамматику можно тут).

Завел задачу на студию, чтобы поправить генерацию запросов такого вида.

Как вариант, есть несколько обходных путей, которые помогут достичь желаемого результата:

  1. Case-Expression
    Изменить запрос на select case when (conditional_expression)
    Обсуждение на stackoverflow
    Пример:
@Query("select case when (count(e.total) > 0) then true else false end " +
            "from Order_ e where e.id = :id")
    boolean verifyOrderTotalIsPositive(@Param("id") UUID id);
  1. Проверка в коде
    Изменить запрос на select count(o) и делать проверку на > 0 в коде явно, по типу
boolean exists = repository.countByVersionInputIdAndIdNot(versionInputId, id) > 0;
  1. Derived repository method
boolean existsByVersionInput_IdAndIdNot(UUID versionInputId, UUID id);
  1. DataManager
default boolean verifyOrderTotalIsPositive() {
     getDataManager().load(Order.class)...   
}

С уважением,
Михаил