0

okay, i have an 3 entities: Topic, User, Category, Picture. User have a picture, and topic have an User and Category.

class Topic {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;

    @Column(nullable = false)
    String header;

    @Column(nullable = false)
    String description;

    @Column(name = "is_anonymous", nullable = false)
    boolean isAnonymous;

    @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    @Column(name = "creation_date", nullable = false)
    LocalDateTime creationDate;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id", nullable = false)
    User author;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", nullable = false)
    Category category;
}

And i also have an topic DTO

class TopicDTO {

    String header;

    String description;

    boolean isAnonymous;

    LocalDateTime creationDate;

    UserDTO userDTO;

    CategoryDTO categoryDTO;
}

I can to inject ModelMapper into TopicService, and use it to convert, but it doesn't work as I need, in this case, if i trying to convert Topic to TopicDTO, in the converted TopicDTO object, UserDTO and CategoryDTO will be null, but in the debug, before converting, in the Topic object - Category object and User object is not null, they are initialized.

I trying to write a CRUD services for each entities, into which i inject repositories that extends CrudRepository. And when i get from controller TopicDTO, i call topicService.save(topicDTO), but in the topic service, method save, i dont want to cascade save user, i dont want to cascade save categories, i want to save topic with existing samples category and user, how i can to do that? Sorry for my awful english

Влад
  • 15
  • 1
  • 1
  • 4
  • Do you REALLY need DTO pattern? see more : https://stackoverflow.com/a/1058186/5485454 – Kai-Sheng Yang Apr 11 '22 at 17:31
  • @Kai-ShengYang, I'm not really familiar with Spring, I am just learning, and I read this link, a lot of complicated things are written there, from which I singled out that DTO used to transfer data between Service layer and the UI layer, that's why i decide to used DTO. For example, User entity have an Email, password, role (admin/user). In DTO i excluded these fields, and controller dont know anything about my entities, controller only knows about my DTOs... I'm not sure if this is correct, this conclusion is the result of reading different questions, articles, which I may have misunderstood – Влад Apr 12 '22 at 08:34

3 Answers3

0

You can create an of method within your TopicDTO class to construct the object. Providing the userDTO and categoryDTO as arguements will allow them to be set to null or their respective objects if they exist.

class TopicDTO {

    String header;

    String description;

    //all the other variables...

    public static TopicDTO of(Topic topic, UserDTO userDTO, CategoryDTO categoryDTO) {
    return new TopicDTO(
            topic.getHeader(),
            topic.getDescription(),
            topic.getIsAnonymous(),
            topic.getCreationDate(),
            userDTO,
            categoryDTO);
    }
}
lew
  • 70
  • 10
  • As my friend explained to me, it is better to avoid using constructors with parameters. will be better to create an empty object, and assign values ​​​​to it through setters – Влад Apr 12 '22 at 08:37
  • Thanks for the advice. That does make alot of sense, would this also apply for a 1-1 conversion ? I.e. if I only had one Topic arguement ? – lew Apr 12 '22 at 10:39
  • Idk for sure, but i think yes, because your setter may have some logic that the constructor will avoid... Although in the constructor you can assign values ​​​​through setters.... so I'm confused, I'm sorry, I can't really answer the question =)) – Влад Apr 13 '22 at 07:51
0

You can either use a code generator like MapStruct which I really don't preconise as you'll have to learn how to annotate your DTOs in order to map them and that it's quite deprecated. (For example it's testable only with junit4).

You should rather use Lombok builders to instantiate DTOs from your entities. Futhermore you can test it easily with junit5 in TDD like this :

class TopicMapperTest {

    TopicMapper topicMapper;

    @Mock
    UserMapper userMapper;

    Clock clock;

    @BeforeEach
    void setUp() {
        topicMapper = new TopicMapper();
        clock = Clock.fixed(LocalDateTime.now().toInstant());
    }

    @Test
    void should_map_topic_to_topicDTO() {
        // Given
        Topic topic = new Topic(1, "header", "description", false, LocalDateTime.now(clock), new User(), new Category());

        TopicDTO expected = TopicDTO.builder()
                .header("header")
                .description("description")
                .isAnonymous(false)
                .creationDate(LocalDateTime.of())
                .userDTO(userMapper.mapUser(new User()))
                .categoryDTO(categoryMapper.mapCategory(new Category()))
                .build();


        // When
        TopicDTO result = topicMapper.mapTopic(topic);

        // Then
        assertEquals(expected, result);
    }
}

And your mapper should looks like (I let you complete it to make your test pass) :

public class TopicMapper {

    UserMapper userMapper = new UserMapper();

    public TopicDTO mapTopic(Topic topic) {
        return TopicDTO.builder()
                .header(topic.getHeader())
                .description(topic.getDescription())
                .userDTO(userMapper.mapUser(topic.getAuthor()))
                .isAnonymous(topic.isAnonymous())
                .build();
    }
}
Gweltaz Niquel
  • 629
  • 4
  • 14
  • Is it a better way, then ModelMapper? – Влад Apr 12 '22 at 08:05
  • I didn't know ModelMapper. Thanks for the tips. I still think It's clearer like that and quite useless to use mapping framework (No configuration, no learning, easily testable) but it's about colors and taste... – Gweltaz Niquel Apr 12 '22 at 09:20
0

You should use constructors to create objects only! Do not use getters/setters when instantiating the object because this is an anti-pattern. It's hard to maintain what setter you have used already and what not. Object should be fully created through the constructor and keyword 'new' and it will have a state immediately.

To map Entity to dto I would suggest creating a simple Mapper class that accepts Entity in the constructor and return new dto object and that's it.

Ahmet Emre Kilinc
  • 5,489
  • 12
  • 30
  • 42
Artem S
  • 11
  • 3