JPA:有时会错过合并

JPA:有时会错过合并,jpa,eclipselink,Jpa,Eclipselink,我有一个带有主键(ID)的作业实体。 我坚持使用它来设置id。 然后我更新一个需要ID的字段(ImgWorkName) 大多数情况下(1000次中有999次),一切都正常。 但有时(比如1000次中的1次),当我在JPA中进行后续查询时,更新的字段还没有设置! 该字段位于数据库中(因此,如果重新启动应用程序,我总是会看到更新的字段) 我是一个有经验的程序员。 我已经为这个问题挣扎了一年多了,所以我真的很高兴能得到一些帮助 这个问题在3-4年前开始出现,但很少出现,然后慢慢地,这个错误出现得越来越

我有一个带有主键(ID)的作业实体。 我坚持使用它来设置id。 然后我更新一个需要ID的字段(ImgWorkName)

大多数情况下(1000次中有999次),一切都正常。 但有时(比如1000次中的1次),当我在JPA中进行后续查询时,更新的字段还没有设置! 该字段位于数据库中(因此,如果重新启动应用程序,我总是会看到更新的字段)

我是一个有经验的程序员。 我已经为这个问题挣扎了一年多了,所以我真的很高兴能得到一些帮助

这个问题在3-4年前开始出现,但很少出现,然后慢慢地,这个错误出现得越来越频繁,所以现在几乎每天都会发生

一个月前,我将服务器从Ubuntu12升级到Ubuntu16——没有区别

一个月前,我从Java6升级到Java8——没有区别

然后我将已有7年历史的JPA库升级到JPA2.1和最新的Tomcat7——没有区别

这些年来,数据库的规模越来越大。MySQL作业表中现在大约有100万行,但查询只返回大约600行。 对我来说,这似乎是一个JPA错误,但我找不到有类似问题的人

下面是相关的代码片段。提前谢谢

@Entity
@Table(name = "job")
public class Job implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "id", nullable = false)
  private Integer id;

  @Column(name = "imgOrgName", length = 300)
  private String imgOrgName;

  @Column(name = "imgWorkName", length = 100)
  private String imgWorkName;

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

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

  // more fields, getters and setters
}




public class FF {

  public EntityManagerFactory emf = javax.persistence.Persistence.createEntityManagerFactory(emfNavn);

  public EntityManager emq = emf.createEntityManager();

  // Bad workaround
  private LinkedHashMap<Integer, Job> jobIdTilJobWorkaround = new LinkedHashMap<Integer, Job>();


  private void scanCustomerTodoDir() {

        EntityManager emEnqueueJobs = emf.createEntityManager();
        try {
          // Add all files as jobs (we dont know the new jobids yet)
          emEnqueueJobs.getTransaction().begin();
          ArrayList<Job> jobs = new ArrayList<Job>();

          for (Path f : subfiles) {

            Job j = new Job();
            j.file_tmp = f;
            j.setTodoFileSize((int) f.toFile().length());
            j.setCreated(new Date());
            j.setUser(user);
            j.setService(service);
            j.setImgOrgName(imgOrgName);
            j.setState("QUEUED");
            jobs.add(j);
            emEnqueueJobs.persist(j);
          }

          // commit to get all jobids
          emEnqueueJobs.getTransaction().commit();


          for (Job j : jobs) {
            emEnqueueJobs.getTransaction().begin();
            // Use the Job ID to decide file name
            File fn = new File("/some/path/and/stuff_"+j.getId()); 
            j.setImgWorkName(fn.getName());     // This update gets lost!    
            emEnqueueJobs.merge(j);

            emEnqueueJobs.getTransaction().commit();

            jobIdTilJobWorkaround.put(j.getId(), j);  // BAD workaround for unfinishedJobsq missing the ImgWorkName in the merge!    
          }
        } finally {
          emEnqueueJobs.close();
        }
  }

  public Query unfinishedJobsq = emq.createNativeQuery("select * from ff.job WHERE state = 'QUEUED' OR state = 'GROUPWAIT' ORDER BY id DESC", Job.class);

  private void scanServiceproviderDoneDir() {
    List<Job> unfinishedJobs = unfinishedJobsq.getResultList();
    for (Job j : unfinishedJobs) try {

      if (j.getImgWorkName() == null) { // error! This happens sometimes!
        Job j2 = jobIdTilJobWorkaround.get(j.getId());
        // BAD workaround code for broken JPA (missing the ImgWorkName) here
        j = j2;
      }

      if (...) {   // The commit below always works/merges
        EntityManager emJobState = emf.createEntityManager();
        emJobState.getTransaction().begin();
        j.setState("DONE");
        long str = doneFile.length();
        j.setDoneFileSize((int) str);
        j.setDone(new Date());
        emJobState.merge(j);
        emJobState.getTransaction().commit();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

}

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="ffprod" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>ff.db.User</class>
    <class>ff.db.Job</class>
    <properties>
      <property name="eclipselink.jdbc.native-sql" value="false"/>
      <property name="eclipselink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="eclipselink.jdbc.url" value="jdbc:mysql://localhost/ff"/>
      <property name="eclipselink.jdbc.user" value="ff"/>
      <property name="eclipselink.logging.level" value="INFO"/>
      <property name="eclipselink.logging.timestamp" value="false"/>
      <property name="eclipselink.logging.exceptions" value="true"/>
      <property name="eclipselink.logging.thread" value="false"/>
    </properties>
  </persistence-unit>
@实体
@表(name=“job”)
公共类作业实现可序列化{
@身份证
@GeneratedValue(策略=GenerationType.IDENTITY)
@基本(可选=假)
@列(name=“id”,nullable=false)
私有整数id;
@列(name=“imgOrgName”,长度=300)
私有字符串imgOrgName;
@列(name=“imgWorkName”,长度=100)
私有字符串imgWorkName;
@列(name=“created”)
@时态(TemporalType.TIMESTAMP)
创建私人日期;
@列(name=“todoFileSize”)
私有整数todoFileSize;
//更多字段、getter和setter
}
公共类FF{
公共EntityManagerFactory emf=javax.persistence.persistence.createEntityManagerFactory(emfNavn);
public EntityManager emq=emf.createEntityManager();
//糟糕的解决方法
私有LinkedHashMap jobIdTilJobWorkaround=新LinkedHashMap();
专用无效扫描程序CustomerToDoDir(){
EntityManager emEnqueueJobs=emf.createEntityManager();
试一试{
//将所有文件添加为作业(我们还不知道新的作业ID)
emEnqueueJobs.getTransaction().begin();
ArrayList作业=新建ArrayList();
用于(路径f:子文件){
作业j=新作业();
j、 文件_tmp=f;
j、 setTodoFileSize((int)f.toFile().length());
j、 setCreated(新日期());
j、 setUser(用户);
j、 设置服务(服务);
j、 setimgorgnaname(imgorgnaname);
j、 设置状态(“排队”);
增加(j);
持久化(j);
}
//承诺获取所有作业ID
emEnqueueJobs.getTransaction().commit();
对于(作业j:作业){
emEnqueueJobs.getTransaction().begin();
//使用作业ID确定文件名
File fn=new File(“/some/path/和/stuff_u2;”+j.getId());
j、 setImgWorkName(fn.getName());//此更新丢失!
合并(j);
emEnqueueJobs.getTransaction().commit();
jobIdTilJobWorkaround.put(j.getId(),j);//未完成的jobsq在合并中缺少imgworksname的错误解决方法!
}
}最后{
expndtw-1.jobs.close();
}
}
public Query unfinishedJobsq=emq.createNativeQuery(“从ff.job中选择*,其中state='QUEUED'或state='GROUPWAIT'按id DESC排序”,job.class);
私有void scanServiceproviderDoneDir(){
List unfinishedJobs=unfinishedJobsq.getResultList();
对于(作业j:未完成的作业)请尝试{
如果(j.getImgWorkName()==null){//error!有时会发生这种情况!
Job j2=jobIdTilJobWorkaround.get(j.getId());
//这里的坏JPA解决方案代码(缺少ImgWorkName)
j=j2;
}
如果(…){//下面的提交始终有效/合并
EntityManager emJobState=emf.createEntityManager();
emJobState.getTransaction().begin();
j、 设定状态(“完成”);
long str=doneFile.length();
j、 setDoneFileSize((int)str);
j、 设置完成(新日期());
emJobState.merge(j);
emJobState.getTransaction().commit();
}
}捕获(例外e){
e、 printStackTrace();
}
}
}
org.eclipse.persistence.jpa.PersistenceProvider
ff.db.User
ff.db.Job

将整个
scannerToDoDir
代码封装在一个事务中不是更安全吗?您不需要提交事务来获取ID,一个
flush
就足够了。默认情况下,EclipseLink使用持久化单元缓存,在实体管理器之间共享;它充当本地实体管理器缓存的二级缓存。有可能你在那里得到了过时的条目。尝试在持久化单元XML中设置
NONE
,看看会发生什么。除此之外,搜索“MySQL陈旧数据”;对于类似的行为,似乎有相当多的点击率。示例:另请参见:我不知道从REPEATABLE-READ模式更改为READ-committed模式将如何改变任何事情-除非您确切知道问题的原因,否则这只是一个偶然的机会,这两种模式之间的差异似乎不太可能。正如第一次提到的,持久化是一个独立于更新的事务,因此,如果读取操作是一个独立的线程,那么它可以拾取新实例,但不能拾取更新。将它们放在一个事务中将确保一个单独的操作将获得全部或全部。您对本机查询使用emq,而对其他所有查询使用不同的/新的Em。由于EntityManager有自己的缓存用于镜像事务,因此您需要