2

I've been using Eclipselink in my project for a long time, but today I came across a problem, and I can not solve it.

The problem is this: at first I search my "Function" object in an instance of my program, after that in another instance I search and change that same record (changed the "Interval" and "Date" column) and saved. After this in the first instance I change only the "Interval", but in the database the "Date" field has been changed even if I have not changed, Eclipselink (in the first instance) generates in the UPDATE the change of the field "Date" only because the "Function" of the first instance is outdated with the database, whereas changes were made to the registry in the meantime, between searching the object and saving it.

When analyzing the querys generated in the database i noticed that Eclipselink generates a SELECT before each UPDATE, but in my case i would not need it, how can I make Eclipselink only add in the UPDATE the fields that had changes? And not those that differ from the database.

My Eclipselink configuration:

"javax.persistence.jdbc.driver", "com.mysql.cj.jdbc.Driver"
"javax.persistence.jdbc.url", "jdbc:mysql://{IP}:3306/{USER}?useTimezone=true&serverTimezone=America/Sao_Paulo&autoReconnect=true&zeroDateTimeBehavior=convertToNull"
"javax.persistence.jdbc.user", user
"javax.persistence.jdbc.password", password
"eclipselink.cache.shared.default", "false"
"eclipselink.logging.level", "WARNING"
"eclipselink.query-results-cache", "false"
"eclipselink.refresh", "true"
"eclipselink.weaving", "static"
"connection.autoReconnectForPools", "true"
"connection.autoReconnect", "true"

Code:

Function function = FunctionDAO.getFunctionByName("A");
function.setInterval(0);
...
EntityManager manager = {config};
manager.getTransaction().begin();
manager.merge(function);
manager.getTransaction().commit();
manager.close();

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name = "function")
public class Function extends ModelAudit implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "name")
    private String name;

    @Column(name = "interval")
    private Integer interval;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "date")
    private Date date;

    ...
    getters and setters

enter image description here

  • not "describe" code, but put is here – Jacek Cz May 09 '18 at 09:15
  • I added it up, I do not know if it helps – Matheus Sant Anna de Oliveira May 09 '18 at 11:15
  • And `Function` declaration? Few cases exist when "generates a SELECT before each UPDATE", for example lazy loading etc ... other is pesimistic/optimistic locking, or You are fighting witch cache. Many reasons are possible – Jacek Cz May 09 '18 at 14:12
  • Is this desktop application, or in servlet container, or in full JEE ? – Jacek Cz May 09 '18 at 14:12
  • It's a Library used in a web application and one service – Matheus Sant Anna de Oliveira May 09 '18 at 14:24
  • It isn't clear from your question or your code what you are doing or want to have happen. FunctionDAO.getFunctionByName("A"); presumably would result in a select from the database to load the Function into an EntityManager context. If function isn't in the context when merge is called, JPA requires the context to load it, which is why you see a select. If you want to change or fix this, you need to look at your use cases - using the same EM for loading as merging would be the ideal solution IMO – Chris May 09 '18 at 14:27
  • Web server is far more 'cache friendly' than network of desktop 'synchronised' only on relational database – Jacek Cz May 09 '18 at 14:27
  • Add that to your question, not as a comment – Chris May 09 '18 at 14:29
  • @Chris Thanks for explanation, I agree with You (maybe I'm not fluent in English) – Jacek Cz May 09 '18 at 14:32
  • I added in the question the requested code snippet and improved the explanation of my problem. – Matheus Sant Anna de Oliveira May 09 '18 at 14:46

1 Answers1

2

I don't like DAO objects as this problem is common one when using the find/save pattern, and why optimistic locking is strongly recommended. Any number of modifications could have occurred from the time you read in the entity up until the time you call save/merge on it, and if your method is wrapped in its own transaction and just uses em.merge(), your instance overwrites what is in the database entirely. The longer you keep an entity around, the better the chance some other process has changed it and made your copy of the data stale.

Options:

  1. Use some form of optimistic locking. When reading in an entity, it will have versioning so that when you call em.merge if the version in the database is ahead of what you pass in, it will give an exception to notify you of a potential conflict.

  2. Implement DAO changeValue methods that only update selection of fields within the entity. For example a setIntervalByName(String name, Integer interval) method which could use a JPA update query to avoid the select if you didn't care to read in the entity, or use the same functionality similar to your getFunctionByName method.

Using both allows passing around the object for complex changes such as for REST api that might serialize the entity for get/put requests, while still allowing optimization to avoid reading in the entity graph for simple changes elsewhere.

Chris
  • 20,138
  • 2
  • 29
  • 43
  • +1 "I don't like DAO objects as this problem is common..." A "few years" in Java world my thinking change perspective to more pure JEE (7 & 8), with reduced intermediate layers, according to evangelism of Adam Bien – Jacek Cz May 09 '18 at 16:29
  • One doubt, I read the Eclipselink docs and located the "Annotation" **ExistenceChecking** it would not solve my problem? Since it could disable SELECT before the UPDATE which is what is causing me error. I tried to use it but it did not work for me. [Link](http://www.eclipse.org/eclipselink/documentation/2.7/jpa/extensions/annotations_ref.htm#BABICJEJ) – Matheus Sant Anna de Oliveira May 09 '18 at 20:28
  • Select is not causing you any errors, it is what is allowing the provider to determine A) that the entity exists and B) what changed. setting the existence checking policy tells it how to solve A, but it still needs a way to figure out what to send across in the update statement. If the entity doesn't exist in one of its caches, it won't have a way to figure out what has changed; it either has to read it in and compare or send it all across, overwriting the entire row with your potentially stale state. You are still stuck with dealing with stale data - use optimistic locking. – Chris May 09 '18 at 20:41
  • Following the documentation, I tried to use the `@OptimisticLocking annotation (type = OptimisticLockingType.CHANGED_COLUMNS)` annotation but the problem remains the same – Matheus Sant Anna de Oliveira May 14 '18 at 11:37
  • Changed_columns isn't going to help, as it looks at what has changed since the object was read into the context. If you are serializing an object around and then merging it into an empty context, the point you call merge is the reference point used for it to determine the changed fields. Try version locking, as the field gets passed with your entity and shows when your entity is stale. – Chris May 14 '18 at 13:45
  • I try to use the `VERSION_COLUMN` on `@OptimisticLocking` but the `@Version` don't work on all types of columns, only on int and timestamp. – Matheus Sant Anna de Oliveira May 14 '18 at 14:28