Jersey API + JPA/Hibernate 条件延迟加载不起作用
Jersey API + JPA/Hibernate Criteria Lazy Loading not working
这是我拥有的一个简化的 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;
}
}
现在这是我的 class 电话类型:
@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;
}
}
我在 TelephoneType 中使用 @JsonAutoDetect 注释的原因首先是自定义 json 属性 名称(我需要停用默认的 jsonautodetect),还因为 如果我不这样做,我在获取队列时会出错
No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: my.package.Patient["phoneType"]->my.package.TelephoneType_$$_jvste17_13["handler"])
因此,如果没有 @JsonAutoDetect 注释,我会得到错误,使用注释时,不会发生延迟加载,并且 TelephoneType 始终加载到 json 响应中。
我使用 Criteria 进行查询:
return this.entityManager.find(Patient.class, primaryKey);
正如我在不同的帖子中阅读的那样,我还在我的应用程序(泽西 API)的 web.xml 中添加了以下内容:
<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(一些比我重的对象显示在示例中)所以我真的很感激找到一种方法来激活这个延迟加载的东西...
要了解此处发生的情况,您必须了解延迟加载在 Hibernate 中的工作原理。
当列表声明为 "lazy loaded" 时,Hibernate 框架使用 Javassist 实现 "lazy loaded" JavassistLazyInitializer
对象。
因此,患者对象上的 phoneType 不是 TelephoneType class 的实现。它是它的代理。
但是,当调用此对象上的 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();
根据需要调整查询。
看看jackson-datatype-hibernate
它使用休眠代理
的jackson'aware'进行json序列化
如果您使用的是 JSON,那么我假设您是通过 REST 端点提供结果的。然后发生的事情是您将 Patient
实体传递回 REST 服务。当 REST 服务(在本例中为 Jersey)时,serializes
Patient
实体会触及所有属性,甚至遍历它们,以便构建尽可能完整的树。为了做到这一点,每次 Jersey 遇到一个尚未初始化的 属性 时,Hibernate 都会再次调用数据库。这只有在 EntityManager
尚未关闭时才有可能。
这就是您必须安装 OpenEntityManagerInViewFilter
的原因。没有它,当您退出服务层时 EntityManager
将关闭,您会得到一个 LazyInitializationException
。 OpenEntityManagerInViewFilter
在视图级别打开 EntityManager
并保持打开状态,直到 HTTP 请求完成。因此,虽然它看起来像是一个修复,但实际上并不是,因为正如您所见,当您无法控制谁在访问您的实体的属性时,在本例中 Jersey
,您最终会加载您没有加载的内容'不想加载。
最好删除 OpenEntityManagerInViewFilter
并找出您想要 Jersey
序列化的内容。一旦弄清楚了,至少有两种方法可以处理它。 IHMO,"best practice" 是要有 DTO,即数据传输对象。这些是不是实体但具有几乎相同字段的 POJO。在这种情况下,PatientDTO
将包含除 phoneType
属性 之外的所有内容(或者可能只是 Id)。您将在构造函数中将 Patient
传递给它,它会复制您希望 Jersey 序列化的字段。然后,您的服务层将负责返回 DTO 而不是 Entities
,至少对于 REST 端点而言。您的客户将获得代表这些 DTO 的 JSON 图表,让您更好地控制进入 JSON 的内容,因为您将 DTO 与 Entities
.
分开编写
另一种选择是使用 JSON 注释来防止 Jersey 尝试序列化您不想序列化的属性,例如 phoneType
,但这最终会成为问题。会有相互冲突的要求,你永远无法很好地解决它。
虽然一开始制作 DTO 似乎是一种可怕的痛苦,但它并没有看起来那么糟糕,甚至当您想要序列化对客户端更友好的值时,它甚至会有所帮助。因此,我的建议是丢掉 OpenEntityManagerInViewFilter
并构建一个适当的服务层 returns DTO,或有时称为视图对象。
参考文献:What is Data Transfer Object?
REST API - DTOs or not?
Gson: How to exclude specific fields from Serialization without annotations
这是我拥有的一个简化的 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;
}
}
现在这是我的 class 电话类型:
@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;
}
}
我在 TelephoneType 中使用 @JsonAutoDetect 注释的原因首先是自定义 json 属性 名称(我需要停用默认的 jsonautodetect),还因为 如果我不这样做,我在获取队列时会出错
No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: my.package.Patient["phoneType"]->my.package.TelephoneType_$$_jvste17_13["handler"])
因此,如果没有 @JsonAutoDetect 注释,我会得到错误,使用注释时,不会发生延迟加载,并且 TelephoneType 始终加载到 json 响应中。
我使用 Criteria 进行查询:
return this.entityManager.find(Patient.class, primaryKey);
正如我在不同的帖子中阅读的那样,我还在我的应用程序(泽西 API)的 web.xml 中添加了以下内容:
<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(一些比我重的对象显示在示例中)所以我真的很感激找到一种方法来激活这个延迟加载的东西...
要了解此处发生的情况,您必须了解延迟加载在 Hibernate 中的工作原理。
当列表声明为 "lazy loaded" 时,Hibernate 框架使用 Javassist 实现 "lazy loaded" JavassistLazyInitializer
对象。
因此,患者对象上的 phoneType 不是 TelephoneType class 的实现。它是它的代理。
但是,当调用此对象上的 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();
根据需要调整查询。
看看jackson-datatype-hibernate 它使用休眠代理
的jackson'aware'进行json序列化如果您使用的是 JSON,那么我假设您是通过 REST 端点提供结果的。然后发生的事情是您将 Patient
实体传递回 REST 服务。当 REST 服务(在本例中为 Jersey)时,serializes
Patient
实体会触及所有属性,甚至遍历它们,以便构建尽可能完整的树。为了做到这一点,每次 Jersey 遇到一个尚未初始化的 属性 时,Hibernate 都会再次调用数据库。这只有在 EntityManager
尚未关闭时才有可能。
这就是您必须安装 OpenEntityManagerInViewFilter
的原因。没有它,当您退出服务层时 EntityManager
将关闭,您会得到一个 LazyInitializationException
。 OpenEntityManagerInViewFilter
在视图级别打开 EntityManager
并保持打开状态,直到 HTTP 请求完成。因此,虽然它看起来像是一个修复,但实际上并不是,因为正如您所见,当您无法控制谁在访问您的实体的属性时,在本例中 Jersey
,您最终会加载您没有加载的内容'不想加载。
最好删除 OpenEntityManagerInViewFilter
并找出您想要 Jersey
序列化的内容。一旦弄清楚了,至少有两种方法可以处理它。 IHMO,"best practice" 是要有 DTO,即数据传输对象。这些是不是实体但具有几乎相同字段的 POJO。在这种情况下,PatientDTO
将包含除 phoneType
属性 之外的所有内容(或者可能只是 Id)。您将在构造函数中将 Patient
传递给它,它会复制您希望 Jersey 序列化的字段。然后,您的服务层将负责返回 DTO 而不是 Entities
,至少对于 REST 端点而言。您的客户将获得代表这些 DTO 的 JSON 图表,让您更好地控制进入 JSON 的内容,因为您将 DTO 与 Entities
.
另一种选择是使用 JSON 注释来防止 Jersey 尝试序列化您不想序列化的属性,例如 phoneType
,但这最终会成为问题。会有相互冲突的要求,你永远无法很好地解决它。
虽然一开始制作 DTO 似乎是一种可怕的痛苦,但它并没有看起来那么糟糕,甚至当您想要序列化对客户端更友好的值时,它甚至会有所帮助。因此,我的建议是丢掉 OpenEntityManagerInViewFilter
并构建一个适当的服务层 returns DTO,或有时称为视图对象。
参考文献:What is Data Transfer Object?
REST API - DTOs or not?
Gson: How to exclude specific fields from Serialization without annotations