I have a resultset class:
Public Class AResultSet
Implements IEnumerable(Of ConcreteResult)
Private _list As List(Of ConcreteResult)
Public Sub New()
_list = New List(Of ConcreteResult)
End Sub
Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of ConcreteResult) Implements System.Collections.Generic.IEnumerable(Of ConcreteResult).GetEnumerator
Return _list.GetEnumerator
End Function
Public Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return _list.GetEnumerator
End Function
End Class
and a linq query:
Dim res As AResultSet = (From pk In testPackages, _
pp In pk.PackagePriceCollection _
Select New ConcreteResult(pk, pp))
But I get a cast error. So if I change the
Dim res As AResultSet
To:
Dim res As IEnumerable(Of ConcreteResult)
It works. But I want to cast the linq query result to the type AResultSet, which is also an IEnumerable(Of ConrecteResult).
Or am I doing something wrong here?
The result of calling Select is not an AResultSet, which is why the cast will fail. Nothing in the query knows that you want to create a AResultSet. Just because the result and AResultSet both implement the same interface doesn't mean they're the same type.
You could create an instance of AResultSet from the results, however:
Dim query = (From pk In testPackages, _
pp In pk.PackagePriceCollection _
Select New ConcreteResult(pk, pp))
Dim res as AResultSet = new AResultSet(query.ToList)
While it's unclear to me why you might want to create a class to duplicate the functionality of List(Of T), what you are trying to do is not directly possible. You should either create an implicit (Widening) user defined cast operator in your class or create a constructor that takes an IEnumerable(Of ConcreteResult) and uses that to fill the private list field.
Public Class AResultSet
Implements IEnumerable(Of ConcreteResult)
Private list As List(Of ConcreteResult)
Private Sub New(l As List(Of ConcreteResult))
list = l
End Sub
Public Shared Widening Operator CType(seq As IEnumerable(Of ConcreteResult)) As AResultSet
Return New AResultset(seq.ToList())
End Sub
...
End Class
Related
I am following this guide for calling my stored procedure. I searched around for other resources for an example where IN and OUT parameters are of type table but couldn't find one.
I am having error: Cannot convert SQL type TABLE to Java type java.lang.Object
There are no ways to know what exactly caused the problem. But I'm giving an example of a stored function that actually worked for me. Stored functions could be an alternative approach to stored procedures which need to return DTOs.
#Repository
public interface CustomRepository extends CrudRepository<YourObject, UUID> {
#Query(value = "SELECT * from your_stored_procedure(:key)", nativeQuery = true)
List<YourObject> findAlldata(#Param("key") String key);
}
#Service
public class CustomService {
#Autowired
private CustomRepository customRepository;
public List<YourObject> getAllData(String key) {
List<YourObject> yourObjects = this.customRepository.findAlldata(key);
}
}
And here is the code for the stored function in Postgres. The return objects should have a similar structure for both the repository and the procedure.
CREATE FUNCTION public.your_stored_procedure(key text) RETURNS TABLE(id uuid, name character varying)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY SELECT
id,
name
FROM public.your_table;
END;
$$;
I am calling Stored Proc from Spring Data JPA :
Procedure is:
create or replace procedure GET_LATEST_GC (arg1 IN VARCHAR2, res1 OUT VARCHAR2, res2 OUT VARCHAR2)
AS
BEGIN
DELETE FROM GC_T WHERE id = arg1;
COMMIT;
BEGIN
SELECT gc.NAME, s.SIP INTO res1, res2
FROM GC_T gc, STAFF_T s WHERE s.id = gc.id
AND START_TIME = (SELECT MAX(START_TIME) FROM GC_T);
EXCEPTION
WHEN others THEN
res1 := '';
END;
END;
Spring Data JPA code
//Repository
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(procedureName="GET_LATEST_GC")
Object[] plus1(#Param("arg1") String arg1);
}
//Entity
#Data
#Entity
#NamedStoredProcedureQuery(name = "GET_LATEST_GC",
procedureName = "GET_LATEST_GC", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "arg1", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "res1", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "res2", type = String.class)})
#Table(schema = "abc", name = "GC_T")
public class GcT implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private String id;
#Column(name = "NAME")
private String name;
}
//Call
Object[] activeGCInfo =activeDao.plus1(arg);
Procedure is accepting one parameter and I am also passing 1 argument.Then also I am getting this error:
Hibernate: {call GET_LATEST_GC(?,?)}
ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ORA-06550: line 1, column 7:\nPLS-00306: wrong number or types of arguments in call to 'GET_LATEST_GC'\nORA-06550: line 1, column 7:\nPL/SQL: Statement ignored\n
Please let me know where I am doing wrong.
Thank you
Update- Tried to add OUT params also as per suggestion
//Repo
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(procedureName="GET_LATEST_GC")
Object[] plus1(#Param("arg1") String arg1,#Param("res1") String res1,#Param("res2") String res2);
}
//Call
Object[] activeGCInfo =activeDao.plus1(arg,"","");
I am sending three args but it is showing me 4 args in error:
Hibernate: {call GET_LATEST_GC(?,?,?,?)} SqlExceptionHelper - ORA-06550: line 1, column 7:\nPLS-00306: wrong number or types of
arguments in call to 'GET_LATEST_GC'\nORA-06550: line 1, column
7:\nPL/SQL: Statement ignored\n
Try changing the result from Object[] to Map<String, Object, along with referencing the proc name with name instead of procedureName. Based on the error, I'm not sure that it will fix it. Spring Data JPA does expect a Map as the return value for multiple output params, so each output param can be found as the key in that Map. But I think the main error is that procedureName maps directly to the db, but name= will map to the correct Entity
//Repo
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(name="GET_LATEST_GC")
Map<String, Object> plus1(#Param("arg1") String arg1);
}
//Call
Map<String, Object> activeGCInfo =activeDao.plus1(arg);
Here's what happened:
you declared a procedure with 3 parameters: 1 in and 2 out
you said: "Procedure is accepting one parameter and I am also passing 1 argument"
that was the 1st procedure's parameter (arg1 IN)
it results in "PLS-00306: wrong number or types of arguments"
Of course it does; you need to provide 2 more arguments (datatype should be able to accept VARCHAR2 values returned by the procedure).
I need to find the table records which will come between the dates passed by the user. I am trying to write a specification for this but it is showing me compile time error as below :
The method between(Expression<? extends Y>, Expression<? extends Y>, Expression<? extends Y>)
in the type CriteriaBuilder
is not applicable for the arguments (Expression<Date>, Object, Object)
I have tried search on the various forums but didn't able to get how to solve this issue,may be I am doing something wrong. Please help me on this.
Specification Class
public class ScheduleClassSpecification implements Specification<ScheduleClassInformation> {
private SearchCriteria criteria;
#Override
public Predicate toPredicate(Root<ScheduleClassInformation> root,
CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Expression<String> expression;
Predicate predicate =null;
if((criteria.getKey().equalsIgnoreCase("student"))){
expression = root.join("course").join("student").get("student");
predicate = criteriaBuilder.equal(expression, criteria.getValue());
}else if(criteria.getKey().equalsIgnoreCase("startDate") || criteria.getKey().equalsIgnoreCase("endDate")){
predicate = criteriaBuilder.between
(root.<Date>get(criteria.getKey()).as(java.util.Date.class),
criteria.getValue(),
criteria.getValue()); // Compile Time Error on this line
}
return predicate;
}
Criteria Class
public class SearchCriteria {
private String key;
private String operation;
private Object value;}
Service For Extracting the record from Repository
if(!Utility.isNull(Id)){
idSpec = new ScheduleClassSpecification(new SearchCriteria("Student",":",Id));
}
Page<SCOutput> listreturn = scRepo.findAll(Specification.where(idSpec), SCOutput.class,new PageRequest(0, 100));
As the compiler error tells you need an Expression.
You can easily do that by replacing criteria.getValue() with criteriaBuilder.literal(criteria.getValue())
I have an entity model, for simplification purposes let's say it looks like this :
public class Results {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long firstUser;
private Long secondUser;
private Double average;
private Double median;
private Double score;
}
This is my ResultsService Implementation:
public class ResultsServiceImpl implements ResultsService{
#Autowired
private CalculateDataRepository calculateDataRepository;;
#Autowired
private ResultsService resultsService;
Results results=new Results();
public void Average(Long id1, Long id2){
UserData firstClient = calculateDataRepository.findOne(id1);
userData secondClient = calculateDataRepository.findOne(id2);
clientId = firstClient.getClient().getId();
secondId = secondClient.getClient().getId();
Double average=(firstClient.getA()+secondClient.getA())/2;
results.setAverage(average);
}
public void Score(Long id1, Long id2){
SurveyData firstClient = surveyDataRepository.findOne(id1);
SurveyData secondClient = surveyDataRepository.findOne(id2);
clientId = firstClient.getClient().getId();
secondId = secondClient.getClient().getId();
Double average=(firstClient.getB()+secondClient.getB());
results.setScore(average);
results.setFirstUser(clientId );
results.setSecondUser(secondId );
resultsService.save(results);
}
....
I tried declaring Results results=new Results(); inside every method, but when I save them they get saved in different rows, instead of the same one.
How do I hold the reference so that when I call the setter of a field in one function, it's in the same row as the setter of a field in the other function.
To keep the problem focused, I tried to avoid showing the implementation of calculateDataRepository which is just the repository of an entity where some results are saved for different users.
The Results Method has no foreign field reference nor a reference from somewhere else, as there are fields firstUser and secondUser which I set from one of the methods;
Thank you.
Edit:
Results results=resultsService.findByFirstUserAndSecondUser(clientId, secondId);
if(results==null) {
results= new Results();
// Store to db ?
}
results.setAverage();
resultsService.save(results);
Actually you need a method in ResultsRepository
Results findByFirstAndSecond(Long first, Long second);
In the each Average and Score methods (BTW Java naming convention requires to have method names start from lowercase letter) call the findByFirstAndSecond(id1, id2)
If the method returns null (no such result) create a new instance and save in the DB (INSERT). If some Results is returned store the info there and save changes in DB (UPDATE).
First experiments with Spring Data and MongoDB were great. Now I've got the following structure (simplified):
public class Letter {
#Id
private String id;
private List<Section> sections;
}
public class Section {
private String id;
private String content;
}
Loading and saving entire Letter objects/documents works like a charm. (I use ObjectId to generate unique IDs for the Section.id field.)
Letter letter1 = mongoTemplate.findById(id, Letter.class)
mongoTemplate.insert(letter2);
mongoTemplate.save(letter3);
As documents are big (200K) and sometimes only sub-parts are needed by the application: Is there a possibility to query for a sub-document (section), modify and save it?
I'd like to implement a method like
Section s = findLetterSection(letterId, sectionId);
s.setText("blubb");
replaceLetterSection(letterId, sectionId, s);
And of course methods like:
addLetterSection(letterId, s); // add after last section
insertLetterSection(letterId, sectionId, s); // insert before given section
deleteLetterSection(letterId, sectionId); // delete given section
I see that the last three methods are somewhat "strange", i.e. loading the entire document, modifying the collection and saving it again may be the better approach from an object-oriented point of view; but the first use case ("navigating" to a sub-document/sub-object and working in the scope of this object) seems natural.
I think MongoDB can update sub-documents, but can SpringData be used for object mapping? Thanks for any pointers.
I figured out the following approach for slicing and loading only one subobject. Does it seem ok? I am aware of problems with concurrent modifications.
Query query1 = Query.query(Criteria.where("_id").is(instance));
query1.fields().include("sections._id");
LetterInstance letter1 = mongoTemplate.findOne(query1, LetterInstance.class);
LetterSection emptySection = letter1.findSectionById(sectionId);
int index = letter1.getSections().indexOf(emptySection);
Query query2 = Query.query(Criteria.where("_id").is(instance));
query2.fields().include("sections").slice("sections", index, 1);
LetterInstance letter2 = mongoTemplate.findOne(query2, LetterInstance.class);
LetterSection section = letter2.getSections().get(0);
This is an alternative solution loading all sections, but omitting the other (large) fields.
Query query = Query.query(Criteria.where("_id").is(instance));
query.fields().include("sections");
LetterInstance letter = mongoTemplate.findOne(query, LetterInstance.class);
LetterSection section = letter.findSectionById(sectionId);
This is the code I use for storing only a single collection element:
MongoConverter converter = mongoTemplate.getConverter();
DBObject newSectionRec = (DBObject)converter.convertToMongoType(newSection);
Query query = Query.query(Criteria.where("_id").is(instance).and("sections._id").is(new ObjectId(newSection.getSectionId())));
Update update = new Update().set("sections.$", newSectionRec);
mongoTemplate.updateFirst(query, update, LetterInstance.class);
It is nice to see how Spring Data can be used with "partial results" from MongoDB.
Any comments highly appreciated!
I think Matthias Wuttke's answer is great, for anyone looking for a generic version of his answer see code below:
#Service
public class MongoUtils {
#Autowired
private MongoTemplate mongo;
public <D, N extends Domain> N findNestedDocument(Class<D> docClass, String collectionName, UUID outerId, UUID innerId,
Function<D, List<N>> collectionGetter) {
// get index of subdocument in array
Query query = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query.fields().include(collectionName + "._id");
D obj = mongo.findOne(query, docClass);
if (obj == null) {
return null;
}
List<UUID> itemIds = collectionGetter.apply(obj).stream().map(N::getId).collect(Collectors.toList());
int index = itemIds.indexOf(innerId);
if (index == -1) {
return null;
}
// retrieve subdocument at index using slice operator
Query query2 = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query2.fields().include(collectionName).slice(collectionName, index, 1);
D obj2 = mongo.findOne(query2, docClass);
if (obj2 == null) {
return null;
}
return collectionGetter.apply(obj2).get(0);
}
public void removeNestedDocument(UUID outerId, UUID innerId, String collectionName, Class<?> outerClass) {
Update update = new Update();
update.pull(collectionName, new Query(Criteria.where("_id").is(innerId)));
mongo.updateFirst(new Query(Criteria.where("_id").is(outerId)), update, outerClass);
}
}
This could for example be called using
mongoUtils.findNestedDocument(Shop.class, "items", shopId, itemId, Shop::getItems);
mongoUtils.removeNestedDocument(shopId, itemId, "items", Shop.class);
The Domain interface looks like this:
public interface Domain {
UUID getId();
}
Notice: If the nested document's constructor contains elements with primitive datatype, it is important for the nested document to have a default (empty) constructor, which may be protected, in order for the class to be instantiatable with null arguments.
Solution
Thats my solution for this problem:
The object should be updated
#Getter
#Setter
#Document(collection = "projectchild")
public class ProjectChild {
#Id
private String _id;
private String name;
private String code;
#Field("desc")
private String description;
private String startDate;
private String endDate;
#Field("cost")
private long estimatedCost;
private List<String> countryList;
private List<Task> tasks;
#Version
private Long version;
}
Coding the Solution
public Mono<ProjectChild> UpdateCritTemplChild(
String id, String idch, String ownername) {
Query query = new Query();
query.addCriteria(Criteria.where("_id")
.is(id)); // find the parent
query.addCriteria(Criteria.where("tasks._id")
.is(idch)); // find the child which will be changed
Update update = new Update();
update.set("tasks.$.ownername", ownername); // change the field inside the child that must be updated
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
)
;
}