0

Revised on 2014-12-02 - persistence.xml and wildfly config for eclipselink/mysql Revised on 2014-12-02 - better problem information, complete code as suggested and screenshots.

I have two MYSQL tables in a one-to-many relation. I display them in Vaadin using two tables (tbl_products, tbl_prices) bound to associated JPAContainers (productsContainer, pricesContainer). In tbl_prices, I display a field from the first table, using pricesContainer.addNestedContainerProperty().

The problem is, when I change the QTY field value in tbl_products, the change is immediately reflected to the database, however the nested property in tbl_prices is never aware of the change until a browser refresh. (pricesContainer.getItem(itemId).getProperty("product.qty") returns the old value). Clearly shown in the After quantity update screenshots at the end of the post.

tbl_prices.refreshRowCache() and pricesContainer.refresh() did not help, although a container refresh is supposed to refresh the values from DB)

I will appreciate any help on how to resolve this. Could not get an answer on vaadin.com forums. Here is how the entities are configured:

@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
}

Here is two screenshots (dropbox links since reputation points not enough to post images)

Before quantity update: https://www.dropbox.com/s/ej459r0v80popsb/before_quantity_update.png?dl=0

After quantity update: https://www.dropbox.com/s/ryl358pyymguonl/after_quantity_update.png?dl=0

And here is the persistence.xml and relevant wildfly configuration

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)
oscar
  • 123
  • 1
  • 6
  • Could you please post some example code of how you update quantity in table1 and trying to refresh table2? With just the two entities is a bit hard to figure the problem out. Thanks – MarcelloGarini Dec 01 '14 at 07:06
  • More information added as recommended. Thanks.I would normally expect the table2 (pricesTable) to get notified about the change in nestedContainerProperty. However it is not. Thus I force a refresh once productContainer qty property changes. However I cannot get the expected result there either) – oscar Dec 01 '14 at 22:50
  • Hi. Thanks for the screenshot but what I would need is the actual code (how you set up everything and how you make the updates). My guess is that you gave some config in your code that prevents a correct refresh, because such problem never occured to me. – MarcelloGarini Dec 02 '14 at 07:20
  • I had modified the post to include the code setting up the UI when posting the screenshots. If that is not what you need, can you tell me what else I should post? persistence.xml may be? Thanks. – oscar Dec 02 '14 at 08:34
  • alright, I'll try to reproduce your problem as soon as I get some free time. I'm quite interested in this since it's a situation I might step in too sooner or later. – MarcelloGarini Dec 03 '14 at 16:03

1 Answers1

0

Revision: documenting my findings after spending several hours on this issue

Common: 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

Case 1) persistence.xml transaction-type="RESOURCE_LOCAL", shared_cache_mode="NONE"

In this case clearing the EntityManagerFactory cache seems to be only solution to the problem.

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);
}

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

In this case no need to evict EntityManagerFactory cache, but pricesContainer needs a manual refresh for the nested property to refresh its value.

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

Case 3) persistence.xml transaction-type="JTA", shared_cache_mode="ALL" or "NONE". (Check this blog entry for how to make Vaadin work with JTA transactions)

For both these cases no need to evict EntityManagerFactory cache, but pricesContainer needs a manual refresh for the nested property to refresh its value. The upper side is shared_cache_mode can be set to "NONE", which seems to be required for Vaadin JPAContainer to play nice (according to several recommendations in Vaadin Forums)

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

Any suggestions are welcome on how to make the nested property refresh without manual intervention. In a real project a manual refresh will require one Container.addItemSetChangeListener() for the source container and one Container.getItem(i).addValueChangeListener() for every item in the container - which would create a massive overhead.

oscar
  • 123
  • 1
  • 6
  • For now you can stick with it but evicting the cache is quite a brutal solution. If you can provide me with some code on how you prepare the table, the container, and how you make the updates. Regards – MarcelloGarini Dec 02 '14 at 07:22
  • I was suspicious about evict not being an elegant solution. I also added persistence.xml and relevant wildfly config steps. (table and container properties are already above just after entities, form updates the "qty" field in products table). Thanks for your efforts to help. – oscar Dec 02 '14 at 08:57
  • Thanks to @MarcelloGarini for encouraging to not stop at the "brutal solution" and dig deeper. – oscar Dec 03 '14 at 12:22