Mapstruct: join on id - java

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.

Related

Spring #RedisHash findAll() return null values

I'm using Redis to store students with entity:
#RedisHash("Student")
public class Student implements Serializable {
#Id
private Long id;
#Indexed
private String name;
private Integer age;
// getters
// setters
// Constructor with full parameters
}
and repository:
#Repository
public interface StudentRepository extends CrudRepository<Student, Long> {
}
I can save a list of students to Redis database and get that list without any error:
#Autowired
StudentRepository repo;
List<Student> students = new ArrayList<>();
Student student1 = new Student(........);
students.add(student1);
Student student2 = new Student(........);
students.add(student2);
repo.findAll().forEach(){
System.out.println(student);
}
The problem is when other project of mine (I'm building apps with micro-service architecture), I use findAll() function to get that list of students, it returns a list of two null elements. If I use findByName(String name), it still returns desired result.
Anyone who used to face this problem can help me, thank you in advanced ?
Turns out my Student class on the other project has the same #RedisHash("Student") but different full class name (same class name but different package). I think this issue belongs to the library.
Updated: cause #RedisHash doesn't work like expected, I found the way: that is adding #TypeAlias("Student") to the entity Student, therefore you can place Student anywhere in your source code

How can i fix output of Classes with relationship using Dtos in spring boot?

my class
public class Teacher
has ManyToMany relationship with Students :
public class Student
when i return teachers i want only their student ids , instead of full information about their students.
we have two Dtos (DataTransferObject) : StudentDto and TeacherDto with some variables.
what can i do to solve this problem?
I want Students as well to return only names or ids of Teachers. instead they return full dto list of teachers.
thanks.
In your TeacherDto you shouldn't have list of StudentDtos but rather list of Integer (if Integer is type of id). Than you should implement some mapping logic, for example in the constructor, like this:
public class TeacherDto {
private final Set<Integer> studentIds;
public TeacherDto(Teacher teacher) {
this.studentIds = teacher.getStudents().stream()
.map(Student::getId)
.collect(Collectors.toSet());
}
public Set<Integer> getStudentIds() {
return studentIds;
}
}
If you have many different DTOs, there is a lot of libraries which help you automate mapping between them.

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();
}

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 ?

Best way of handling generic entities in Spring MVC(Generics, Reflection, or other)?

I have an entity named Commercial. I have an Category entity where the list of commercial categories are hold. For each category there is an separate entity extending Commercial(like RestaurantCommercial, PhotoStudioCommercial etc. total up to 20 entities) with JOINED inheritance strategy.
Commercial entity holds up general properties like title, text contactnumber of some company's commercial, while RestaurantCommercial and PhotoStudioCommercial holds additional specific properties concerned with that category.
The problem is that writing a separate dao and controller for each entity is a bit plenty of work, so I am searching for a neat way to handle this issue.
I need an unified controller and may be the DAO for handling the form control and persisting new instances of the entities that extend Commercial.
Here is approximately what I was thinking about:
#RequestMapping(value={"/vendor/commercial/{categoryName}/new"}, method=RequestMethod.GET)
public String showNewCommercialForm(#PathVariable("categoryName") String categoryName,
Map<String, Object> map) {
Category category = categoryDAO.getCategoryByServiceName(categoryName);
Class clazz = Class.forName(category.getClassName()); //here className is smth like domain.commercial.RestaurantCommercial
map.put("commercialInstance", clazz.newInstance());
//separate view for each category of commercial
return "vendor/commercial/"+categoryName+"/new";
}
And I was thinking for a similar controller for saving form data even if I would have to write a sperate binder for this stuff.
So the question is: What would you suggest to handle this issue or what would be the best practice if you had already faced similar need(Generics, Reflection or smth else)? Or if that would be worthy or not and why?
Thanks in advance.
I create a Dao interface for such cases:
public interface Dao<T extends Commercial> {
T getAll();
}
After that an abstract Dao implementation, for example hibernate based:
public CommonHibernateDao<T extends Commercial> implements Dao<T>
{
private final Class<T> entityType;
protected CommonHibernateDao(Class<T> entityType) {
this.entityType = entityType;
}
public List<T> getAll() {
// hibernate get all implementation
}
}
And RestaurantCommercial Dao interface and implementation:
public interface RestaurantCommercialDao extends Dao<RestaurantCommercial> {
}
public class HibernateRestaurantCommercialDao extends CommonHibernateDao<RestaurantCommercial> implements RestaurantCommercialDao {
public HibernateRestaurantCommercialDao() {
super(RestaurantCommercial.class);
}
}
All implementation goto CommonHibernateDao. In it's subclasses only constructor calling neaded. Basically you can do it with reflection but as for me it is not clear.
For controller (something like RESTfull API):
#Controller
public class YourController() {
#RequestMapping(value = "/restapi/{entityType}")
public String postEntity(HttpServletRequest request, #PathVariable(value = "entityType") String entityType) {
// If enetity name will be packegae + class name
Object beanInstance = Class.forName(entityName);
ServletRequestDataBinder binder = new ServletRequestDataBinder(beanInstance);
binder.bind(request);
// Here by type of entity you can get DAO and persist
}
}
If form input names will be same to your bean names - binding will do all automatically.

Categories

Resources