LINUX.ORG.RU

Jackson2 && MixIn

 , ,


0

1

Есть две связные сущности: Organization самодостаточна, Site имеет ссылку на нее:

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
public class Site extends AbstractIdentifiable {

    @OneToOne(optional = false)
    private Organization organization;

    @ManyToMany(mappedBy = "sites", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private Set<Member> members = new HashSet<>();

    @OneToMany(mappedBy = "site", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private Set<Location> locations = new HashSet<>();

    public Site() {
    }

    public Organization getOrganization() {
        return organization;
    }

    public void setOrganization(Organization organization) {
        this.organization = organization;
    }

    public Set<Member> getMembers() {
        return members;
    }

    public void setMembers(Set<Member> members) {
        this.members = members;
    }

    public Set<Location> getLocations() {
        return locations;
    }

    public void setLocations(Set<Location> locations) {
        this.locations = locations;
    }

}
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

@Entity
public class Organization extends AbstractIdentifiable implements Serializable {

    @Column(nullable = false)
    @NotBlank
    private String title;

    @OneToOne(mappedBy = "organization", cascade = CascadeType.ALL)
    private Site site;

    @OneToOne(mappedBy = "organization", cascade = CascadeType.ALL)
    private Advertiser advertiser;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Site getSite() {
        return site;
    }

    public void setSite(Site site) {
        this.site = site;
    }

    public Advertiser getAdvertiser() {
        return advertiser;
    }

    public void setAdvertiser(Advertiser advertiser) {
        this.advertiser = advertiser;
    }

}

Когда я сериализую Site приложение зацикливается из-за циклических ссылок. Поставить @JsonIgnore в сущности Organization у поля site нельзя, ибо в другом месте программы потребуется сериализовать сущность Organization как корневую, с сериализацией Organization как вложенной сущности. На помощь приходит MixIn. Делаю так:

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.helan.adkiosk.entitiy.Advertiser;
import com.helan.adkiosk.entitiy.Site;

public interface OrganizationMixIn {

    @JsonIgnore
    public Site getSite();

    @JsonIgnore
    public Advertiser getAdvertiser();

}
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.helan.adkiosk.entitiy.Location;
import com.helan.adkiosk.entitiy.Member;

import java.util.Set;

public interface SiteWithOrganizationMixIn {

    @JsonIgnore
    public Set<Member> getMembers();

    @JsonIgnore
    public Set<Location> getLocations();

}
public String index() throws JsonProcessingException {
    ObjectMapper mapperWithMixIn = new ObjectMapper();
    mapperWithMixIn.addMixIn(Site.class, SiteWithOrganizationMixIn.class);
    mapperWithMixIn.addMixIn(Organization.class, OrganizationMixIn.class);
    return mapperWithMixIn.writeValueAsString(siteRepository.findAll());
}

Все работает как и задумывалось. Не устраивает только то, что я должен явно указывать поля в каждом MixIn, которые я не хочу сериализовывать с помощью аннотации JsonIgnore. Есть ли возможность реализовать обратную логику: по умолчанию сериализация отключена для всех полей, и в каждом MixIn я должен включить определенные поля? Или еще лучше, Изначально в сущности я указываю поля, которые сериализуются, а затем добавляю еще требуемые в каждом MixIn?

Еще вопрос, в mvc могу ли я не создавать в явном виде ObjectMapper и вызывать сериализацию в строку, а указать с помощью какой-нибудь аннотации используемые в сериализации набор MixIn и возвращать ссылку объект из метода контроллера?

Ответ на: комментарий от BattleCoder

Не важно. Меня интересуют возможности реализации двух фич: указание используемых в сериализации mixIn для ResponseBody и политика явного разрешения сериализации свойств, а не наоборот запрета.

popov-aa
() автор топика
Ответ на: комментарий от popov-aa

А вас не интересует то, что тот разработчик, который после вас будет этот код поддерживать, вас линчевать захочет?

BattleCoder ★★★★★
()
Ответ на: комментарий от BattleCoder

А вас не интересует то, что тот разработчик, который после вас будет этот код поддерживать, вас линчевать захочет?

Почему? Чем плох этот подход и какой лучше?

popov-aa
() автор топика

Не надо мешать persistence entity и dto. То есть сущности, которые используются для внутренней логики и сохранения в СУБД != сущности, которые отдаются наружу через какой-либо интерфейс (rest или что-то другое).

Заведи отдельно сущности, для внешнего обмена (dto), опиши их так как должна выглядеть наружная логика и отдавай их, а не Entity. Какая-нибудь Orika поможет тебе быстро отображать entity <=> dto.

Даже если кажется, что persistence entity совпадают с dto, не делай так. Потому что потом они расползутся и будет у тебя класс, в котором намешаны всякие @JsonIgnore и @Transient. А у тебя они уже расползлись.

ma1uta ★★★
()

Думал тут минимум кода, но нет,
Тут туча дерьмища на каждый пук.
Тошнит от дто помоек вончючих?
А что ты хотел? Это - ява, Люк.

crutch_master ★★★★★
()

Выкинь, короче, этот свой гибернейт, раскури mybatis/jdbc template, рефлексию и сделай себе фреймворк какой надо. Больше пользы получишь, чем трахаться с этим куском дерьма. Почитай про lombook и не пиши уже эти дефолтные get/set.

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 2)
Ответ на: комментарий от ma1uta

Допустим у меня 20 сущностей. В простейшем случае мне придется написать 20 DTO. А если на каждую сущность мне нужно два варианта сериализации, получается уже 40 DTO.

Слишком много когда. Гораздо проще и эффективнее написать MixIn на исключительные случае и комбинировать их.

Но вашу мысль я понял. Очень жаль, что джава так многословна.

popov-aa
() автор топика
Ответ на: комментарий от crutch_master

Про ломбок слышал. Сейчас взглянул еще раз, выглядет потрясающе. Ушел внедрять в своей проект.

popov-aa
() автор топика
Ответ на: комментарий от popov-aa

Очень жаль, что джава так многословна.

Она не то, чтобы многословна. Это фреймворки просто такие конченные. Ты может придумать аннотацию, где просто будешь перечислять, какие поля тебе нужны для сериализации когда, и что в них будет. Можешь даже в json это описать. Но jackson такое не сожрёт, поэтому надо будет придумывать что-то еще.

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 1)
Ответ на: комментарий от popov-aa

Да даже если есть 20 сущностей (ты решил наружу отдавать справочники)?

В одном случае при использовании lombok у тебя да, 40 классов, содержащих описание твоих сущностей (без геттеров и сеттеров). А в другом в 20 классах будет помойка, и добавление одного нового поля будет вызывать боль и муки, потому что будет либо ломать сохранение в СУБД, либо заставлять лепить костыли (особенно, когда встанет задача загрузки/выгрузки нескольких файлов).

ma1uta ★★★
()
Ответ на: комментарий от ma1uta
new Pair(leftMapper.map(rs, 1, ctx), // In JDBC, column numbers start at 1
         rightMapper.map(rs, 2, ctx)); // ..for MOTHERF***ING REASONS

Весёлые парни.

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 1)
Ответ на: комментарий от ma1uta

Таки да, он крут. Можно даже приделать свой sql кодогенератор поверх, т.к. они не как упоротые авторы mybatis которые работают только с xml конфигом или аннотациями. Тут можно ручками дернуть мапер с нужными тебе классами. Перспективная штука.

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 1)
Ответ на: комментарий от crutch_master

Обычно первое, что находят - это myBatis и Hibernate. Потом оказывается, что есть EclipseLink (который в качестве jpa тоже неплох).

На самом деле jpa (hibernate/eclipselink) генерируют вполне годный sql (конечно, про специфичные штуки каждой СУБД придётся забыть). Но для этого надо понимать, как он генерирует sql и уметь им пользоваться. И да, придётся мириться с тем, что в stacktrace-е будет 5-10 дополнительных вызовов jpa.

Но если уж связался с jpa и без него никак, тогда http://www.querydsl.com очень облегчает жизнь, и позволяет избежать встречи с Criteria API, заменяя запросы примерно на вот такой код:

cats = query.select(catEntity).from(cat)
    .innerJoin(mate).on(cat.mateId.eq(mate.id))
    .where(cat.dtype.eq("Cat"), mate.dtype.eq("Cat"))
    .fetch();

Если можно отказаться от jpa, тогда можно взять jdbi или jOOQ.

ma1uta ★★★
()
Последнее исправление: ma1uta (всего исправлений: 1)
Ответ на: комментарий от ma1uta

С hibernate в считалках такая фигня, что получение данных упорно смешивается с логикой. В результате имеем тучу запросов по одной строчке где-нибудь в цилке. Переписывание даёт тоже самое, что и mybatis + грязный и не нужный секас с jpa. Вот когда я искал замену было всё, кроме jdbi, или мне он был просто не по глазам.

Но если уж связался с jpa и без него никак, тогда http://www.querydsl.com очень облегчает жизнь, и позволяет избежать встречи с Criteria API, заменяя запросы примерно на вот такой код:

Тут хотелось бы чего-то такого, чтобы плясалось от таблицы и не надо было, например, при запросе каждый раз указывать ключи для связей. Да, это как бы про гибернейт, но не совсем.

crutch_master ★★★★★
()

не мешай сущности. для hibernate своё для json своё. все проблемы уйдут. То что возможно кол-во кода вырастет это не проблема - кол-во кода это не сама цель, а средство для упрощения, если минимизация кода не упрощает, а только лишь усложняет, то это плохая минимизация от неё никакого проку.

vtVitus ★★★★★
()
Ответ на: комментарий от vtVitus

Но она упрощает. Во всех случаях я буду мапить одни и те же поля, класса сущности на такие же поля класса DTO. DTO будет соответствовать один к одному классам сущностей, просто от ресурса к ресурсу будет менять состав данных. Где-то будут отдаваться связные списки, где-то нет.

Но допустим я решил использовать DTO. Вопрос то остается открытым, как в использовании MixIn перейти от запрещения ненужного, к разрешению нужного?

popov-aa
() автор топика
Ответ на: комментарий от popov-aa

Я вижу по данной теме, как оно упрощает и это только цветочки - будут накапливаться изменения и всё превратится в набор проверок кому чего. Если у тебя две сущности тебе не нужен MixIn - ты возвращаешь в json то что тебе нужно, а не то что есть в сущности бд. MixIn это в принципе костыль, и я его использую как временное решение в прототипах, в проектах я его запрещаю - геморЪ от него большой при рефакторингах.

vtVitus ★★★★★
()
Последнее исправление: vtVitus (всего исправлений: 1)
Ответ на: комментарий от popov-aa

А какие у него области применения?

Грубо говоря, когда ты хочешь навесить аннотацию на класс, но по каким-то причинам не можешь этого сделать.

ya-betmen ★★★★★
()

По поводу DTO полностью соглашусь. Дошел до той точки, когда ответы фронтэнду перестали совпадать по структуре с сущностями JPA, а MixIn перестали справляться со сложностью сериализации. Перешел на DTO и избавился от всех этих проблем.

popov-aa
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.