Java Hibernate与Thymeleaf无限递归

Java Hibernate与Thymeleaf无限递归,java,javascript,hibernate,thymeleaf,Java,Javascript,Hibernate,Thymeleaf,我有两个类,其中一个atribute映射为一对一/多对一关系。问题是,当我执行select并传递到te视图时,我想用Thymeleaf将对象解析为javascript,但它会循环无限的关系原因。 我的课程: 职业球员: @Entity @Table(name = "player") public class Player { @Id @Column(name = "id") @GeneratedValue private int id; @Column(name = "level") pri

我有两个类,其中一个atribute映射为一对一/多对一关系。问题是,当我执行select并传递到te视图时,我想用Thymeleaf将对象解析为javascript,但它会循环无限的关系原因。 我的课程: 职业球员:

@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...
}
然后,我将Player对象传递给view and console。使用javascript记录它:

<script th:inline="javascript">
/*<![CDATA[*/
    console.log([[${player}]]);
/*]]>*/
</script>
这就是错误:


当解析到javascript时,我如何防止双向关系,比如忽略所有InventoryItems中的播放器对象?

我今天遇到了这个问题,我想Thymeleaf并没有提供一个简单的解决方案。在将父对象的引用传递给Thymeleaf之前,您总是可以将其设置为null,但这似乎有点难看

在研究Thymeleaf的源代码时,我注意到它用于获取有关属性的信息,因此我们可以通过实现BeanInfo来隐藏一些属性。最简单的方法是在bean的同一个包上创建一个类,并在名称后面附加BeanInfo。您可以扩展SimpleBeanifo并只实现您在本例中感兴趣的方法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:

现在我们实现了一个泛型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);
    }
}
最后,注释要隐藏的属性的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...
}
就在这里。现在你可以随意改变你的属性,你的玩家属性不会被Thymeleaf看到

<> P>一个重要的问题是,它不是100%的java bean规范,它不考虑索引属性的存在和其他一些细节。你可以看一看内省者,看看他们是如何提取这些信息并改进这个解决方案的


但是,就个人而言,我认为这应该在Thymeleaf内部解决。Thymeleaf是一个非常优雅的工具,不得不求助于这种解决方法让我有点难过。

我使用了您的第一个建议,将对父对象的引用设置为null,这就成功了
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);
    }
}
public class InventoryItemBeanInfo extends HiddenPropertyAwareBeanInfo {
    public InventoryItemBeanInfo() {
        super(InventoryItem.class);
    }
}
@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...
}