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 ?
Related
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",
}
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();
}
I am using Mapstruct to map from generated DTOs (metro, xsd) to our business domain objects. My difficulty is that the DTOs don't actually reference child objects but instead use IDs to reference associated instances.
Trying to break this down to a simplified case, I have come up with an example:
SchoolDTO has a lists of teachers and courses. The teacher of a
course is only referenced through a teacherId in each course.
In the business domain School only has a list of teachers who each
hold a list of their courses.
Class diagram: UML: DTO / Domain
Initially I was hoping to solve this in mapstruct syntax with something like a join on foreignId and teacher id (or some qualifiedBy association), pseudo code as follows:
#Mapping(source="courses", target="teachers.courses", where="teacher.id = course.teacherId")
DTOs:
public class SchoolDto {
List<TeacherDto> teachers;
List<CourseDto> courses;
}
public class TeacherDto {
String id;
String name;
}
public class CourseDto {
String name;
String teacherId;
}
Domain:
public class School {
List<Teacher> teachers;
}
public class Teacher {
String name;
List<Course> courses;
}
public class Course {
String name;
}
I am right now working around it with fairly big #AfterMapping methods but I feel this isn't such an exceptional use case - so maybe I am missing something rather obvious. What is the correct/intended way to solve these type of "joins" in a mapping with Mapstruct?
I doubt that you can do this without an #AfterMapping. MapStruct is "just" for mapping one object to another one, it doesn't support any kind of queries to find or join data.
If you are not already using it this sounds like a good use-case for using a context. Then the #AfterMapping is not really big:
#Mapper
public abstract class SchoolMapper {
public School toSchool(SchoolDto school) {
return toSchool( school, school.getCourses() );
}
protected abstract School toSchool(SchoolDto school, #Context List<CourseDto> courses);
#Mapping(target = "courses", ignore = true) // see afterMappingToTeacher
protected abstract Teacher toTeacher(TeacherDto teacher, #Context List<CourseDto> courses);
protected abstract Course toCourse(CourseDto course);
#AfterMapping
void afterMappingToTeacher(#MappingTarget target, TeacherDto source, #Context List<CourseDto> courses) {
// omitted null-checks
List<Course> courses = new ArrayList<>();
for(CourseDto course : courses) {
if(course.getTeacherId().equals(source.getId())) {
courses.add( toCourse(course) );
}
}
target.setCourses( courses );
}
}
(when using Java >= 8 you can use an interface with default methods)
In case you need to query things multiple times you can things create an own class as a context which for example has own methods for finding all courses by a teacher ID.
Ok, lets say I have a model.
#Entity
public class Person extends Model {
public String name;
public String email;
}
Now. I have a couple of transient fields that don't exist in the database, but I need to access them. Basically they are fields for displaying the Person on a web page.
#Entity
public class PersonDisplay extends Person {
#Transient
public String DT_RowClass = "";
#Transient
public String cellColor = "";
}
Is it possible to query the PersonDisplay and get the Person object plus the PersonDisplay default values?
Like this.
PersonDisplay display = PersonDisplay.find("byEmail" , "test#test.com").first();
I'm running into a few errors when I do this.
Let me know if I need to explain this again.
Look up the #PostLoad annotation, which you can use to initialize any transient variables.
I have a problem trying to map an inheritance tree. A simplified version of my model is like this:
#MappedSuperclass
#Embeddable
public class BaseEmbedded implements Serializable {
#Column(name="BE_FIELD")
private String beField;
// Getters and setters follow
}
#MappedSuperclass
#Embeddable
public class DerivedEmbedded extends BaseEmbedded {
#Column(name="DE_FIELD")
private String deField;
// Getters and setters follow
}
#MappedSuperclass
public abstract class BaseClass implements Serializable {
#Embedded
protected BaseEmbedded embedded;
public BaseClass() {
this.embedded = new BaseEmbedded();
}
// Getters and setters follow
}
#Entity
#Table(name="MYTABLE")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE", discriminatorType=DiscriminatorType.STRING)
public class DerivedClass extends BaseClass {
#Id
#Column(name="ID", nullable=false)
private Long id;
#Column(name="TYPE", nullable=false, insertable=false, updatable=false)
private String type;
public DerivedClass() {
this.embedded = new DerivedClass();
}
// Getters and setters follow
}
#Entity
#DiscriminatorValue("A")
public class DerivedClassA extends DerivedClass {
#Embeddable
public static NestedClassA extends DerivedEmbedded {
#Column(name="FIELD_CLASS_A")
private String fieldClassA;
}
public DerivedClassA() {
this.embedded = new NestedClassA();
}
// Getters and setters follow
}
#Entity
#DiscriminatorValue("B")
public class DerivedClassB extends DerivedClass {
#Embeddable
public static NestedClassB extends DerivedEmbedded {
#Column(name="FIELD_CLASS_B")
private String fieldClassB;
}
public DerivedClassB() {
this.embedded = new NestedClassB();
}
// Getters and setters follow
}
At Java level, this model is working fine, and I believe is the appropriate one. My problem comes up when it's time to persist an object.
At runtime, I can create an object which could be an instance of DerivedClass, DerivedClassA or DerivedClassB. As you can see, each one of the derived classes introduces a new field which only makes sense for that specific derived class. All the classes share the same physical table in the database. If I persist an object of type DerivedClass, I expect fields BE_FIELD, DE_FIELD, ID and TYPE to be persisted with their values and the remaining fields to be null. If I persist an object of type DerivedClass A, I expect those same fields plus the FIELD_CLASS_A field to be persisted with their values and field FIELD_CLASS_B to be null. Something equivalent for an object of type DerivedClassB.
Since the #Embedded annotation is at the BaseClass only, Hibernate is only persisting the fields up to that level in the tree. I don't know how to tell Hibernate that I want to persist up to the appropriate level in the tree, depending on the actual type of the embedded property.
I cannot have another #Embedded property in the subclasses since this would duplicate data that is already present in the superclass and would also break the Java model.
I cannot declare the embedded property to be of a more specific type either, since it's only at runtime when the actual object is created and I don't have a single branch in the hierarchy.
Is it possible to solve my problem? Or should I resignate myself to accept that there is no way to persist the Java model as it is?
Any help will be greatly appreciated.
Wow. This is the simplified version? I assume that the behavior that you are seeing is that BaseEmbedded field is persisted but not the FIELD_CLASS_A or B?
The problem is that when Hibernate maps the DerivedClassA and B classes, it reflects and sees the embedded field as a BaseEmbedded class. Just because you then persist an object with the embedded field being a NestedClass, the mapping has already been done and the FIELD_CLASS_A and B are never referenced.
What you need to do is to get rid of the NestedClass* and embedded field and instead have the fieldClassA and B be normal members of DerivedClassA and B. Then add add a name field to the #Entity which will put them both in the same table I believe. This will allow you to collapse/simplify your class hierarchy a lot further.
See: http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e1168
#Entity(name = "DerivedClass")
#DiscriminatorValue("A")
public class DerivedClassA extends DerivedClass {
#Column(name="FIELD_CLASS_A")
private String fieldClassA;
...
#Entity(name = "DerivedClass")
#DiscriminatorValue("B")
public class DerivedClassB extends DerivedClass {
#Column(name="FIELD_CLASS_B")
private String fieldClassB;
...