Джерси 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 Объекты, как их иногда называют.
ссылки: что такое объект передачи данных?
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