java - Hibernate 和 Thymeleaf 无限递归

标签 java javascript hibernate thymeleaf

我有 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>

这是错误: enter image description here

在解析为 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 规范,它没有考虑索引属性的存在和一些其他细节。您可以查看 Introspectorsource code了解他们如何提取此信息并改进此解决方案。

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

关于java - Hibernate 和 Thymeleaf 无限递归,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29895541/

相关文章:

java - 当我想从文件创建 ArrayList 时,我是否要从文件实例化对象? java

java - 当我在非事务中使用事务bean时,Spring会建立与数据库的连接吗?

javascript - 如何在 'shouting' 时删除部分命令?

javascript - 更新 Javascript 数组,以便我可以将它传递给 PHP

java - 带有接口(interface)模型对象的通用 Hibernate Dao

java - 为什么两个日期类一个在 java.util.Date 和 java.sql.Date 中?

javascript - 如何将数字附加到函数名称以创建多个函数?

java - h2 上的 hibernate ID 生成器 AUTO_INCREMENT 和集群中的 MySQL

java - Hibernate 仅在类为 Final 时才起作用 [否则抛出 SingleTableEntityPersister]

java - 指定应用程序的工作线程数