DATAJPA-455 - Add support for stored procedure backed repository methods.
Added support for JPA 2.1 stored procedures mapping for repository methods. Introduced @Procedure annotation for declaring stored procedure metadata. Repository methods backed by stored procedures are represented as a StoredProcedureJpaQuery that is constructed by JpaQueryFactory#fromProcedureAnnotation. Enhanced JpaQueryLookupStrategy to support @Procedure. Introduced StoredProcedureAttribute to capture the derived configuration for a stored procedure query. The stored procedure needed for the tests is created via the schema-stored-procedures.sql script that is picked up by the customized DataSource definition in infrastructure.xml. Added new test class UserRepositoryStoredProcedureTests to be able to exclude those tests for OpenJPA. OpenJPA tests don't work with hsqldb 2.x and use hsqldb 1.x instead which doesn't support stored procedures. Original pull request: #80.
This commit is contained in:
committed by
Oliver Gierke
parent
e32e724fc8
commit
09535813c2
@@ -34,6 +34,10 @@ import javax.persistence.NamedAttributeNode;
|
||||
import javax.persistence.NamedEntityGraph;
|
||||
import javax.persistence.NamedEntityGraphs;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.NamedStoredProcedureQueries;
|
||||
import javax.persistence.NamedStoredProcedureQuery;
|
||||
import javax.persistence.ParameterMode;
|
||||
import javax.persistence.StoredProcedureParameter;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
@@ -50,6 +54,14 @@ import javax.persistence.TemporalType;
|
||||
@NamedEntityGraph(name = "User.detail", attributeNodes = { @NamedAttributeNode("roles"),
|
||||
@NamedAttributeNode("manager"), @NamedAttributeNode("colleagues") }) })
|
||||
@NamedQuery(name = "User.findByEmailAddress", query = "SELECT u FROM User u WHERE u.emailAddress = ?1")
|
||||
@NamedStoredProcedureQueries({ //
|
||||
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = {
|
||||
@StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
|
||||
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) }) //
|
||||
})
|
||||
@NamedStoredProcedureQuery(name = "User.plus1IO", procedureName = "plus1inout", parameters = {
|
||||
@StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
|
||||
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
|
||||
public class User {
|
||||
|
||||
@Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2014 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.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.data.jpa.support.EntityManagerTestUtils.*;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.ParameterMode;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.StoredProcedureQuery;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.repository.sample.UserRepository;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration tests for JPA 2.1 stored procedure support.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.6
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:application-context.xml")
|
||||
@Transactional
|
||||
public class UserRepositoryStoredProcedureTests {
|
||||
|
||||
@Autowired UserRepository repository;
|
||||
@PersistenceContext EntityManager em;
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void callProcedureWithInAndOutParameters() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
assertThat(repository.plus1inout(1), is(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void callProcedureExplicitNameWithInAndOutParameters() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
assertThat(repository.explicitlyNamedPlus1inout(1), is(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void entityAnnotatedCustomNamedProcedurePlus1IO() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
assertThat(repository.entityAnnotatedCustomNamedProcedurePlus1IO(1), is(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void plainJpa21() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
StoredProcedureQuery proc = em.createStoredProcedureQuery("plus1inout");
|
||||
proc.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN);
|
||||
proc.registerStoredProcedureParameter(2, Integer.class, ParameterMode.OUT);
|
||||
|
||||
proc.setParameter(1, 1);
|
||||
proc.execute();
|
||||
|
||||
assertThat(proc.getOutputParameterValue(2), is((Object) 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void plainJpa21_entityAnnotatedCustomNamedProcedurePlus1IO() {
|
||||
|
||||
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
StoredProcedureQuery proc = em.createNamedStoredProcedureQuery("User.plus1IO");
|
||||
|
||||
proc.setParameter("arg", 1);
|
||||
proc.execute();
|
||||
|
||||
assertThat(proc.getOutputParameterValue("res"), is((Object) 2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2014 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.query;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.object.IsCompatibleType.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.junit.Before;
|
||||
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.User;
|
||||
import org.springframework.data.jpa.repository.support.JpaEntityMetadata;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link StoredProcedureAttributeSource}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.6
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StoredProcedureAttributeSourceUnitTests {
|
||||
|
||||
StoredProcedureAttributeSource creator;
|
||||
@Mock JpaEntityMetadata<User> entityMetadata;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
creator = StoredProcedureAttributeSource.INSTANCE;
|
||||
|
||||
when(entityMetadata.getJavaType()).thenReturn(User.class);
|
||||
when(entityMetadata.getEntityName()).thenReturn("User");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateStoredProcedureAttributesFromProcedureMethodWithImplicitProcedureName() {
|
||||
|
||||
StoredProcedureAttributes attr = creator.createFrom(method("plus1inout", Integer.class), entityMetadata);
|
||||
|
||||
assertThat(attr.getProcedureName(), is("plus1inout"));
|
||||
assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class)));
|
||||
assertThat(attr.getOutputParameterName(), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateStoredProcedureAttributesFromProcedureMethodWithExplictName() {
|
||||
|
||||
StoredProcedureAttributes attr = creator.createFrom(method("explicitlyNamedPlus1inout", Integer.class),
|
||||
entityMetadata);
|
||||
|
||||
assertThat(attr.getProcedureName(), is("plus1inout"));
|
||||
assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class)));
|
||||
assertThat(attr.getOutputParameterName(), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateStoredProcedureAttributesFromProcedureMethodWithExplictProcedureNameValue() {
|
||||
|
||||
StoredProcedureAttributes attr = creator.createFrom(method("explicitlyNamedPlus1inout", Integer.class),
|
||||
entityMetadata);
|
||||
|
||||
assertThat(attr.getProcedureName(), is("plus1inout"));
|
||||
assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class)));
|
||||
assertThat(attr.getOutputParameterName(), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateStoredProcedureAttributesFromProcedureMethodWithExplictProcedureNameAlias() {
|
||||
|
||||
StoredProcedureAttributes attr = creator.createFrom(
|
||||
method("explicitPlus1inoutViaProcedureNameAlias", Integer.class), entityMetadata);
|
||||
|
||||
assertThat(attr.getProcedureName(), is("plus1inout"));
|
||||
assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class)));
|
||||
assertThat(attr.getOutputParameterName(), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateStoredProcedureAttributesFromProcedureMethodBackedWithExplicitlyNamedProcedure() {
|
||||
|
||||
StoredProcedureAttributes attr = creator.createFrom(
|
||||
method("entityAnnotatedCustomNamedProcedurePlus1IO", Integer.class), entityMetadata);
|
||||
|
||||
assertThat(attr.getProcedureName(), is("User.plus1IO"));
|
||||
assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class)));
|
||||
assertThat(attr.getOutputParameterName(), is("res"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateStoredProcedureAttributesFromProcedureMethodBackedWithImplicitlyNamedProcedure() {
|
||||
|
||||
StoredProcedureAttributes attr = creator.createFrom(method("plus1", Integer.class), entityMetadata);
|
||||
|
||||
assertThat(attr.getProcedureName(), is("User.plus1"));
|
||||
assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class)));
|
||||
assertThat(attr.getOutputParameterName(), is("res"));
|
||||
}
|
||||
|
||||
private static Method method(String name, Class<?>... paramTypes) {
|
||||
return ReflectionUtils.findMethod(DummyRepository.class, name, paramTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
static interface DummyRepository {
|
||||
|
||||
/**
|
||||
* Explicitly mapped to a procedure with name "plus1inout" in database.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure("plus1inout")
|
||||
Integer explicitlyNamedPlus1inout(Integer arg);
|
||||
|
||||
/**
|
||||
* Explicitly mapped to a procedure with name "plus1inout" in database via alias.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure(procedureName = "plus1inout")
|
||||
Integer explicitPlus1inoutViaProcedureNameAlias(Integer arg);
|
||||
|
||||
/**
|
||||
* Implicitly mapped to a procedure with name "plus1inout" in database via alias.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure
|
||||
Integer plus1inout(Integer arg);
|
||||
|
||||
/**
|
||||
* Explicitly mapped to named stored procedure "User.plus1IO" in {@link EntityManager}.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure(name = "User.plus1IO")
|
||||
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
|
||||
|
||||
/**
|
||||
* Implicitly mapped to named stored procedure "User.plus1" in {@link EntityManager}.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure
|
||||
Integer plus1(@Param("arg") Integer arg);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.QueryHint;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -33,6 +34,7 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryHints;
|
||||
import org.springframework.data.jpa.repository.query.Procedure;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -334,4 +336,36 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
|
||||
*/
|
||||
// @Query(value = "select u.binaryData from User u where u.id = :id")
|
||||
// byte[] findBinaryDataByIdJpaQl(@Param("id") Integer id);
|
||||
|
||||
/**
|
||||
* Explicitly mapped to a procedure with name "plus1inout" in database.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure("plus1inout")
|
||||
Integer explicitlyNamedPlus1inout(Integer arg);
|
||||
|
||||
/**
|
||||
* Implicitly mapped to a procedure with name "plus1inout" in database via alias.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure(procedureName = "plus1inout")
|
||||
Integer plus1inout(Integer arg);
|
||||
|
||||
/**
|
||||
* Explicitly mapped to named stored procedure "User.plus1IO" in {@link EntityManager}.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure(name = "User.plus1IO")
|
||||
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
|
||||
|
||||
/**
|
||||
* Implicitly mapped to named stored procedure "User.plus1" in {@link EntityManager}.
|
||||
*
|
||||
* @see DATAJPA-455
|
||||
*/
|
||||
@Procedure
|
||||
Integer plus1(@Param("arg") Integer arg);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2014 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.support;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Utility class with {@link EntityManager} related helper methods.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public abstract class EntityManagerTestUtils {
|
||||
|
||||
private EntityManagerTestUtils() {}
|
||||
|
||||
public static boolean currentEntityManagerIsAJpa21EntityManager(EntityManager em) {
|
||||
return ReflectionUtils.findMethod(((org.springframework.orm.jpa.EntityManagerProxy) em).getTargetEntityManager()
|
||||
.getClass(), "getEntityGraph", String.class) != null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user