Update:
After I digged into the source code of spring-data-jpa repository query methods, I found the root cause is the createQuery
method of PartTreeJpaQuery as below.
This method will be called by the invoke method of the QueryExecutorMethodInterceptor of the RepositoryFactorySupport when you call the spring-data-jpa repository query methods interface.
public Query createQuery(Object[] values) {
CriteriaQuery<?> criteriaQuery = cachedCriteriaQuery;
List<ParameterMetadata<?>> expressions = this.expressions;
ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) {
JpaQueryCreator creator = createCreator(accessor, persistenceProvider);
criteriaQuery = creator.createQuery(getDynamicSort(values));
expressions = creator.getParameterExpressions();
}
TypedQuery<?> jpaQuery = createQuery(criteriaQuery);
return restrictMaxResultsIfNecessary(invokeBinding(getBinder(values, expressions), jpaQuery));
}
At the first time call, the cachedCriteriaQuery
variable will be SELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id
and the values
variable is [[1000000002], [5, 10]]
which are both correct as expected.
However, at the second time call, the cachedCriteriaQuery
is SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id
and the values
is [[1000000003], [5]]
. Looks like the value pass into openjpa and jpa query is correct but the criteriaQuery
isn't.
If I changed the openjpa
libs to version 2.2.2, each time of calling this method, the cachedCriteriaQuery
variable are ALWAYS SELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id
.
Why the
cachedCriteriaQuery
variable will be changed?It is a private and final field of QueryPreparer of PartTreeJpaQuery and the only way can assign it shall be the constructor of QueryPreparer. I have togged a breakpoint there, but between the first and second time call, I haven't seen any process has called this constructor and how can this variable became changed?
All I have done is just changed the openjpa libs.
But above codes are spring-data-jpa and/or spring-data-commons. How can this affect the behavior of the Query creation?
Give the unit test code as below to query the subtask entity by find IN
the deviceId
and state
fields. The first time query is find IN deviceId 1000000002
and, state 5
and 10
. The second time query is find IN deviceId 1000000003
and state 5
.
List<Subtask> subtasks = this.subtaskDao.findByDeviceIdInAndStateInOrderByIdAsc(Arrays.asList(new String[]{"1000000002"}), Arrays.asList(new Integer[]{5, 10}));
System.out.println("1" + subtasks);
print(subtasks);
Thread.sleep(8000);
List<Subtask> subtasks1 = this.subtaskDao.findByDeviceIdInAndStateInOrderByIdAsc(Arrays.asList(new String[]{"1000000003"}), Arrays.asList(new Integer[]{5}));
System.out.println("2" + subtasks1);
print(subtasks1);
private void print(List<Subtask> subtasks)
{
for (Subtask subtask : subtasks)
{
System.out.println(subtask.getId() + ", " + subtask.getDeviceId());
}
}
The subtaskDao is spring-data-jpa repository query method
interface as below
public interface SubtaskDao extends DaoBase<Subtask, Long>
{
public List<Subtask> findByDeviceIdInAndStateInOrderByIdAsc(@Param("deviceIdList") Collection<String> deviceIdList, @Param("states") Collection<Integer> states);
}
However the results are as below which are not correct as we expect. The second time query has come out the result is the subtask of deviceId 1000000002
instead of deviceId 1000000003
.
According to the openjpa.Log, at the first time it query(at line 7272) Query "SELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id"
.
But at the second time, it query Query "SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id"
. Why the parameter are not :deviceIdList
and :states
?
7248 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - Found datasource1: datasource 193492784 from configuration. StoreContext: org.apache.openjpa.kernel.BrokerImpl@3344d163
7248 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - org.apache.openjpa.persistence.EntityManagerFactoryImpl@21362712 created EntityManager org.apache.openjpa.persistence.EntityManagerImpl@3344d163.
7272 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - Query "SELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id" is cached."
7273 PersistenceUnitAppDeploy TRACE [main] openjpa.Query - Executing query: [Query: org.apache.openjpa.kernel.QueryImpl@5403907; candidate class: class devicemanage.repository.appdeploy.entity.Subtask; query: null] with parameters: ?
7278 PersistenceUnitAppDeploy TRACE [main] openjpa.DataCache - Cache miss while looking up key "org.apache.openjpa.datacache.QueryKey@35db94c8[query:[SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id],access path:[devicemanage.repository.appdeploy.entity.Subtask],subs:true,ignoreChanges:false,startRange:0,endRange:9223372036854775807,timeout:-1]".
7317 PersistenceUnitAppDeploy TRACE [main] openjpa.jdbc.SQL - <t 2066366456, conn 178371348> executing prepstmnt 1924108520
SELECT t0.id, t0.despair_count, t0.device_id, t0.domain_id, t0.finish_time, t0.remark, t0.state, t0.task_id, t0.task_type, t0.version
FROM subtask t0
WHERE (t0.device_id = ? AND t0.device_id IS NOT NULL AND (t0.state = ? OR t0.state = ?) AND t0.state IS NOT NULL)
ORDER BY t0.id ASC
[params=?, ?, ?]
7324 PersistenceUnitAppDeploy TRACE [main] openjpa.jdbc.SQL - <t 2066366456, conn 178371348> [7 ms] spent
7357 PersistenceUnitAppDeploy TRACE [main] openjpa.DataCache - Put key "org.apache.openjpa.datacache.QueryKey@35db94c8[query:[SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id],access path:[devicemanage.repository.appdeploy.entity.Subtask],subs:true,ignoreChanges:false,startRange:0,endRange:9223372036854775807,timeout:-1]" into cache.
7358 PersistenceUnitAppDeploy TRACE [main] openjpa.jdbc.JDBC - <t 2066366456, conn 0> [0 ms] close
7360 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - Query "SELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id" is removed from cache excluded permanently. Query "SELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id" is not cached because its result is not obtained by executing a select statement. This can happen if the query was evaluated in-memory. The result was provided by org.apache.openjpa.datacache.QueryCacheStoreQuery$CachingResultObjectProvider. .
7361 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - org.apache.openjpa.persistence.EntityManagerImpl@3344d163.close() invoked.
1[devicemanage.repository.appdeploy.entity.Subtask@16f15ae9, devicemanage.repository.appdeploy.entity.Subtask@6206b4a7, devicemanage.repository.appdeploy.entity.Subtask@77896335]
981, 1000000002
982, 1000000002
983, 1000000002
15371 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - Found datasource1: datasource 193492784 from configuration. StoreContext: org.apache.openjpa.kernel.BrokerImpl@5d14e99e
15371 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - org.apache.openjpa.persistence.EntityManagerFactoryImpl@21362712 created EntityManager org.apache.openjpa.persistence.EntityManagerImpl@5d14e99e.
15372 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - Query "SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id" is cached."
15372 PersistenceUnitAppDeploy TRACE [main] openjpa.Query - Executing query: [Query: org.apache.openjpa.kernel.QueryImpl@38cfecf3; candidate class: class devicemanage.repository.appdeploy.entity.Subtask; query: null] with parameters: ?
15372 PersistenceUnitAppDeploy TRACE [main] openjpa.DataCache - Cache miss while looking up key "org.apache.openjpa.datacache.QueryKey@35db9909[query:[SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id],access path:[devicemanage.repository.appdeploy.entity.Subtask],subs:true,ignoreChanges:false,startRange:0,endRange:9223372036854775807,timeout:-1]".
15373 PersistenceUnitAppDeploy TRACE [main] openjpa.jdbc.SQL - <t 2066366456, conn 178371348> executing prepstmnt 632104437
SELECT t0.id, t0.despair_count, t0.device_id, t0.domain_id, t0.finish_time, t0.remark, t0.state, t0.task_id, t0.task_type, t0.version
FROM subtask t0
WHERE (t0.device_id = ? AND t0.device_id IS NOT NULL AND (t0.state = ? OR t0.state = ?) AND t0.state IS NOT NULL)
ORDER BY t0.id ASC
[params=?, ?, ?]
15374 PersistenceUnitAppDeploy TRACE [main] openjpa.jdbc.SQL - <t 2066366456, conn 178371348> [1 ms] spent
15375 PersistenceUnitAppDeploy TRACE [main] openjpa.DataCache - Put key "org.apache.openjpa.datacache.QueryKey@35db9909[query:[SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id],access path:[devicemanage.repository.appdeploy.entity.Subtask],subs:true,ignoreChanges:false,startRange:0,endRange:9223372036854775807,timeout:-1]" into cache.
15375 PersistenceUnitAppDeploy TRACE [main] openjpa.jdbc.JDBC - <t 2066366456, conn 0> [0 ms] close
15375 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - Query "SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id" is removed from cache excluded permanently. Query "SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id" is not cached because its result is not obtained by executing a select statement. This can happen if the query was evaluated in-memory. The result was provided by org.apache.openjpa.datacache.QueryCacheStoreQuery$CachingResultObjectProvider. .
15375 PersistenceUnitAppDeploy TRACE [main] openjpa.Runtime - org.apache.openjpa.persistence.EntityManagerImpl@5d14e99e.close() invoked.
2[devicemanage.repository.appdeploy.entity.Subtask@1860a7a1, devicemanage.repository.appdeploy.entity.Subtask@40d96578, devicemanage.repository.appdeploy.entity.Subtask@c97721b]
981, 1000000002
982, 1000000002
983, 1000000002
The version of spring
, spring-data-jpa
, jdbc driver
and openjpa
libs we used as follows, and the jdk is 1.8
, the database is PostgreSQL 9.4 windows version
.
<springVersion>4.3.0.RELEASE</springVersion>
<springDataJpaVersion>1.10.2.RELEASE</springDataJpaVersion>
<openjpaVersion>2.4.1</openjpaVersion>
<postgreSQLJDBCVersion>9.4.1208</postgreSQLJDBCVersion>
The entity classes are enhanced at the build time by openjpa-maven-plugin
<plugin>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-maven-plugin</artifactId>
<version>${openjpaVersion}</version>
<executions>
<execution>
<id>enhancer</id>
<phase>process-classes</phase>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa</artifactId>
<version>${openjpaVersion}</version>
</dependency>
</dependencies>
<configuration>
<includes>devicemanage/repository/**/*.class</includes>
<addDefaultConstructor>true</addDefaultConstructor>
<enforcePropertyRestrictions>true</enforcePropertyRestrictions>
</configuration>
</plugin>
The EntityManagerFactoryBean
settings and persistence.xml
are as below
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter" />
</property>
<property name="persistenceUnitName" value="PersistenceUnitAppDeploy" />
<property name="jpaProperties">
<props>
<prop key="openjpa.ConnectionURL">jdbc:postgresql:127.0.0.1:5432/database</prop>
<prop key="openjpa.ConnectionUserName">user</prop>
<prop key="openjpa.ConnectionPassword">user</prop>
<prop key="openjpa.ConnectionDriverName">org.postgresql.Driver</prop>
</props>
</property>
</bean>
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="PersistenceUnitAppDeploy" transaction-type="RESOURCE_LOCAL">
<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
<class>devicemanage.repository.appdeploy.entity.Subtask</class>
... // ommit other entity declaration
<properties>
<property name="openjpa.RemoteCommitProvider" value="sjvm" />
<property name="openjpa.QueryCache" value="true" />
<property name="openjpa.RuntimeUnenhancedClasses" value="unsupported" />
<property name="openjpa.InverseManager" value="true(Action=warn)" />
<property name="openjpa.OrphanedKeyAction" value="log(Channel=Orphans, Level=TRACE)" />
<property name="openjpa.ConnectionFactoryProperties" value="PrettyPrint=true, PrettyPrintLineLength=200" />
<property name="openjpa.Multithreaded" value="true" />
<property name="openjpa.LockManager" value="none" />
<property name="openjpa.WriteLockLevel" value="none" />
<property name="openjpa.Compatibility" value="QuotedNumbersInQueries=true" />
<property name="openjpa.Log" value="DefaultLevel=TRACE, Runtime=TRACE, Tool=TRACE, SQL=TRACE, Query=TRACE" />
</properties>
</persistence-unit>
</persistence>
Even if I have set both the QueryCache and QuerySQLCache to false, it still not works right.
<property name="openjpa.jdbc.QuerySQLCache" value="false" />
<property name="openjpa.QueryCache" value="false" />
However, if I downgrade the openjpa
libs to version 2.2.2
, it works well exactly with all the same code and configuration. Moreover, if I used the @Query
for the subtaskDao interface as below, it also works well even with openjpa version 2.4.1
.
@Query("SELECT entity FROM Subtask as entity WHERE entity.deviceId IN :deviceIdList AND entity.state IN :states")
public List<Subtask> findByDeviceIdInAndStateInOrderByIdAsc(@Param("deviceIdList") Collection<String> deviceIdList, @Param("states") Collection<Integer> states);
What is the tricky I haven't discover?