Spring data jpa calling stored procedure with lots of parameters - java

Is it possible to map an object to all the parameters inside a NamedStoredProcedureQuery? I need to call a stored procedure that has over 40 parameters that it takes in. The following code becomes really long and hard to read:
#NamedStoredProcedureQuery(
name="SomeSPName",
procedureName="SomeSPName",
resultClasses={ MyEntity.class },
parameters = {
#StoredProcedureParameter(name="PACKAGE", type=String.class, mode=ParameterMode.IN),
#StoredProcedureParameter(name="APPNM", type=String.class, mode=ParameterMode.IN),
// 40 more required parameters here...
)
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query
#Procedure(name="SomeSPName")
long getResult(#Param("PACKAGE") String package, #Param("APPNM") String appnm, /* The rest of the params here */);
}
So my question is can I replace the arguments inside getResult with an object that has all the getters and setters for the parameters and Hibernate will automatically assign the parameters correctly. For example:
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query
#Procedure(name="SomeSPName")
long getResult(MyObject params);
}
Here, MyObject will have fields corresponding to each of the StoredProcedureParameter defined up top. Hopefully, this makes sense. I just don't want to put all the 40+ parameters in the getResult definition. I'm using DB2 if it makes a difference. Any help would be appreciated!

EntityManager entityManager;
public Long getResult(Params params) {
StoredProcedureQuery storedProcedure = entityManager
.createStoredProcedureQuery("SomeSPName", Long.class)
.registerStoredProcedureParameter("PACKAGE", String.class, ParameterMode.IN)
.registerStoredProcedureParameter("APPNM", String.class, ParameterMode.IN)
//...
.setParameter("PACKAGE", params.getPackage())
.setParameter("APPNM", params.getAppNm())
//...
;
return storedProcedure.getSingleResult();
}

Related

How to pass complete query as String in repository method as argument and use with the #Query Annotation?

I prepared a query in serviceImplementation ,After that I passed this query as method argument of
presentDaySummarySmscWise(String finalQuery) Repository method.
In Repository Interface ,I passed this query with #Query Annotaion like shown in code.
strong text
//ServiceImplementation class
#Service
public class ErrorCodeServiceImpl implements ErrorCodeService {
#Autowired
ErrorCodeRepository errorCodeRepo;
#Override
public List<Object[]> errorCodeDefaultSummary() {
String finalQuery="select smsc,vf_reason_code from reason_code_report where
log_date='2021-05-27'";
List<Object[]> result = errorCodeRepo.presentDaySummarySmscWise(finalQuery);
return result;
}
strong text
//Repository
#Repository
public interface ErrorCodeRepository extends JpaRepository<ErrorCodeReportEntity, ErrorCodeReportKeys>{
#Query(value=":query",nativeQuery=true)
List<Object[]> presentDaySummarySmscWise(String query);
}
You cannot replace arbitrary fragments of the query using a query parameter. Only very specific sections of the query allow parameters.
If you need dynamic queries, Specifications or QueryDSL is what you want.
I believe you need to add #Param("query") into your argument in the repository.
List<Object[]> presentDaySummarySmscWise(#Param("query") String query);

Any way to use the `#Procedure` annotation without an entity?

So I'd like a "Void-Repository" through which to gain access to stored procedures that are not necessarily operation on entities.
#Repository
public interface StoredProceduresRepository extends CrudRepository<Void, Long> {
#Procedure("my_answer_giver")
String getMyAnswer(#Param("input") String input);
}
But that does, of course, not work because the CrudRepository expects Void to be an entity.
Is there a way to use the #Procedure annotation without having to create dummy entities or am I stuck with an implemented class that makes use of the EntityManager to query via prepared statements?
Because let's be honest, that's fugly:
#Repository
public class StoredProceduresRepository {
#PersistenceContext
EntityManager em;
public String getMyAnswer(String input) {
Query myAnswerGiver = em
.createStoredProcedureQuery("my_answer_giver")
.registerStoredProcedureParameter("input", String.class, ParameterMode.IN)
.setParameter("input", input);
Object result = ((Object[]) myAnswerGiver.getSingleResult())[0];
return (String) result;
}
}
If it is ok for you you can use any Entity you have, in place of this Void. The Entity provided there should not matter.
public interface StoredProceduresRepository extends JpaRepository<SomeUnrelatedEntity, Long> {
#Procedure("my_answer_giver")
String getMyAnswer(#Param("input") String input);
}
I have been using it like this with database views.

Spring Data JPA: Generate dynamic query

I have an entity that hold some logic data :
#Entity
public class Person {
private Long id.
private String name;
private int age;
private String address;
...
}
I create my Spring data interface
#Repository
public interface CardInventoryRepository extends JpaRepository<Person , Long> {
}
My purpose is to create a dynamic query based on the exist values of my entity for example
if the name is null the query is :
select * from Person p Where p.age=12 AND p.address="adress.."
When the address is null the query should be :
select * from Person p Where p.age=12 AND p.name="ALI"
I want to extract data using only the non empty fields ?
is there any solution suing spring data for building dynamic queries ?
Thanks in advance
Based on Spring doc https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example
Query by Example (QBE) is a user-friendly querying technique with a
simple interface. It allows dynamic query creation and does not
require you to write queries that contain field names. In fact, Query
by Example does not require you to write queries by using
store-specific query languages at all.
DEFINITION:
An Example takes a data object (usually the entity object or a sub-type of it) and a specification how to match properties. You can use Query by Example with JPA
Repositories.
To do so, let your repository interface extend QueryByExampleExecutor<T>, for example:
public interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}
Here are the available methods in QueryByExampleExecutor :
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
USAGES:
Example<Person> example = Example.of(new Person("Jon", "Snow"));
repo.findAll(example);
ExampleMatcher matcher = ExampleMatcher.matching().
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
Example<Person> example = Example.of(new Person("Jon", "Snow"), matcher);
repo.count(example);
MORE INFO
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example
https://github.com/spring-projects/spring-data-examples/tree/master/jpa/query-by-example
Spring Data JPA: Query by Example?
Yes, please take a look at the QueryDSL support for Spring Data. Your use case can be implemented via a Predicate. In a nutshell, you have to create a predicate in which you would pass the non null fields, and then pass that predicate to a findAll method that takes a Predicate as argument. Your repository interface also has to extend QueryDslPredicateExecutor
Need to extend repository from JpaSpecificationExecutor
#Repository
#Transactional
public interface EmployeeDAO extends CrudRepository<Employee,Long>,JpaSpecificationExecutor<Employee>{
}
Use specification and predicate like below
public List<Employee> findByCriteria(String employeeName,String employeeRole){
return employeeDAO.findAll(new Specification<Employee>() {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if(employeeName!=null) {
predicates.add(criteriaBuilder.and(criteriaBuilder.like(root.get("employeeName"), "%"+employeeName+"%")));
}
if(employeeRole!=null){
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("employeeRole"), employeeRole)));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});
}

Spring Data query method with IN keyword using a List of enums as parameter

I'm using a Spring Data JPA (4.3.5) repository and a query method using IN keyword clause with a List<Enum> field as parameter. Problem is that it's not working as I expect.
Given an entity like:
#Entity
#Table(name = "R_REPRESENTACIO")
public class Representacio {
#Enumerated(EnumType.STRING)
private Estat estat;
...
//getters and setters
...
}
With that SQL declaration:
CREATE TABLE R_REPRESENTACIO (
UUID NUMBER(19) NOT NULL,
...
ESTAT VARCHAR2(255) NULL,
...
);
Estat is an Enum class like:
public enum Estat {
VALIDA,
PENDENT_VALIDACIO,
PENDENT_DOCUMENTACIO,
...
}
And a JPA repository like:
public interface RepresentacioRepository extends JpaRepository<Representacio, Long> {
List<Representacio> findAllByEstatIn(List<Estat> estats);
}
When I run (integration test class):
List<Estat> estats =
Arrays.asList(Estat.VALIDA,Estat.PENDENT_DOCUMENTACIO,Estat.PENDENT_VALIDACIO);
List<cat.aoc.representa.domain.entity.representacio.Representacio> allByEstatIn = representacioRepository.findAllByEstatIn(estats);
SQL generated is (in an in memory H2 DB):
2018-08-01 12:30:48.534--ServerSession(1175154004)--Connection(384887832)--Thread(Thread[main,5,main])--SELECT UUID, .... FROM R_REPRESENTACIO WHERE (ESTAT IN ((?,?,?)))
bind => [VALIDA, PENDENT_DOCUMENTACIO, PENDENT_VALIDACIO]
No SQL exception is thrown and zero results are returned.
But this SHOULD return 1 result as this (equivalent) SQL returns:
SELECT count(*) FROM R_REPRESENTACIO WHERE ESTAT IN ('VALIDA','PENDENT_DOCUMENTACIO','PENDENT_VALIDACIO');
COUNT(*)
----------
1
The unique difference I'm able to see is how i wrap the IN clause arguments between '' (that column is a VARCHAR).
I don't know why generated SQL from the JPA repository is not returning results.
(I've also tried findAllByEstatIsIn(List<Estat> estats) with same zero results returned).
Any suggestion/explanation?
PS: Workarounded (not happy with) using
List<Representacio> findAllByEstatOrEstatOrEstat(Estat estat, Estat estat2, Estat estat3);
but that is uggly and wrong in many ways...
I suggest a parameter with type List<String> and adding the converter String -> Enum so Spring will be able to convert that. So, basically:
List<Representacio> findByEstatIn(List<String> estats);
2.
#Configuration
public class ConverterConfiguration extends RepositoryRestConfigurerAdapter {
#Autowired
private EstatsConverter estatsConverter;
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(estatsConverter);
super.configureConversionService(conversionService);
}
3.
#Component
public class EstatsConverter implements Converter<String, Estat> {
#Override
public Estat convert(String source) {
return Estat.fromString(source);
}
}
I have no idea if that's gonna work, but I remember doing something similar, only in MongoDB. Let me know if you try that.
It is very simple with the spring data JPA and JPQL:
#Repository
public interface RepresentacioRepository extends JpaRepository<Representacio, Long> {
#Query("select r from Representacio r where r.estat in :estat2")
List<Representacio> findByEnumEstat(#Param("estat2") List<Estat> estatList);
}

RepositoryItemReader doesn't find methods with arguments

I'm setting up an ItemRepositoryReader for the reader in a springBatch step.
public ItemReader<EcheanceEntity> reader(){
RepositoryItemReader<EcheanceEntity> reader = new RepositoryItemReader<EcheanceEntity>();
reader.setRepository(echeanceRepository);
reader.setMethodName("findById");
List parameters = new ArrayList();
long a = 0;
parameters.add(a);
reader.setArguments(parameters);
Map<String, Direction> sort = new HashMap<String, Direction>();
sort.put("id", Direction.ASC);
reader.setSort(sort);
return reader;
}
this is the line in my repository.
public interface EcheanceRepository extends JpaRepository<EcheanceEntity, Long>{
public EcheanceEntity findById(long id);
#Override
public List<EcheanceEntity> findAll();
If a use the method findAll(), so without any arguments, it works fine. But if I use the method findById(long id) I get "no such method exception, findById(java.lang.Long, org.springframework.data.domain.PageRequest)" from the ItemRepositoryReader. The method works fine without using the reader when I'm testing it by using immediately the repository.
Thank you.
In a case of using the RepositoryItemReader#setMethodName method you need to add an argument of type Pageable in your repository method signature at last position:
public interface EcheanceRepository extends JpaRepository<EcheanceEntity, Long> {
public Page<EcheanceEntity> findById(long id, Pageable pageable);
...
You can find description of it in the documentation: http://docs.spring.io/spring-batch/apidocs/org/springframework/batch/item/data/RepositoryItemReader.html#setMethodName-java.lang.String-
public void setMethodName(java.lang.String methodName)
Specifies what method on the repository to call. This method must
take Pageable as the last argument.

Categories