I need to invoke a stored procedure using the JPA. The stored procedure operates on multiple tables and return some of the columns from these tables.
Tried with the #Procedure it doesn't seem to work, always the stored procedure is not found in this case.
Directly calling the procedure using native query was successful, but with this approach, I am need to map the result returned to List of an Object.
My implementation in the repository looks like below,
#Query(value = "EXECUTE dbs.multitable_Test :inputObj", nativeQuery = true)
List<sp> multitable_Test(#Param("inputObj")String inputObj);
The result returned from the stored procedure needs to be mapped to the sp class.
How can this be achieved while we have multiple tables response in the single result set?
Already tried with the attributeConvert from
this link, still getting the below exception.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type
Any help with this is appreciated.
Firstly, this is not really the use case for procedure. Procedure is meant to modify data on the database without any return value, then you could use:
#Procedure(procedureName = "procedure_name")
void procedure(); // notice void
You should rather use a function using create function syntax. Function can modify data and return the result.
Secondly if you want to map it to some class, I see two solutions (using EntityManager):
Using ResultTransformer:
entityManager.createNativeQuery(
"select * from function_name(:parameter)"
)
.setParameter("parameter", parameter)
.unwrap(org.hibernate.query.NativeQuery.class)
.setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
return new Sp(tuple[0]);
}
#Override
public List transformList(List collection) {
return collection;
}
})
.getResultList();
Note that ResultTransformer is deprecated, but is so powerful, it will not be removed until there is a sensible replacement, see the note from hibernate developer.
Using ResultSetMapping. Place the proper annotation over an entity:
#SqlResultSetMapping(
name = "sp_mapping",
classes = #ConstructorResult(
targetClass = Sp.class,
columns = {
#ColumnResult(name = "attribute", type = Long.class)
})
)
And invoke the function using the mapping as parameter:
entityManager.createNativeQuery(
"select * " +
"from function_name(:parameter);",
"sp_mapping"
)
.setParameter("parameter", parameter)
.getResultList();
Related
I created a PostgreSQL function to get some data from an array of UUID.
i.e:
create function journey_statistics(journey_ids uuid[])
returns TABLE(project_id uuid, project_name character varying,...)
language plpgsql
If I run the next sql statement it returns the expected data:
select * from journey_statistics(array['0f36c7a5-04eb-4329-8e93-a13625a4ffa6'::uuid, 'bc10ee72-7b7f-4bbd-a70a-75477b484d58'::uuid])
But then, when I implement it on Java and run it. I am getting the next error:
o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: function journey_statistics(uuid, uuid) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
This is the native query I am using to call it. And I have used same in other similar functions with no errors. So I can not understand what is the issue or what I am doing wrong.
#Query(value = "select cast(project_id as varchar(36)) as projectId, project_name as projectName, cast(project_leader as varchar(36)) as projectLeader" +
" from journey_statistics(:uuids)", nativeQuery = true)
Collection<JourneyStatisticsView> getJourneyStatisticsById(Collection<UUID> uuids);
I have tried to cast data to an array but it looks it is transform to a record[] array.
But more strange is if I pass a Collection of Strings and then I try to cast them I get
function journey_statistics(character varying, character varying) does not exist
Any help appreciated, thank you.
I found a workaround to pass the Collection into the function.
It is not as sophisticated as I wanted. But at least it works.
Basically, I have created a new Repository to use the entity manager and create my own sql statement.
#Repository
public class JourneyStatisticsCustomRepositoryImpl implements JourneyStatisticCustomRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List getJourneyStatisticsByIds(Collection<UUID> uuids) {
final Collection<String> formattedUUID = uuids.stream().map(uuid -> "cast('" + uuid + "' as uuid)").collect(Collectors.toSet());
final String joinedUUIDs = Strings.join(formattedUUID.iterator(), ',');
return entityManager.createNativeQuery("select cast(project_id as varchar(36)) as projectId, ..." +
"... from journey_statistics(array[" + joinedUUIDs +"])", JourneyStatisticsView.class).getResultList();
}
This sends the proper collection to the function and returns the data as expected.
Hope this could help someone else with similar issues.
I am using Hibernate to execute my query, in admin panel i am getting correct result but while using in Hibernate it is not giving any result.
Dao layer -
#Query("select new com.eventila.pms.entity.ReferenceLead(projectId,count(lm)) from LeadMaster lm where lm.vendorId= ?1 and lm.source = 'share' group by lm.projectId")
List<ReferenceLead> getReferenceByUser(String userId);
Pojo -
#lombok.Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ReferenceLead {
String projectId;
Long referenceLead;
Long count;
protected ReferenceLead(){}
public ReferenceLead(String projectId,Long count) {
this.projectId=projectId;
this.count=count;
}
}
After executing this i am getting a empty list.
Please help me out.
In your select query return the fields without calling new constructor:
#Query("select projectId, count(lm) from LeadMaster lm where lm.vendorId = ?1 and lm.source = 'share' group by lm.projectId")
List<ReferenceLead> getReferenceByUser(String userId);
Hibernate will instantiate the object using these fields. Also, add #Entity annotation to your ReferenceLead class.
'source' is the keyword in SQL.
It is a keyword used in MERGE. i.e. WHEN NOT MATCHED BY SOURCE.
The word MATCHED also exhibits the same behaviour in that it gets highlighted grey in the editor.
Neither of these are reserved keywords though so if used as an identifier they do not need to be delimited (unless you find the syntax highlighting distracting).
How can we select specific fields in Spring Data Mongo. I tried the following but I got cast exception from Foo to String.
Using #Query
#Query(value="{path : ?0}", fields="{path : 0}")
String findPathByPath(String path);
Non #Query
String findPathByPath(String path);
Here is the document model
#Document(collection = "foo")
public class Foo {
String name, path;
…
}
MongoDB only returns JSON documents for standard queries. What you'd like to see can be achieved by still returning a List<Foo>. The fields property in #Query will cause only the fields set to 1 being returned.
#Query(value="{ path : ?0}", fields="{ path : 0 }")
List<Foo> findByPath(String path);
We usually recommend introducing a dedicted DTO for that so that you prevent the partially filled Foo instance from being handed to save(…) in turn.
Another option is using the aggreation framework but that's more involved.
You can use
Query query = new Query();
query.fields().include("path");
You can use
public interface PersonRepository extends MongoRepository<Person, String>
#Query(value="{ 'firstname' : ?0 }",fields="{ 'firstname' : 1, 'lastname' : 1}")
List<Person> findByThePersonsFirstname(String firstname);
}
More information in spring data documentation
You can use below query to get specific fields.
#Query(fields="{path : 1}")
Foo findPathByPath(String path);
Records present in DB
{
"name" : "name2",
"path" : "path2"
},
{
"name" : "name3",
"path" : "path3"
}
Below query will return Foo object if path=Path3
{
"name": null,
"path": "path3"
}
we need to specify required fields with fieldName:1 and if don't require then specify it with 0.
I found this question while trying to get the value of a field from a specific object in my collection. From what my research shows, Mongo doesn't provide a way to natively return just a specific field's value from an object. (Disappointing since it seems pretty basic to be able to return just a specific value from a field like I would do in SQL or JSONPath).
To get around this, I wrote the following method using Spring MongoDB with Java 11:
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.MongoTemplate; //not used, just showing which type the template is
import java.util.Arrays;
import static java.util.Objects.requireNonNull;
/**
* Use this method to get a specific field from an object saved in Mongo. The objectId will be
* the id of the object to fetch, and the fieldValueToReturn will be the field to return.
*
* #return the value of the provided field-path converted to the class type provided
*/
public <T> T getFieldValueById(String objectId, String fieldValueToReturn, String collectionName, Class<T> classTypeToReturn) {
var query = new Query().addCriteria(Criteria.where("_id").is(objectId));
query.fields().include(fieldValueToReturn);
var result = mongoTemplate.findOne(query, org.bson.Document.class, collectionName);
requireNonNull(result, "Did not find any documents with id '" + objectId + "' in collection: " + collectionName);
return result.getEmbedded(Arrays.asList(fieldValueToReturn.split("\\.")), classTypeToReturn);
}
The getEmbedded call allows us to get the value of the nested field within the returned Bson document.
To use the method, just call it like this:
getFieldValueById("A1234", "field.nestedfield.nestedfield", "collectionName", String.class);
Hopefully this helps out someone else looking on how to do this.
As a side note, I'm not sure how to extend this to return a list of objects - if I get to that dilemma and solve it, I will try to update this answer. I'm also not sure if this is slower than running a Mongo aggregate query - I haven't tried running any performance comparisons between the two methods.
EDIT 2022-09-30: To return a list of a custom Pojo, it looks like you'll have to use an aggregate query via spring-data-mongodb. Also it seems basic queries are faster than aggregate queries, so use basic queries where possible.
You can directly pass your json query with #Query annotation, for example:
#Query("{ 'firstname' : 'john' }")
Here is the link to all json based queries in Spring Data MongoDb - https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongodb.repositories.queries.json-based
You can do the following.
In your repository, you have the method:
String findPathByPath(String path);
If the document looks like this (below), and you want to only return path
#Document(collection = "foo")
public class Foo {
String name;
String path;
String type;
…
}
Then create a Projection interface, e.g.
#Projection(name = "flattenedFoo", types = Foo.class)
public interface FlattenedInvoice {
String getPath(); // This returns the path value in Foo class
}
You can use the getter methods to get the fields from Foo that you are interested in.
Then in your get request, you would have to specify the projectionName.
e.g. with (#Resource path)
#RestResource(path = "findByPath", rel = "findByPath")
String findPathByPath(String path);
You could then say (In a get request):
..../findByPath?path=target_path&projection=flattenedFoo
this would then return a json with only the fields specifies in FlattenedFoo interface.
My understanding is, that with Spring data JPA I cannot have a query method to fetch all rows where a column equals a given non-null method parameter and use the same method to fetch all rows where this column is NULL when the method parameter is null.
Is that correct?
So I have to distinguish this in my JAVA code and I must use a separate query method explicitly asking for null values, like in the example below?
// Query methods
List<Something> findByParameter(Parameter parameter);
List<Something> findByParameterIsNull();
...
List<Something> result = new ArrayList<>();
if (parameter == null)
result = findByParameterIsNull();
else
result = findByParameter(parameter);
That's bad, if I have 4 parameters which could be null and would have to code 16 different query methods.
You are right.
A request has been made to support better handling of null parameters.
https://jira.spring.io/browse/DATAJPA-121
In your case, i would advise you to write your repository implementation and to use a custom CriteriaQuery to handle your case.
Also you can use the #Query annotation with the is null syntax :
#Query("[...] where :parameter is null"
public List<Something> getSomethingWithNullParameter();
EDIT
Since Spring data jpa 2.0, spring now supports #Nullable annotation. This can be helpful to handle null parameters passed.
From the documentation :
#Nullable – to be used on a parameter or return value that can be null.
i found something...if u put the parameter in the jpa method like this
#Param("value") String value,
then it can be null and in the query you will have this condition:
(table.value = :value OR :value IS NULL)
if the value is null it will automatically return true and if is not null, it will search that value in the table.
It seems Query by Example might be what you need.
Query by Example is a new feature in Spring Data (since version Hopper, out April 2016), which allows one to create simple dynamic queries with a code like this
Person person = new Person();
person.setFirstname("Dave");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
personRepository.count(example);
personRepository.findOne(example);
personRepository.findAll(example);
Methods count/findOne/findAll that take an instance of org.springframework.data.domain.Example as a parameter (and some of them also take sorting/pagination parameters) are coming from org.springframework.data.repository.query.QueryByExampleExecutor<T> interface, which is extended by org.springframework.data.jpa.repository.JpaRepository<T, ID extends Serializable> interface.
In short, all JpaRepository instances now have these methods.
Today as of Jun 2018, by looking at https://jira.spring.io/browse/DATAJPA-121, the query will automatically form is null if your parameter is null.
I did that in my project, it is true:
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.0.7.RELEASE'
--
public interface AccountDao extends CrudRepository<T, ID> {
//this can accept null and it will become isNull
public List<MyAccount> findByEmail(String email);
}
if parameter is null:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email is null
if parameter is not null:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email=?
11:02:41.623 [qtp1507181879-72] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [testing#hotmail.com]
Then it comes to an interesting question, some developers want better control to ignore the parameter in query if it is null, this is still being under investigating in https://jira.spring.io/browse/DATAJPA-209.
In my case membershipNumber is nullable, and I have handled it this way. This will handle all the cases where table.membershipNumber is null too.
#Query(value = "SELECT pr FROM ABCTable pr " +
"WHERE LOWER(pr.xyz) = LOWER(:xyz) " +
"and LOWER(pr.subscriptionReference) = LOWER(:subscriptionReference) " +
"and pr.billId = :billId " +
"and ((pr.membershipNumber = :membershipId) or (pr.membershipNumber = null and :membershipId = null))")
List<PaymentRequest> getSomething (#Param("xyz") String xyz,
#Param("subscriptionReference") String subscriptionReference,
#Param("billId") Integer billId,
#Param("membershipId") String membershipNumber);
While this has been answered and the accepted answer is relevant to the current question but there is another way to handle your null parameters in a JpaRespository. Posting this here as this can be leveraged when someone wants to query by ignoring fields when null and have dynamic query built.
The below code sample should demonstrate the same
public class User{
private String firstName;
private String lastName;
}
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User,Long>{
public Page<AppUser> findAll(Specification<AppUser> user,Pageable page);
public default Page<AppUser> findAll(User user,Pageable page){
return findAll(search(user),page);
}
static Specification<User> search(User entity) {
return (root, cq, cb) -> {
//To ensure we start with a predicate
Predicate predicate = cb.isTrue(cb.literal(true));
if(entity.getFirstName() != null && !entity.getFirstName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("firstName")), "%"+entity.getFirstName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
if(entity.getLastName() != null && !entity.getLastName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("lastName")), "%"+entity.getLastName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
return predicate;
}
}
}
I was able to apply IS NULL appropriately in case of null input using below workaround.
#Query("SELECT c FROM ConfigRLLOAContent c WHERE ((:suffixId IS NOT NULL AND c.suffixId = :suffixId) OR (:suffixId IS NULL AND c.suffixId IS NULL))")
Optional<ConfigRLLOAContent> findByRatableUnitId(#Param("suffixId") String suffixId);
Above approach will apply filters only when suffixId is non-null,
else, IS NULL filter will be applied.
There's also an issue raised on github, to which introduction of #NullMeans is proposed here.
I had the same issue with similar task - one parameter in the query was optional, so to get rid of this error, I managed to use the following query with 2 casts:
#Query(value = "select distinct name from table "
+ "where coalesce(cast(table.field_optional as text) = cast(?1 as text), true) "
+ "and lower(table.non_optional_field) like ?2 "
+ "limit ?3", nativeQuery = true)
List<String> method(String optionalParam, String param, int limit);
This coalesce part would transform into simple 'true' if optionalParam is null
I am currently in the process of learning the Java Spring Framework, and I am having difficulty understanding why the following query is failing to return any results from the database.
I am ultimately trying to create a where method in my OffersDAO class that allows my to query on a specific field, for a specific value.
public List<Offer> where(String field, String value){
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("field", field);
params.addValue("value", value);
String sql = "select * from offers where :field = :value";
return jdbc.query(sql, params, new RowMapper<Offer>(){
public Offer mapRow(ResultSet rs, int arg1) throws SQLException {
Offer offer = new Offer();
offer.setId(rs.getInt("id"));
offer.setName(rs.getString("name"));
offer.setText(rs.getString("text"));
offer.setEmail(rs.getString("email"));
return offer;
}
});
}
I am able to successfully query the database for results when I specify the field explicitly, as follows:
String sql = "select * from offers where name = :value";
Obviously there is something wrong with specifying the field name dynamically. My guess is it is most likely due to the fact that the field key is being inserted as a mysql string (with ''), when in fact mysql expects a column name for the :field placeholder.
My questions are as follows:
Is there a way to accomplish what I am attempting to do above, using the jdbc NamedParameterJdbcTemplate class?
If I cannot accomplish the above, by what means can I?
Thank you
Edit: No exceptions are thrown. In the case when I am attempting to supply the column name, a empty result set is returned.
You can't specify the field name in a parameter - only the field value. Since you know the DB schema when you're writing the code, this shouldn't be much of a problem.
What about include all possible fields in the filter but restricting their usage by field name param. Like this:
select * from offers where
('name'=:field and name = :value)
OR
('field2'=:field and field2 = :value)
OR
('field3'=:field and field3 = :value)
I don't know how You can implement it with spring (I mean use variable column names) but I can suggest to use the following principle.
Keep your query like template:
String sql = "select * from offers where ##field = :value";
And every time before execution replace ##value parameter with the column You want.
And then You are gone.