diff --git a/src/docbkx/reference/jpa.xml b/src/docbkx/reference/jpa.xml index bec4b8585..28fcb53dd 100644 --- a/src/docbkx/reference/jpa.xml +++ b/src/docbkx/reference/jpa.xml @@ -763,6 +763,42 @@ public interface UserRepository extends JpaRepository<User, Long> { +
+ Locking + + To specify the lock mode to be used the + @Lock annotation can be used on query + methods: + + + Defining lock metadata on query methods + + interface UserRepository extends Repository<User, Long> { + + // Plain query method + @Lock(LockModeType.READ) + List<User> findByLastname(String lastname); +} + + + This method declaration will cause the query being triggered to be + equipped with the LockModeType + READ. You can also define locking for CRUD methods by + redeclaring them in your repository interface and adding the + @Lock annotation: + + + Defining lock metadata on CRUD methods + + interface UserRepository extends Repository<User, Long> { + + // Redeclaration of a CRUD method + @Lock(LockModeType.READ); + List<User> findAll(); +} + +
+
Auditing diff --git a/src/main/java/org/springframework/data/jpa/repository/Lock.java b/src/main/java/org/springframework/data/jpa/repository/Lock.java new file mode 100644 index 000000000..45f6ea48b --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/Lock.java @@ -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(); +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 99bea4a8a..6ee857102 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -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 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { 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 createCountQuery(Object[] values) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index 7ccad43b8..73cf1454a 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -22,9 +22,11 @@ import java.util.ArrayList; 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; @@ -93,6 +95,17 @@ public class JpaQueryMethod extends QueryMethod { return result; } + /** + * 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. diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index bfcfa5799..9a30ac689 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -40,6 +40,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { private final EntityManager entityManager; private final QueryExtractor extractor; + private final LockModeRepositoryPostProcessor lockModePostProcessor; /** * Creates a new {@link JpaRepositoryFactory}. @@ -51,6 +52,9 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { Assert.notNull(entityManager); this.entityManager = entityManager; this.extractor = PersistenceProvider.fromEntityManager(entityManager); + this.lockModePostProcessor = LockModeRepositoryPostProcessor.INSTANCE; + + addRepositoryProxyPostProcessor(lockModePostProcessor); } /* @@ -82,11 +86,11 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { Class repositoryInterface = metadata.getRepositoryInterface(); JpaEntityInformation 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; } /* diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java index 469cf9936..24eb98ff9 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java @@ -21,7 +21,7 @@ import javax.persistence.EntityManager; 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 @@ import org.springframework.util.Assert; * @author Eberhard Wolff * @param the type of the repository */ -public class JpaRepositoryFactoryBean, S, ID extends Serializable> extends +public class JpaRepositoryFactoryBean, S, ID extends Serializable> extends TransactionalRepositoryFactoryBeanSupport { private EntityManager entityManager; @@ -58,7 +58,6 @@ public class JpaRepositoryFactoryBean, S, ID exte */ @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { - return createRepositoryFactory(entityManager); } @@ -69,7 +68,6 @@ public class JpaRepositoryFactoryBean, S, ID exte * @return */ protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { - return new JpaRepositoryFactory(entityManager); } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/LockMetadataProvider.java b/src/main/java/org/springframework/data/jpa/repository/support/LockMetadataProvider.java new file mode 100644 index 000000000..4de971291 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/LockMetadataProvider.java @@ -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(); +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/support/LockModeRepositoryPostProcessor.java b/src/main/java/org/springframework/data/jpa/repository/support/LockModeRepositoryPostProcessor.java new file mode 100644 index 000000000..5350b7ff8 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/LockModeRepositoryPostProcessor.java @@ -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; + } + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 65d019e19..16f9af5e8 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import javax.persistence.EntityManager; +import javax.persistence.LockModeType; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; @@ -59,6 +60,8 @@ public class SimpleJpaRepository implements JpaRepos private final EntityManager em; private final PersistenceProvider provider; + private LockMetadataProvider lockMetadataProvider; + /** * Creates a new {@link SimpleJpaRepository} to manage objects of the given {@link JpaEntityInformation}. * @@ -85,6 +88,16 @@ public class SimpleJpaRepository implements JpaRepos this(JpaEntityInformationSupport.getMetadata(domainClass, em), em); } + /** + * Configures a custom {@link LockMetadataProvider} to be used to detect {@link LockModeType}s to be applied to + * queries. + * + * @param lockMetadataProvider + */ + public void setLockMetadataProvider(LockMetadataProvider lockMetadataProvider) { + this.lockMetadataProvider = lockMetadataProvider; + } + private Class getDomainClass() { return entityInformation.getJavaType(); } @@ -367,17 +380,8 @@ public class SimpleJpaRepository implements JpaRepos */ private TypedQuery getQuery(Specification spec, Pageable pageable) { - CriteriaBuilder builder = em.getCriteriaBuilder(); - CriteriaQuery query = builder.createQuery(getDomainClass()); - - Root root = applySpecificationToCriteria(spec, query); - query.select(root); - - if (pageable != null) { - query.orderBy(toOrders(pageable.getSort(), root, builder)); - } - - return em.createQuery(query); + Sort sort = pageable == null ? null : pageable.getSort(); + return getQuery(spec, sort); } /** @@ -399,7 +403,7 @@ public class SimpleJpaRepository implements JpaRepos query.orderBy(toOrders(sort, root, builder)); } - return em.createQuery(query); + return applyLockMode(em.createQuery(query)); } /** @@ -444,4 +448,10 @@ public class SimpleJpaRepository implements JpaRepos return root; } + + private TypedQuery applyLockMode(TypedQuery query) { + + LockModeType type = lockMetadataProvider == null ? null : lockMetadataProvider.getLockModeType(); + return type == null ? query : query.setLockMode(type); + } } diff --git a/src/test/java/org/springframework/data/jpa/repository/LockIntegrationTest.java b/src/test/java/org/springframework/data/jpa/repository/LockIntegrationTest.java new file mode 100644 index 000000000..1fca7e6db --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/LockIntegrationTest.java @@ -0,0 +1,83 @@ +/* + * 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 static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.io.Serializable; + +import javax.persistence.EntityManager; +import javax.persistence.LockModeType; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.repository.sample.RoleRepository; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; + +/** + * Integratio test for lock support. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class LockIntegrationTest { + + @Mock + EntityManager em; + @Mock + CriteriaBuilder builder; + @Mock + CriteriaQuery criteriaQuery; + @Mock + JpaEntityInformation information; + @Mock + TypedQuery query; + + /** + * @see DATAJPA-73 + */ + @Test + public void usesLockInformationAnnotatedAtRedeclaredMethod() { + + when(information.getJavaType()).thenReturn(Role.class); + when(em.getCriteriaBuilder()).thenReturn(builder); + when(builder.createQuery(Role.class)).thenReturn(criteriaQuery); + when(em.createQuery(criteriaQuery)).thenReturn(query); + when(query.setLockMode(any(LockModeType.class))).thenReturn(query); + + JpaRepositoryFactory factory = new JpaRepositoryFactory(em) { + @Override + @SuppressWarnings("unchecked") + public JpaEntityInformation getEntityInformation(Class domainClass) { + return (JpaEntityInformation) information; + } + }; + + RoleRepository repository = factory.getRepository(RoleRepository.class); + + repository.findAll(); + + verify(query).setLockMode(LockModeType.READ); + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java b/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java index e1f61397c..7228c3a09 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java @@ -15,12 +15,14 @@ */ package org.springframework.data.jpa.repository.query; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; import java.util.List; import javax.persistence.EntityManager; +import javax.persistence.LockModeType; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.persistence.QueryHint; @@ -30,6 +32,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.jpa.repository.support.PersistenceProvider; import org.springframework.data.repository.Repository; @@ -101,6 +104,24 @@ public class AbstractJpaQueryTests { verify(result, never()).setHint("bar", "foo"); } + /** + * @see DATAJPA-73 + */ + @Test + public void addsLockingModeToQueryObject() throws Exception { + + when(query.setLockMode(any(LockModeType.class))).thenReturn(query); + + Method method = SampleRepository.class.getMethod("findOneLocked", Integer.class); + QueryExtractor provider = PersistenceProvider.fromEntityManager(em); + JpaQueryMethod queryMethod = new JpaQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), + provider); + + AbstractJpaQuery jpaQuery = new DummyJpaQuery(queryMethod, em); + Query result = jpaQuery.createQuery(new Object[] { Integer.valueOf(1) }); + verify(result).setLockMode(LockModeType.PESSIMISTIC_WRITE); + } + interface SampleRepository extends Repository { @QueryHints({ @QueryHint(name = "foo", value = "bar") }) @@ -108,6 +129,10 @@ public class AbstractJpaQueryTests { @QueryHints(value = { @QueryHint(name = "bar", value = "foo") }, forCounting = false) List findByFirstname(String firstname); + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @org.springframework.data.jpa.repository.Query("select u from User u where u.id = ?1") + List findOneLocked(Integer primaryKey); } class DummyJpaQuery extends AbstractJpaQuery { diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java index 1fbe51430..e40bfc899 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.*; import java.lang.reflect.Method; import java.util.List; +import javax.persistence.LockModeType; import javax.persistence.QueryHint; import org.junit.Before; @@ -32,6 +33,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.sample.User; +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.sample.UserRepository; @@ -56,7 +58,7 @@ public class JpaQueryMethodUnitTests { RepositoryMetadata metadata; Method repositoryMethod, invalidReturnType, pageableAndSort, pageableTwice, sortableTwice, modifyingMethod, - nativeQuery, namedQuery; + nativeQuery, namedQuery, findWithLockMethod; /** * @throws Exception @@ -73,8 +75,10 @@ public class JpaQueryMethodUnitTests { sortableTwice = InvalidRepository.class.getMethod(METHOD_NAME, String.class, Sort.class, Sort.class); modifyingMethod = UserRepository.class.getMethod("renameAllUsersTo", String.class); - nativeQuery = InvalidRepository.class.getMethod("findByLastname", String.class); - namedQuery = InvalidRepository.class.getMethod("findByNamedQuery"); + nativeQuery = ValidRepository.class.getMethod("findByLastname", String.class); + namedQuery = ValidRepository.class.getMethod("findByNamedQuery"); + + findWithLockMethod = ValidRepository.class.getMethod("findOneLocked", Integer.class); } @Test @@ -214,6 +218,18 @@ public class JpaQueryMethodUnitTests { assertThat(queryMethod.getNamedQueryName(), is("Foo.bar")); } + /** + * @see DATAJPA-73 + */ + @Test + public void discoversLockModeCorrectly() throws Exception { + + JpaQueryMethod method = new JpaQueryMethod(findWithLockMethod, metadata, extractor); + LockModeType lockMode = method.getLockModeType(); + + assertEquals(LockModeType.PESSIMISTIC_WRITE, lockMode); + } + /** * Interface to define invalid repository methods for testing. * @@ -244,11 +260,18 @@ public class JpaQueryMethodUnitTests { // Modifying and Sort is not allowed @Modifying void updateMethod(String firstname, Sort sort); + } + + static interface ValidRepository { @Query(value = "query", nativeQuery = true) List findByLastname(String lastname); @Query(name = "Foo.bar") List findByNamedQuery(); + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("select u from User u where u.id = ?1") + List findOneLocked(Integer primaryKey); } } diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java index e40ab9f25..07a82281b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java @@ -15,7 +15,10 @@ */ package org.springframework.data.jpa.repository.sample; +import javax.persistence.LockModeType; + import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.repository.Lock; import org.springframework.data.repository.CrudRepository; /** @@ -25,4 +28,10 @@ import org.springframework.data.repository.CrudRepository; */ public interface RoleRepository extends CrudRepository { + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#findAll() + */ + @Lock(LockModeType.READ) + public Iterable findAll(); } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java index d14ea82ea..996a6c93b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java @@ -70,6 +70,9 @@ public class SimpleJpaRepositoryUnitTests { repo = new SimpleJpaRepository(information, em); } + /** + * @see DATAJPA-124 + */ @Test public void doesNotActuallyRetrieveObjectsForPageableOutOfRange() {