Джерси 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