Джерси API + JPA/Hibernate Критерии Ленивый Загрузка не работает

Вот упрощенная POJO, у меня есть:

@Entity
@Table( name = "Patient" )
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
(
                name="Discriminator",
                discriminatorType=DiscriminatorType.STRING
                )
@DiscriminatorValue(value="P")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Patient implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name = "ID", unique = true, nullable = false)
    protected Integer ID;

    @ManyToOne(targetEntity = TelephoneType.class, fetch=FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name="IDPhoneType")
    protected TelephoneType phoneType;


    @JsonProperty(required=false, value="phoneType")
    public TelephoneType getPhoneType() {
        return phoneType;
    }
    public void setPhoneType(TelephoneType phoneType) {
        this.phoneType = phoneType;
    }
}

Теперь вот мой класс TelephoneType:

@Entity
@Table( name = "TelephoneType" )
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@JsonAutoDetect(getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE, fieldVisibility=Visibility.NONE)
public class TelephoneType implements Serializable{

private static final long serialVersionUID = -3125320613557609205L;

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name = "ID", unique = true, nullable = false)
private Integer ID;

@Column(name = "Name")
private String name;

@Column(name = "Description")
private String description;

public TelephoneType() {
}

@JsonProperty(value="id")
public int getID() {
    return ID;
}

public void setID(int iD) {
    ID = iD;
}

@JsonProperty(value="name")
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@JsonProperty(value="description")
public String getDescription() {
    return description;
}

public void setDescription(String description) {
    this.description = description;
}

}

Причина, по которой я использую аннотацию @JsonAutoDetect в TelephoneType, сначала настраивает имена свойств json (мне нужно было дезактивировать jsonautodetect по умолчанию), а также потому, что , если я этого не сделаю, я получаю сообщение об ошибке при получении очереди

Сериализатор не найден для класса org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer и никаких свойств, обнаруженных для создания BeanSerializer (чтобы исключить исключение, отключите SerializationFeature.FAIL_ON_EMPTY_BEANS)) (через цепочку ссылок: my.package.Patient [ "phoneType" ] → my.package.TelephoneType _ $$ _ jvste17_13 [ "обработчик" ])

Таким образом, без аннотации @JsonAutoDetect я получаю ошибку и с аннотацией no Lazy Loading происходит, и TelephoneType всегда загружается в ответ json.

Я использую критерии для запроса:

return this.entityManager.find(Patient.class, primaryKey);

Я также добавил, что, как я читал в разных сообщениях, следующим образом в web.xml моего приложения (Джерси API):

<filter>
    <filter-name>OpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>OpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Теперь я как-то пропустил что-то в своей конфигурации, но не могу понять, что и у нас есть много отношений @ManyToOne в db, которые значительно замедляют api (некоторые более тяжелые объекты, чем тот, который я показал в примере), поэтому Я бы очень хотел найти способ активировать эту ленивую загрузку...

Ответ 1

Если вы используете JSON, я предполагаю, что вы предоставляете результаты через конечную точку REST. Что происходит тогда, вы передаете объект Patient обратно в службу REST. Когда служба REST, Джерси в этом случае, serializes объект Patient, она затрагивает все свойства и даже просматривает их, чтобы как можно больше построить дерево. Чтобы сделать это, каждый раз, когда Джерси попадает в свойство, которое еще не инициализировано, Hibernate делает другой звонок обратно в базу данных. Это возможно только в том случае, если EntityManager еще не закрыт.

Вот почему вы должны установить OpenEntityManagerInViewFilter. Без него EntityManager закрывается, когда вы выходите из уровня сервиса, и получаете LazyInitializationException. OpenEntityManagerInViewFilter открывает EntityManager на уровне представления и сохраняет его открытым до тех пор, пока HTTP-запрос не будет завершен. Поэтому, хотя это похоже на исправление, на самом деле это не так, потому что, когда вы теряете контроль над тем, кто обращается к свойствам ваших объектов, в этом случае Jersey, вы в конечном итоге загружаете вещи, которые вам не нужны загружать.

Лучше удалить OpenEntityManagerInViewFilter и выяснить, что именно вы хотите Jersey для сериализации. После того, как вы это выяснили, есть по крайней мере два способа справиться с этим. IHMO, "наилучшей практикой" является наличие DTO или объектов передачи данных. Это POJO, которые не являются объектами, но имеют почти одинаковые поля. В случае PatientDTO будет иметь все, кроме свойства phoneType (или, может быть, просто Id). Вы передадите ему Patient в конструкторе, и он скопирует поля, которые вы хотите, чтобы сериализовать Джерси. Тогда ваш уровень обслуживания будет отвечать за возврат DTO вместо Entities, по крайней мере для конечных точек REST. Ваши клиенты получат графики JSON, которые представляют эти DTO, что даст вам лучший контроль над тем, что входит в JSON, потому что вы пишете DTO отдельно от Entities.

Другим вариантом является использование аннотаций JSON для предотвращения попытки Джерси сериализовать свойства, которые вы не хотите сериализовать, например phoneType, но это в конечном итоге становится проблематичным. Будут противоречивые требования, и вы никогда не получите его хорошо отсортированным.

При создании DTO сначала кажется ужасной болью, это не так плохо, как кажется, и даже помогает, когда вы хотите сериализовать ценности, более дружественные к клиенту. Итак, моя рекомендация - потерять OpenEntityManagerInViewFilter и создать надлежащий уровень сервиса, который возвращает DTO, или View Objects, как их иногда называют.

Ссылки: Что такое объект передачи данных?

REST API - DTO или нет?

Gson: как исключить определенные поля из сериализации без аннотаций

Ответ 2

Чтобы понять, что здесь происходит, вам нужно понять, как работает ленивая загрузка в Hibernate.

Когда список объявляется как "ленивый", платформа Hibernate реализует объект "lazy loaded" JavassistLazyInitializer с помощью Javassist. Следовательно, phoneType на объекте пациента не является реализацией вашего класса PhoneType. Он является доверенным лицом к нему. Когда getPhoneType() на этом объекте вызывается, прокси-сервер на пациенте заменяется реальным объектом. К сожалению, @JsonAutoDetect использует отражение в прокси-объекте без вызова метода getPhoneType() и пытается фактически сериализовать объект JavassistLazyInitializer, который, конечно, невозможен.

Я думаю, что самым изящным решением для этого является реализация запроса, который выводит пациентов с их телефонным типом.

Итак, вместо:

return this.entityManager.find(Patient.class, primaryKey);

Внесите что-то вроде:

EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Patient> query = cb.createQuery(Patient.class);
Root<Patient> c = query.from(Patient.class);
query.select(c).distinct(true);
c.fetch("phoneType");
TypedQuery<Patient> typedQuery = em.createQuery(query);
List<Patient> allPatients = typedQuery.getResultList();

Адаптация запроса к вашим потребностям по мере необходимости.

Ответ 3

Посмотрите jackson-datatype-hibernate Это делает сериализацию json с джексоном "осведомленным" прокси-сервера hibernate