Spring data neo4j custom #QueryResult doesn't recognize enums - java

I'm trying to create a custom #QueryResult with multiple fields from different nodes, but it seems like the query result mechanism is unable to map enum correctly.
This is an example that I made for this scenario. I created a basic enum as:
public enum MyEnum{
SOMETHING, SOMETHING_ELSE
}
And spring data neo4j repository method with the query:
#Query("Match (people:People)-[:LIVES_IN]->(country:Country) " +
"RETURN people.enum")
List<WithEnumQueryResult> findPeople();
when I trigger it throws an exception:
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate pl.degath.WithEnumQueryResult using constructor pl.degath.WithEnumQueryResult(pl.degath.MyEnum) with arguments SOMETHING_ELSE
I was able to see through trial and error that:
#Builder
#Getter
#QueryResult
public class WithEnumQueryResult{
private final MyEnum enum; //this one I would like to have, but throws error
private final String enum; //this returns my enum as String (doesn't throw error)
private final People people; //this one has correct enum as a property of people (doesn't throw error)
}
I tried also add some #Converter e.g. #Convert(EnumStringConverter.class) annotation in front of my enum property, but it didn't help out.
Any ideas on how can I make my QueryResult recognize enums?
EDIT:
As mentioned in a comment from the accepted answer, it seems like enums require no-args constructors, so I had to change my immutable object into the:
#Builder
#Getter
#NoArgsConstructor(access = AccessLevel.PRIVATE)
#AllArgsConstructor
#QueryResult
public class WithEnumQueryResult{
private MyEnum enum; //enum is visible now!
}

Below are my entity classes and repository which perfectly works fine.
#Data
#QueryResult
public class PersonResponse {
private Long id;
private String name;
private int age;
private City livesAt;
private Test test;
private List<Person> friends;
}
public enum Test {
A, B
}
Repository method
#Query("MATCH (pr:Person) where ID(pr)=$id return ID(pr) as id, pr.test as test, pr.name as name, pr.age as age")
public PersonResponse getPerson(Long id);
Result:
{
"id": 68,
"name": "Alex",
"age": 24,
"test": "A",
}

Related

Spring Data JPA mapping nested entities

I'm a little bit confused about using projections in Spring Data JPA.
I wanted to optimize my queries by requesting only needed columns (preferably) in one query, and I thought that using projections is a good idea. But it seems that projection with nested projection becomes open and requests all columns and further nesting is impossible.
I've tried to find a solution with #Query (cannot find how to map nested lists), #EntityGraph (cannot find how to request only specified column) and #SqlResultSetMapping (cannot find how to make mapping nested lists), but it hasn't worked for me.
Is there any solution except receiving List<Object[]> and manually mapping?
I have the next entities classes (simplified for the question):
public class TestAttempt{
private Long id;
private User targetUser;
private Test test;
}
public class Test{
private Long id;
private String name;
private Set<Question> questions;
}
public class Question{
private Long id;
private String name;
private Test test;
}
And I wanted to write something like this (it can be just TestAttempt with null in unused fields):
public interface TestAttemptList {
Long getId();
Test getTest();
interface Test {
String getName();
List<Question> getQuestions();
interface Question {
String getName();
}
}
}
public interface TestAttemptRepository extends JpaRepository<TestAttempt, Long> {
List<TestAttemptList> getAllByTargetUserId(Long targetUserId);
}
And in result get something like this:
{
id: 1,
test: {
name: test1,
questions: [{
name: quest1
}, {
name: quest2
}]
}
}
Ive done something like this... You'll have your repository interfaces which will extend CrudRepository et. al. with the full objects (TestAttempt etc) You define your projections separately. The projection interfaces can contain other projection interfaces (TestAttemptSummary can contain a TestSummary) When the projection interface is used within the given repository the defined methods are applied to the object type the repository is configured for. Something like this.
public interface TestAttemptSummary {
Long getId();
TestSummary getTest();
}
public interface TestSummary {
String getName();
List<QuestionSummary> getQuestions();
}
public interface QuestionSummary {
String getName();
}
public interface TestAttemptRepository extends CrudRepository<TestAttempt, Long> {
TestAttemptSummary getTestAttemptSummary();
}

Mapstruct Invoking implicitly other mapper with multiple parameter

Given the following classes and a mapper that takes mulitple source arguments
(I use lombok to keep source as short as possible.)
#Getter
#Setter
public class MySourceOne {
private String src1;
}
#Getter
#Setter
public class MySourceTwo {
private String src2;
}
#Getter
#Setter
public class MyTargetObject {
private String prop1;
private String prop2;
}
#Mapper
public interface MyTargetObjectMapper {
#Mapping(target="prop1", source="a")
#Mapping(target="prop2", source="b")
public MyTargetObject mapMyObject(String a, String b);
}
#Getter
#Setter
public class MyComplexTargetObject {
private MyTargetObject myTargetObject;
}
I am trying to create a mapper for MyComplexTargetObject that will invoke implicitly the MyTargetObjectMapper .
But the "source" won't allow to map multiple parameter like this
#Mapper(uses= {MyTargetObjectMapper.class})
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject", source="one.src1, two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
So I am trying to use an expression="..." instead of source, but nothing works so far.
Any thoughts a clean way to do this without calling the MyTargetObjectMapper in a concrete method?
MapStruct does not support selection of methods with multiple sources.
However: you can do target nesting to do this.
#Mapper
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject.prop1", source="one.src1" )
#Mapping(target="myTargetObject.prop2", source="two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
And let MapStruct take care of generating the mapper. Note: you can still use a MyComplexTargetObjectMapper to do single source to target to achieve this.

Spring Data Mongodb Convert Document Falsely

I have a document structure which has some generic class. For writing to mongodb everything is fine. But when reading documents from mongodb spring data converts document into object falsely. It converts a subdocument with another type. Both types (actual subcollection type and falsely converted type) are inherit from same abstract class.
Model Classes:(getter setters are generated by lombok )
#Data
public abstract class CandidateInfo {
private String _id;
}
#Data
public class CandidateInfoContainer<E extends CandidateInfo> {
private String _id;
private int commentCount = 0;
#Valid
private List<E> values = new ArrayList<>();
}
#Data
public class Responsibility extends CandidateInfo {
#NotNull
private String responsibilityId;
#ReadOnlyProperty
private String responsibilityText;
}
#Data
public class Experience extends CandidateInfo {
#Valid
private CandidateInfoContainer<Responsibility> responsibilities;
}
#Document
#JsonInclude(JsonInclude.Include.NON_NULL)
#Data
public class Candidate {
private String _id;
#Valid
private CandidateInfoContainer<Experience> experiences;
}
And if you create a mongoRepository like below:
#Repository
public interface CandidateRepository extends MongoRepository<Candidate,String>{
}
And use it like:
#Autowired
private CandidateRepository candidateRepository;
Candidate candidate = candidateRepository.findOne("documentId");
Then spring data mongo mapping converter creates candidates.experiences.responsibilities.values list as Experince list but it should be Responsibility list.
You can find a demo project in this link and more information about the issue. Can anyone point out what is wrong? Otherwise i have to write my own converter(demo has one)
If there is any unclear thing, you can ask.
Thanks.
I open an issue in spring-data-mongo here. Appareantly I caught a bug! Thanks everyone

Spring-data : select distinct of a "sub class"

Here is my model:
#Entity
Class SubClass {
...
}
#Entity
Class MainClass {
...
private String name;
#ManyToOne
private SubClass subClass;
...
}
With the use of jpa data, I would like to have a select distinct of the SubClass.
I tried to do that with a repository:
public interface MainClassRepository extends JpaRepository<MainClass, Integer> {
public List<SubClass> findDistinctSubClassByName(String name);
}
The error I have as a result is the following:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [MainClass] to type [SubClass]
I tried to create a Converter and add it as an entity, but Spring doesn't know it.
How can I do what I want? Either by registering my converter in the good place, or by doing something else that I haven't tought of yet?
Thanks!

How does ORMlite manage inheritance between Java classes? [duplicate]

I'm trying to use inheritance with ORMLite and I can't work out if it is supported or not from looking at the documentation and googling.
What I want to do is have
public abstract class Person{
public int id;
public String name;
}
public class Student extends Person{
public String school;
public String year;
// other student stuff
}
public class Teacher extends Person{
public String title;
// other teacher stuff
}
What I can't work out (assuming it's supported) is how to annotate the 3 classes for ORMLite.
Do I only need to annotate the concrete classes with #DatabaseTable(tableName = "Student") or do I need the abstract class also?
I keep getting errors like:
04-24 10:18:30.857: E/AndroidRuntime(30495): Caused by: java.lang.RuntimeException: java.sql.SQLException: Unknown field 'name' from the Android sqlite cursor, not in:[year, school]
The #DatabaseTable annotation is only necessary on the Student or Teacher tables and would not be used if it was on the Person base class.
What you need to have is a #DatabaseField annotation on the id and name fields in Person. For example:
public abstract class Person{
#DatabaseField(generatedId = true)
public int id;
#DatabaseField
public String name;
}
ORMLite should walk the class hierarchy and any fields from the base class should be included in the Student and Teacher tables. If you edit your question to show the #DatabaseField or other annotations, I can comment more.
Ok for that but now, how to implements, in that example, a fourth class containing a List<AbstractPerson> ?
I precise my question :
public class ClassRoom {
#ForeignCollectionField(foreignFieldName="asYouWant")
public Collection<Person> peoples;
}
peoples.add(new Student());
peoples.add(new Teacher());
peoples.add(new Student());
because when ormlite will try to access peoples like :
for (Person person : classRoom.peoples)
{
if (person.getType() == Student)
//do stuff
else if (person.getType() == Student)
//do other stuff
}
It won't be able to get personDAO because it doesn't exist (abstract)...
I get all my database functionnal with good Id's and relation, it's just a data access question ?

Categories

Resources