无法刷新 Vaadin JPAContainer 嵌套属性

Posted

技术标签:

【中文标题】无法刷新 Vaadin JPAContainer 嵌套属性【英文标题】:Cannot refresh Vaadin JPAContainer nested property 【发布时间】:2014-12-01 00:31:41 【问题描述】:

于 2014-12-02 修订 - eclipselink/mysql 的 persistence.xml 和 wildfly 配置 于 2014-12-02 修订 - 更好的问题信息、建议的完整代码和屏幕截图。

我在一对多关系中有两个 MYSQL 表。我使用绑定到关联 JPAContainer(productsContainer、priceContainer)的两个表(tbl_products、tbl_prices)在 Vaadin 中显示它们。在 tbl_prices 中,我使用 pricesContainer.addNestedContainerProperty() 显示第一个表中的一个字段。

问题是,当我更改 tbl_products 中的 QTY 字段值时,更改会立即反映到数据库中,但是 tbl_prices 中的嵌套属性在浏览器刷新之前永远不会知道更改。 (pricesContainer.getItem(itemId).getProperty("product.qty") 返回旧值)。清楚地显示在帖子末尾的数量更新后的屏幕截图中。

tbl_prices.refreshRowCache() 和 pricesContainer.refresh() 没有帮助,尽管容器刷新应该刷新数据库中的值)

对于如何解决此问题,我将不胜感激。无法在 vaadin.com 论坛上获得答案。以下是实体的配置方式:

@Entity
@Table(name="products")
@NamedQuery(name="Product.findAll", query="SELECT p FROM Product p")
public class Product implements Serializable 
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(unique=true, nullable=false)
    private int id;

    @Column(nullable=false, length=255)
    private String name;

    @Column(nullable=false)
    private int qty;

    //bi-directional many-to-one association to Price
    @OneToMany(mappedBy="product")
    private List<Price> prices;

    ... constructor, getters and setters


@Entity
@Table(name="prices")
@NamedQuery(name="Price.findAll", query="SELECT p FROM Price p")
public class Price implements Serializable 
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(unique=true, nullable=false)
    private int id;

    @Column(nullable=false, length=255)
    private String description;

    @Column(nullable=false, precision=10, scale=2)
    private BigDecimal price;

    //bi-directional many-to-one association to Product
    @ManyToOne
    @JoinColumn(name="prodid", nullable=false)
    private Product product;

    @Transient
    private BigDecimal value;
    public BigDecimal getValue() 
        return this.price.multiply(new BigDecimal(product.getQty()));
    

    ... constructor, getters and setters


public class UI_Tables_Test extends CustomComponent 
    public static final String PERSISTENCE_UNIT = "vaadin_sandbox";
    public JPAContainer<Product> productsContainer;
    public JPAContainer<Price> pricesContainer;
    private FieldGroup formFieldGroup;

    public UI_Tables_Test() 
        buildMainLayout();
        setCompositionRoot(mainLayout);

        // Containers
        productsContainer = JPAContainerFactory.make(Product.class, PERSISTENCE_UNIT);
        pricesContainer   = JPAContainerFactory.make(Price.class, PERSISTENCE_UNIT);
        pricesContainer.addNestedContainerProperty("product.*");

        // Tables
        tbl_products.setContainerDataSource(productsContainer);
        tbl_products.setSelectable(true);
        tbl_products.setImmediate(true);

        tbl_prices.setContainerDataSource(pricesContainer);
        tbl_prices.setSelectable(true);
        tbl_prices.setImmediate(true);

        // Columns
        tbl_products.setVisibleColumns(new Object[] "id","name", "qty");
        tbl_prices.setVisibleColumns(new Object[] "id", "product.name", "product.qty", "price", "value");

        // Form
        formFieldGroup = new FieldGroup(new BeanItem<Product>(new Product()));
        formFieldGroup.setBuffered(false);
        formFieldGroup.bind(tf_name, "name");
        formFieldGroup.bind(tf_qty, "qty");

        // ValueChangeListener to set form data source
        tbl_products.addValueChangeListener(new Property.ValueChangeListener() 
            private static final long serialVersionUID = 7133249924369468095L;

            @Override
            public void valueChange(ValueChangeEvent event) 
                Object selectedrow = event.getProperty().getValue();
                if (selectedrow != null) 
                    formFieldGroup.setItemDataSource(tbl_products.getItem(selectedrow));
                
            
        );

        // Property ValueChangeListener to refresh Prices table
        productsContainer.getItem(2).getItemProperty("qty").addValueChangeListener(new Property.ValueChangeListener() 
            @Override
            public void valueChange(ValueChangeEvent event) 
                refreshTable2();
            
        );
    

    public void refreshTable2() 
        // trying various refresh methodologies 
        pricesContainer.refresh();
        pricesContainer.getEntityProvider().refreshEntity(pricesContainer.getItem(3).getEntity());
        tbl_prices.refreshRowCache();

        // trying removing and adding nested container property again
        pricesContainer.removeContainerProperty("product.*");
        pricesContainer.addNestedContainerProperty("product.*");        

        // trying resetting table2 container data source
        tbl_prices.setContainerDataSource(pricesContainer);
        pricesContainer.addNestedContainerProperty("product.*");        
        tbl_prices.setVisibleColumns(new Object[] "id", "product.name", "product.qty", "price", "value");

        // popup message to display the values read from the containers
        String msg = "Product container Qty= " +
                     String.valueOf(productsContainer.getItem(2).getEntity().getQty()) + "\n" +
                     "Price container Qty= " +
                     String.valueOf(pricesContainer.getItem(3).getEntity().getProduct().getQty()) + "\n" +
                     pricesContainer.getContainerProperty(3, "product.qty");

        Notification.show("Test", msg, Notification.Type.ERROR_MESSAGE);
    

    ... build layout

这是两个屏幕截图(dropbox 链接,因为声誉点不足以发布图片)

数量更新前: https://www.dropbox.com/s/ej459r0v80popsb/before_quantity_update.png?dl=0

数量更新后: https://www.dropbox.com/s/ryl358pyymguonl/after_quantity_update.png?dl=0

这里是persistence.xml和相关的wildfly配置

persistence.xml
---------------
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" 
    xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="vaadin_sandbox" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <non-jta-data-source>java:/jdbc/vaadin_sandbox</non-jta-data-source>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <shared-cache-mode>NONE</shared-cache-mode>
        <properties>
            <property name="eclipselink.deploy-on-startup" value="True" />
            <property name="eclipselink.target-server" value="JBoss"/>
            <property name="eclipselink.logging.level" value="FINE" />
        </properties>
    </persistence-unit>
</persistence>

wildfly config:
--------------
eclipselink.jar in /usr/local/wildfly-8.1.0.Final/modules/system/layers/base/org/eclipse/persistence/main
corresponding module.xml
<module xmlns="urn:jboss:module:1.1" name="org.eclipse.persistence">
    <resources>
        <resource-root path="jipijapa-eclipselink-1.0.1.Final.jar"/>
        <resource-root path="eclipselink.jar"/>
    </resources>
    <dependencies>
    ...
    </dependencies>
</module>

workaround for  issueid=414974
$ bin/jboss-cli.sh —connect
[standalone@localhost:9990 /] /system-property=eclipselink.archive.factory:add(value=org.jipijapa.eclipselink.JBossArchiveFactoryImpl)

mysql-connector-java-5.1.30-bin.jar in /usr/local/wildfly-8.1.0.Final/modules/system/layers/base/com/mysql/main
corresponding module.xml
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="com.mysql">
    <resources>
        <resource-root path="mysql-connector-java-5.1.30-bin.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
    </dependencies>
</module>

$ bin/jboss-cli.sh --connect
[standalone@localhost:9990 /] /subsystem=datasources/jdbc-driver=mysql:add(driver-name=mysql,driver-module-name=com.mysql,driver-class-name=com.mysql.jdbc.Driver)
[standalone@localhost:9990 /] /subsystem=datasources/data-source=vaadin_sandbox:add(driver-name=mysql, user-name=secret, password=secret, connection-url=jdbc:mysql://localhost:3306/vaadin_sandbox?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8, min-pool-size=5, max-pool-size=15, jndi-name=java:/jdbc/innodron, enabled=true, validate-on-match=true, valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker, exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter)

【问题讨论】:

您能否发布一些示例代码,说明如何更新 table1 中的数量并尝试刷新 table2?只有这两个实体有点难以解决问题。谢谢 建议添加更多信息。谢谢。我通常希望 table2 (pricesTable) 收到有关 nestedContainerProperty 更改的通知。然而事实并非如此。因此,一旦 productContainer qty 属性更改,我就会强制刷新。但是我也无法在那里得到预期的结果) 嗨。感谢您的屏幕截图,但我需要的是实际代码(您如何设置所有内容以及如何进行更新)。我的猜测是您在代码中提供了一些阻止正确刷新的配置,因为我从未遇到过这样的问题。 我已经修改了帖子,在发布屏幕截图时包含了设置 UI 的代码。如果这不是你需要的,你能告诉我我还应该发布什么吗? persistence.xml 可能是什么?谢谢。 好的,我一有空就会尝试重现您的问题。我对此很感兴趣,因为这种情况我迟早会介入。 【参考方案1】:

修订:在这个问题上花费几个小时后记录我的发现

常见:Wildfly 8.1.0.Final、MySQL 5.6.20、Vaadin 7.3.5、EclipseLink 2.5.2、javax.persistence 2.1.0、mysql-connector-java 5.1.34

案例一)persistence.xml transaction-type="RESOURCE_LOCAL", shared_cache_mode="NONE"

在这种情况下,清除 EntityManagerFactory 缓存似乎是解决问题的唯一方法。

public void refreshTable2() 
    //Evict Entity Manager Cache for the specific item
    em.getEntityManagerFactory().getCache().evict(Price.class, 3);
    // if container is not refreshed, tbl_prices is not updated in the UI.
    pricesContainer.refresh(); 

    // popup message to display the values read from the containers
    String msg = "Product container Qty= " +
                 String.valueOf(productsContainer.getItem(2).getEntity().getQty()) + "\n" +
                 "Price container Qty= " +
                 String.valueOf(pricesContainer.getItem(3).getEntity().getProduct().getQty()) + "\n" +
                 pricesContainer.getContainerProperty(3, "product.qty");

    Notification.show("Test", msg, Notification.Type.ERROR_MESSAGE);

案例2)persistence.xml transaction-type="RESOURCE_LOCAL", shared_cache_mode="ALL"

在这种情况下不需要逐出 EntityManagerFactory 缓存,但 pricesContainer 需要手动刷新嵌套属性以刷新其值。

public void refreshTable2() 
    // if container is not refreshed, tbl_prices is not updated in the UI.
    pricesContainer.refresh(); 

案例 3) persistence.xml transaction-type="JTA", shared_cache_mode="ALL" 或 "NONE"。 (查看this blog entry 了解如何让 Vaadin 处理 JTA 事务)

对于这两种情况都不需要逐出 EntityManagerFactory 缓存,但 pricesContainer 需要手动刷新嵌套属性以刷新其值。上边是 shared_cache_mode 可以设置为“NONE”,这似乎是 Vaadin JPAContainer 玩得好(根据 Vaadin 论坛中的几个建议)所必需的

public void refreshTable2() 
    // if container is not refreshed, tbl_prices is not updated in the UI.
    pricesContainer.refresh(); 

欢迎任何关于如何在没有人工干预的情况下刷新嵌套属性的建议。在实际项目中,手动刷新需要一个 Container.addItemSetChangeListener() 用于源容器,一个 Container.getItem(i).addValueChangeListener() 用于容器中的每个项目 - 这会产生巨大的开销。

【讨论】:

现在你可以坚持使用它,但驱逐缓存是一个相当残酷的解决方案。如果你能提供一些关于如何准备表格、容器以及如何进行更新的代码。问候 我怀疑驱逐不是一个优雅的解决方案。我还添加了 persistence.xml 和相关的 Wildfly 配置步骤。 (表和容器属性已经在实体之后,表单更新产品表中的“数量”字段)。感谢您的帮助。 感谢@MarcelloGarini 鼓励不要停留在“残酷的解决方案”上并深入挖掘。

以上是关于无法刷新 Vaadin JPAContainer 嵌套属性的主要内容,如果未能解决你的问题,请参考以下文章

Vaadin 中的休眠会话错误

Vaadin:JPA 容器的新手

为啥 spring-vaadin 忘记了我设置的 locale,但在页面刷新后突然记住了?

Vaadin 7 在执行后台线程后不刷新 UI(仅当它需要超过 5 分钟时)

Vaadin Flow:删除 PollListener

JPA 容器地址簿缺少工件