Permalink
Browse files

DATAJPA-73 - Added support for locking.

Repository query methods can now be equipped with a @Lock annotation that carries the LockModeType to be used when executing the query. Beyond that, CRUD methods can be redeclared to carry lock metadata as well.

interface UserRepository extends Repository<User, Long> {
  // CRUD method redeclaration
  @Lock(LockModeType.READ)
  List<User> findAll();

  // Query method
  @Lock(LockModeType.READ)
  List<User> findByLastname(String lastname);
}
  • Loading branch information...
1 parent d49d4d5 commit c915077074a42068ac8c64c032f675a0614251d0 @olivergierke olivergierke committed Dec 6, 2011
@@ -763,6 +763,42 @@ public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
</section>
</section>
+ <section>
+ <title>Locking</title>
+
+ <para>To specify the lock mode to be used the
+ <interfacename>@Lock</interfacename> annotation can be used on query
+ methods:</para>
+
+ <example>
+ <title>Defining lock metadata on query methods</title>
+
+ <programlisting language="java">interface UserRepository extends Repository&lt;User, Long&gt; {
+
+ // Plain query method
+ @Lock(LockModeType.READ)
+ List&lt;User&gt; findByLastname(String lastname);
+}</programlisting>
+ </example>
+
+ <para>This method declaration will cause the query being triggered to be
+ equipped with the <interfacename>LockModeType</interfacename>
+ <code>READ</code>. You can also define locking for CRUD methods by
+ redeclaring them in your repository interface and adding the
+ <interfacename>@Lock</interfacename> annotation:</para>
+
+ <example>
+ <title>Defining lock metadata on CRUD methods</title>
+
+ <programlisting language="java">interface UserRepository extends Repository&lt;User, Long&gt; {
+
+ // Redeclaration of a CRUD method
+ @Lock(LockModeType.READ);
+ List&lt;User&gt; findAll();
+}</programlisting>
+ </example>
+ </section>
+
<section id="jpa.auditing">
<title>Auditing</title>
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.persistence.LockModeType;
+
+/**
+ * Annotation used to specify the {@link LockModeType} to be used when executing the query. It will be evaluated when
+ * using {@link Query} on a query method or if you derive the query from the method name.
+ *
+ * @author Aleksander Blomskøld
+ * @author Oliver Gierke
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Lock {
+
+ /**
+ * The {@link LockModeType} to be used when executing the annotated query or CRUD method.
+ *
+ * @return
+ */
+ LockModeType value();
+}
@@ -16,6 +16,7 @@
package org.springframework.data.jpa.repository.query;
import javax.persistence.EntityManager;
+import javax.persistence.LockModeType;
import javax.persistence.Query;
import javax.persistence.QueryHint;
import javax.persistence.TypedQuery;
@@ -122,12 +123,25 @@ protected JpaQueryExecution getExecution() {
return query;
}
+ /**
+ * Applies the {@link LockModeType} provided by the {@link JpaQueryMethod} to the given {@link Query}.
+ *
+ * @param query must not be {@literal null}.
+ * @param method must not be {@literal null}.
+ * @return
+ */
+ private Query applyLockMode(Query query, JpaQueryMethod method) {
+
+ LockModeType lockModeType = method.getLockModeType();
+ return lockModeType == null ? query : query.setLockMode(lockModeType);
+ }
+
protected ParameterBinder createBinder(Object[] values) {
return new ParameterBinder(getQueryMethod().getParameters(), values);
}
protected Query createQuery(Object[] values) {
- return applyHints(doCreateQuery(values), method);
+ return applyLockMode(applyHints(doCreateQuery(values), method), method);
}
protected TypedQuery<Long> createCountQuery(Object[] values) {
@@ -22,9 +22,11 @@
import java.util.Arrays;
import java.util.List;
+import javax.persistence.LockModeType;
import javax.persistence.QueryHint;
import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
@@ -94,6 +96,17 @@ public boolean isModifyingQuery() {
}
/**
+ * Returns the {@link LockModeType} to be used for the query.
+ *
+ * @return
+ */
+ LockModeType getLockModeType() {
+
+ Lock annotation = method.getAnnotation(Lock.class);
+ return (LockModeType) AnnotationUtils.getValue(annotation);
+ }
+
+ /**
* Returns whether the potentially configured {@link QueryHint}s shall be applied when triggering the count query for
* pagination.
*
@@ -40,6 +40,7 @@
private final EntityManager entityManager;
private final QueryExtractor extractor;
+ private final LockModeRepositoryPostProcessor lockModePostProcessor;
/**
* Creates a new {@link JpaRepositoryFactory}.
@@ -51,6 +52,9 @@ public JpaRepositoryFactory(EntityManager entityManager) {
Assert.notNull(entityManager);
this.entityManager = entityManager;
this.extractor = PersistenceProvider.fromEntityManager(entityManager);
+ this.lockModePostProcessor = LockModeRepositoryPostProcessor.INSTANCE;
+
+ addRepositoryProxyPostProcessor(lockModePostProcessor);
}
/*
@@ -82,11 +86,11 @@ protected Object getTargetRepository(RepositoryMetadata metadata) {
Class<?> repositoryInterface = metadata.getRepositoryInterface();
JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainClass());
- if (isQueryDslExecutor(repositoryInterface)) {
- return new QueryDslJpaRepository(entityInformation, entityManager);
- } else {
- return new SimpleJpaRepository(entityInformation, entityManager);
- }
+ SimpleJpaRepository<?, ?> repo = isQueryDslExecutor(repositoryInterface) ? new QueryDslJpaRepository(
+ entityInformation, entityManager) : new SimpleJpaRepository(entityInformation, entityManager);
+ repo.setLockMetadataProvider(lockModePostProcessor.getLockMetadataProvider());
+
+ return repo;
}
/*
@@ -21,7 +21,7 @@
import javax.persistence.PersistenceContext;
import org.springframework.beans.factory.FactoryBean;
-import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
import org.springframework.util.Assert;
@@ -34,7 +34,7 @@
* @author Eberhard Wolff
* @param <T> the type of the repository
*/
-public class JpaRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable> extends
+public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends
TransactionalRepositoryFactoryBeanSupport<T, S, ID> {
private EntityManager entityManager;
@@ -58,7 +58,6 @@ public void setEntityManager(EntityManager entityManager) {
*/
@Override
protected RepositoryFactorySupport doCreateRepositoryFactory() {
-
return createRepositoryFactory(entityManager);
}
@@ -69,7 +68,6 @@ protected RepositoryFactorySupport doCreateRepositoryFactory() {
* @return
*/
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
-
return new JpaRepositoryFactory(entityManager);
}
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.support;
+
+import javax.persistence.LockModeType;
+
+/**
+ * Interface to abstract {@link LockMetadataProvider} that provide the {@link LockModeType} to be used for query
+ * execution.
+ *
+ * @author Oliver Gierke
+ */
+public interface LockMetadataProvider {
+
+ /**
+ * Returns the {@link LockModeType} to be used.
+ *
+ * @return
+ */
+ LockModeType getLockModeType();
+}
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.support;
+
+import java.lang.reflect.Method;
+
+import javax.persistence.LockModeType;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.data.jpa.repository.Lock;
+import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * {@link RepositoryProxyPostProcessor} that sets up interceptors to read {@link LockModeType} information from the
+ * invoked method. This is necessary to allow redeclaration of CRUD methods in repository interfaces and configure
+ * locking information on them.
+ *
+ * @author Oliver Gierke
+ */
+public enum LockModeRepositoryPostProcessor implements RepositoryProxyPostProcessor {
+
+ INSTANCE;
+
+ private static final Object NULL = new Object();
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.core.support.RepositoryProxyPostProcessor#postProcess(org.springframework.aop.framework.ProxyFactory)
+ */
+ public void postProcess(ProxyFactory factory) {
+
+ factory.addAdvice(ExposeInvocationInterceptor.INSTANCE);
+ factory.addAdvice(LockModePopulatingMethodIntercceptor.INSTANCE);
+ }
+
+ /**
+ * Returns the {@link LockMetadataProvider} to lookup the lock information captured by the interceptors.
+ *
+ * @return
+ */
+ public LockMetadataProvider getLockMetadataProvider() {
+ return ThreadBoundLockMetadata.INSTANCE;
+ }
+
+ /**
+ * {@link MethodInterceptor} to inspect the currently invoked {@link Method} for a {@link Lock} annotation. Will bind
+ * the found information to a {@link TransactionSynchronizationManager} for later lookup.
+ *
+ * @see ThreadBoundLockMetadata
+ * @author Oliver Gierke
+ */
+ private static enum LockModePopulatingMethodIntercceptor implements MethodInterceptor {
+
+ INSTANCE;
+
+ /*
+ * (non-Javadoc)
+ * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+ */
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+
+ Method method = invocation.getMethod();
+ Object lockInfo = TransactionSynchronizationManager.getResource(method);
+
+ if (lockInfo != null) {
+ return invocation.proceed();
+ }
+
+ Lock annotation = method.getAnnotation(Lock.class);
+ LockModeType lockMode = (LockModeType) AnnotationUtils.getValue(annotation);
+ TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);
+
+ return invocation.proceed();
+ }
+ }
+
+ /**
+ * {@link LockMetadataProvider} that looks up locking metadata from the {@link TransactionSynchronizationManager}
+ * using the current method invocation as key.
+ *
+ * @author Oliver Gierke
+ */
+ private static enum ThreadBoundLockMetadata implements LockMetadataProvider {
+
+ INSTANCE;
+
+ public LockModeType getLockModeType() {
+
+ MethodInvocation invocation = ExposeInvocationInterceptor.currentInvocation();
+ Object lockModeType = TransactionSynchronizationManager.getResource(invocation.getMethod());
+
+ return lockModeType == NULL ? null : (LockModeType) lockModeType;
+ }
+ }
+}
Oops, something went wrong.

2 comments on commit c915077

@senendds

Excuse me if this is not the proper way of reporting this (I'm not familiar with git):

JPAQueryMethod.getLockModeType should check if annotation is null.

@olivergierke
Member

The null handling is taken care of by AnnotationUtils already.

Please sign in to comment.