我有 2 个类,其中一个属性映射到 OneToMany/ManyToOne 关系。问题是,当我进行选择并传递给 te View 时,我想使用 Thymeleaf 将对象解析为 javascript,但它循环无限地导致关系。 我的类(class): 类玩家:
@Entity
@Table(name = "player")
public class Player {
@Id
@Column(name = "id")
@GeneratedValue
private int id;
@Column(name = "level")
private int level;
@Column(name = "experience")
private int experience;
@OneToMany(mappedBy="player", cascade = CascadeType.ALL)
private List<InventoryItem> inventory;
// Constructor
public Player() {
}
// Getters & Setters...
}
类 InventoryItem:
@Entity
@Table(name = "inventory_item")
public class InventoryItem {
@Id
@GeneratedValue
@Column(name = "id")
private int id;
@ManyToOne
@JoinColumn(name="id_player")
private Player player;
public InventoryItem() {
}
//Getters and Setters...
}
然后我将 Player 对象传递给 View 并使用 javascript 控制台记录它:
<script th:inline="javascript">
/*<![CDATA[*/
console.log([[${player}]]);
/*]]>*/
</script>
这是错误:
在解析为 javascript 时如何防止双向关系,比如忽略所有 InventoryItems
中的 Player
atrubute?
最佳答案
我今天遇到了这个问题,我想 Thymeleaf 没有为此提供简单的解决方案。在将其传递给 Thymeleaf 之前,您始终可以将对父级的引用设置为 null
,但这看起来有点丑陋。
调查 Thymeleaf 的源代码,我注意到它使用 Introspector获取有关属性的信息,因此我们可以通过实现 BeanInfo
来隐藏一些属性。最简单的方法是在您的 bean 的同一个包上创建一个类,并在名称后附加 BeanInfo
。您可以扩展 SimpleBeanInfo
并仅实现您感兴趣的方法(在本例中为 getPropertyDescriptors
)。
在您的示例中,您可以:
public class InventoryItemBeanInfo extends SimpleBeanInfo {
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
try {
PropertyDescriptor id = new PropertyDescriptor("id", InventoryItem.class);
PropertyDescriptor[] descriptors = {id};
return descriptors;
} catch (IntrospectionException e) {
throw new Error(e.toString());
}
}
}
这样,Thymeleaf 将看不到您的 Player
属性,并且您将不再有无限递归。
此解决方案的缺点是您必须记住,每次更改 InventoryItem
bean 的属性时,您都必须转到 BeanInfo
并添加新属性
所以我想到了另一个解决方案。它有点复杂,但一旦设置好,就更容易维护。
我们首先创建一个注释来指示应该隐藏该属性。我们称它为 HiddenProperty
:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HiddenProperty {}
现在我们实现一个通用的 BeanInfo
类,它将使用反射检查 bean 并找到属性。我们还将检查我们的注释是否存在,当它存在时我们忽略该方法:
public class HiddenPropertyAwareBeanInfo extends SimpleBeanInfo {
private Class<?> beanClass;
private PropertyDescriptor[] descriptors;
public HiddenPropertyAwareBeanInfo(Class<?> beanClass) {
this.beanClass = beanClass;
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
if(descriptors != null) {
return descriptors;
}
Method[] methodList = beanClass.getMethods();
List<PropertyDescriptor> propDescriptors = new ArrayList<>();
for(Method m : methodList) {
if(Modifier.isStatic(m.getModifiers())) {
continue;
}
try {
if(m.getParameterCount() == 0 && !m.isAnnotationPresent(HiddenProperty.class)) {
if(m.getName().startsWith("get")) {
propDescriptors.add(new PropertyDescriptor(m.getName().substring(3), beanClass));
} else if(m.getName().startsWith("is")) {
propDescriptors.add(new PropertyDescriptor(m.getName().substring(2), beanClass));
}
}
} catch(IntrospectionException ex) {
continue;
}
}
descriptors = new PropertyDescriptor[propDescriptors.size()];
return propDescriptors.toArray(descriptors);
}
}
现在,创建扩展此类的 BeanInfo
:
public class InventoryItemBeanInfo extends HiddenPropertyAwareBeanInfo {
public InventoryItemBeanInfo() {
super(InventoryItem.class);
}
}
最后,注解要隐藏的属性的getter:
@Entity
@Table(name = "inventory_item")
public class InventoryItem {
@Id
@GeneratedValue
@Column(name = "id")
private int id;
@ManyToOne
@JoinColumn(name="id_player")
private Player player;
public InventoryItem() {
}
@HiddenProperty
public Player getPlayer() {
return this.player;
}
//Other getters and setters...
}
就是这样。现在您可以根据需要更改您的属性,并且您的 Player
属性将不会被 Thymeleaf 看到。
关于此解决方案,要说的一件重要事情是它不是 100% 符合 Java Beans 规范,它没有考虑索引属性的存在和一些其他细节。您可以查看 Introspector
的 source code了解他们如何提取此信息并改进此解决方案。
但是,就我个人而言,我认为这应该在 Thymeleaf 中得到解决。 Thymeleaf 是一个非常优雅的工具,不得不求助于这种解决方法让我有点难过。
关于java - Hibernate 和 Thymeleaf 无限递归,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29895541/