更新:
在我深入研究了 spring-data-jpa 存储库查询方法的源代码后,我发现根本原因是PartTreeJpaQuerycreateQuery
的方法,如下所示。
该方法会在调用spring-data-jpa仓库查询方法接口时被RepositoryFactorySupport的QueryExecutorMethodInterceptor的invoke方法调用。
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));
}
在第一次调用时,cachedCriteriaQuery
变量 willSELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id
和values
变量 is[[1000000002], [5, 10]]
都如预期的那样正确。
但是,在第二次调用时,cachedCriteriaQuery
isSELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id
和 the values
is [[1000000003], [5]]
。看起来传递给 openjpa 和 jpa 查询的值是正确的,但criteriaQuery
不是。
如果我将openjpa
库更改为2.2.2版本,则每次调用此方法时,cachedCriteriaQuery
变量都是ALWAYS SELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id
。
为什么
cachedCriteriaQuery
变量会改变?它是 PartTreeJpaQuery 的 QueryPreparer 的私有和最终字段,唯一可以分配它的方法是 QueryPreparer 的构造函数。我已经在那里设置了一个断点,但是在第一次和第二次调用之间,我没有看到任何进程调用了这个构造函数,这个变量怎么会改变?
我所做的只是更改了 openjpa 库。
但上面的代码是 spring-data-jpa 和/或 spring-data-commons。这如何影响查询创建的行为?
给出如下单元测试代码,通过 and 字段查询子find IN
任务deviceId
实体state
。第一次查询是 find IN deviceId 1000000002
and, state 5
and 10
。第二次查询是 find IN deviceId1000000003
和 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());
}
}
subtaskDaospring-data-jpa repository query method
接口如下
public interface SubtaskDao extends DaoBase<Subtask, Long>
{
public List<Subtask> findByDeviceIdInAndStateInOrderByIdAsc(@Param("deviceIdList") Collection<String> deviceIdList, @Param("states") Collection<Integer> states);
}
然而,结果如下,并不像我们预期的那样正确。第二次查询出来了,结果是 deviceId 的子任务,1000000002
而不是 deviceId 1000000003
。
根据 openjpa.Log,第一次查询(在第 7272 行)Query "SELECT s FROM Subtask s WHERE (s.deviceId IN (:deviceIdList) AND s.state IN (:states)) ORDER BY s.id"
。但在第二次,它查询Query "SELECT s FROM Subtask s WHERE (s.deviceId IN ('1000000002') AND s.state IN (5,10)) ORDER BY s.id"
. 为什么参数不是: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
spring
我们使用的, spring-data-jpa
,jdbc driver
和libs的版本openjpa
如下, jdk 是1.8
, 数据库是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>
实体类在构建时通过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>
EntityManagerFactoryBean
设置persistence.xml
如下_
<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>
即使我将QueryCache和QuerySQLCache都设置为false,它仍然无法正常工作。
<property name="openjpa.jdbc.QuerySQLCache" value="false" />
<property name="openjpa.QueryCache" value="false" />
但是,如果我将库降级openjpa
为 version 2.2.2
,它完全适用于所有相同的 code 和 configuration。此外,如果我使用@Query
下面的 subtaskDao 接口,即使使用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);
我没有发现的棘手问题是什么?