Джерси 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 по умолчанию), а также потому, что если я этого не делаю, я получаю ошибку при получении очереди

сериализатор не найден для класса орг.зимовать.полномочие.POJO-объект.javassist.JavassistLazyInitializer и никаких свойств не обнаружено для создания BeanSerializer (чтобы избежать исключения, отключите SerializationFeature.FAIL_ON_EMPTY_BEANS)) (через цепочку ссылок: my.пакет.Пациент ["phoneType"] - > мой.пакет.TelephoneType_$$_jvste17_13["куратор"])

поэтому без аннотации @JsonAutoDetect я получаю ошибку и с аннотацией не происходит ленивой загрузки и телефонный тип всегда загружается в json ответ.

Я использую критерии, чтобы сделать запрос:

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

Я также добавил, Как я читал в разных сообщениях на so, следующее в 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 в БД, которые значительно замедляют api (некоторые более тяжелые объекты, чем тот, который я показал в Примере), поэтому я бы очень ценю, чтобы найти способ активировать эту ленивую загрузку...

3 ответов


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

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

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

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

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

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

REST API-DTOs или нет?

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


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

когда список объявлен как "лениво загруженный", платформа Hibernate реализует "лениво загруженный"JavassistLazyInitializer объект с Javassist. Следовательно, тип телефона на объекте пациента не является реализацией класса TelephoneType. Это прокси к нему. Когда 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();

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


посмотреть Джексон-тип данных-спящий режим Это делает сериализацию json с Джексоном "осведомленным" о прокси-сервере hibernate