From b6fbda97700a87fbcf01c4f13c19d5f6f03f6b84 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Sun, 8 Sep 2013 20:48:04 +0200 Subject: [PATCH] DATAJPA-396 - Fixed potential multi-threading issues in PartTreeJpaQuery. The CriteriaQuery implementations unfortunately is not thread-safe for some persistence providers. E.g. Hibernate changes the state of it's implementation during the first attempt to use the instance. This causes multi-threading issues if we cache the query instance for later usage (as the instance might be used concurrently). We now synchronize the first usage of the cached CriteriaQuery instance to guard against this situation. Original pull request: #42. --- .../repository/query/PartTreeJpaQuery.java | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 1aa2f3c71..3b0c25417 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -89,13 +89,13 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { */ private class QueryPreparer { - private final CriteriaQuery query; + private final CriteriaQuery cachedCriteriaQuery; private final List> expressions; public QueryPreparer(boolean recreateQueries) { JpaQueryCreator creator = createCreator(null); - this.query = recreateQueries ? null : creator.createQuery(); + this.cachedCriteriaQuery = recreateQueries ? null : creator.createQuery(); this.expressions = recreateQueries ? null : creator.getParameterExpressions(); } @@ -107,20 +107,40 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { */ public Query createQuery(Object[] values) { - CriteriaQuery criteriaQuery = query; + CriteriaQuery criteriaQuery = cachedCriteriaQuery; List> expressions = this.expressions; ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, values); - if (query == null || accessor.hasBindableNullValue()) { + if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) { JpaQueryCreator creator = createCreator(accessor); criteriaQuery = creator.createQuery(getDynamicSort(values)); expressions = creator.getParameterExpressions(); } - TypedQuery jpaQuery = getEntityManager().createQuery(criteriaQuery); + TypedQuery jpaQuery = createQuery(criteriaQuery); return invokeBinding(getBinder(values, expressions), jpaQuery); } + /** + * Checks whether we are working with a cached {@link CriteriaQuery} and snychronizes the creation of a + * {@link TypedQuery} instance from it. This is due to non-thread-safety in the {@link CriteriaQuery} implementation + * of some persistence providers (i.e. Hibernate in this case). + * + * @see DATAJPA-396 + * @param criteriaQuery must not be {@literal null}. + * @return + */ + private TypedQuery createQuery(CriteriaQuery criteriaQuery) { + + if (this.cachedCriteriaQuery != null) { + synchronized (this.cachedCriteriaQuery) { + return getEntityManager().createQuery(criteriaQuery); + } + } + + return getEntityManager().createQuery(criteriaQuery); + } + protected JpaQueryCreator createCreator(ParametersParameterAccessor accessor) { EntityManager entityManager = getEntityManager();