JPA:有时会错过合并
我有一个带有主键(ID)的作业实体。 我坚持使用它来设置id。 然后我更新一个需要ID的字段(ImgWorkName) 大多数情况下(1000次中有999次),一切都正常。 但有时(比如1000次中的1次),当我在JPA中进行后续查询时,更新的字段还没有设置! 该字段位于数据库中(因此,如果重新启动应用程序,我总是会看到更新的字段) 我是一个有经验的程序员。 我已经为这个问题挣扎了一年多了,所以我真的很高兴能得到一些帮助 这个问题在3-4年前开始出现,但很少出现,然后慢慢地,这个错误出现得越来越频繁,所以现在几乎每天都会发生 一个月前,我将服务器从Ubuntu12升级到Ubuntu16——没有区别 一个月前,我从Java6升级到Java8——没有区别 然后我将已有7年历史的JPA库升级到JPA2.1和最新的Tomcat7——没有区别 这些年来,数据库的规模越来越大。MySQL作业表中现在大约有100万行,但查询只返回大约600行。 对我来说,这似乎是一个JPA错误,但我找不到有类似问题的人 下面是相关的代码片段。提前谢谢JPA:有时会错过合并,jpa,eclipselink,Jpa,Eclipselink,我有一个带有主键(ID)的作业实体。 我坚持使用它来设置id。 然后我更新一个需要ID的字段(ImgWorkName) 大多数情况下(1000次中有999次),一切都正常。 但有时(比如1000次中的1次),当我在JPA中进行后续查询时,更新的字段还没有设置! 该字段位于数据库中(因此,如果重新启动应用程序,我总是会看到更新的字段) 我是一个有经验的程序员。 我已经为这个问题挣扎了一年多了,所以我真的很高兴能得到一些帮助 这个问题在3-4年前开始出现,但很少出现,然后慢慢地,这个错误出现得越来越
@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有自己的缓存用于镜像事务,因此您需要