Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » EclipseLink » update in new transaction hangs program
update in new transaction hangs program [message #783694] Thu, 26 January 2012 07:08 Go to next message
Ari Meyer is currently offline Ari MeyerFriend
Messages: 136
Registered: July 2009
Senior Member
Hi,

I'm using Spring 2.5.6 and Eclipselink JPA 2.1.3 (also tested against
2.3.2) on Oracle 11.2, using a non-JTA datasource. I have a case where
I'm logging messages to a DB using JPA. I have a parent-child
relationship between the basic run info (ProcessRun) and each log
message (ProcessRunLog). Each ProcessRunLog (log message) must be
persisted and committed immediately, and there will be no updates to log
messages. Also, every update to the parent ProcessRun record (status
updates, etc.) must be committed immediately. The ProcessRunLogs are
being persisted and committed properly. ProcessRuns, though, while
initially persisting fine, hang the whole program when I try to update
them in a new transaction:

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public ProcessRun update(ProcessRun aProcessRun)
{
return _entityManager.merge(aProcessRun);
}

If I just let the updates commit along with the rest of the parent
transaction, everything goes through fine, but we need to see the
updates immediately (also, the ProcessRun updates can't be rolled back
if the parent transaction fails). It's strange, but no exception is
thrown -- it just hangs until I kill the process.

I've logged the Spring declarative transactions, and everything looks
fine, so I don't think it's a Spring issue. Here's the closest info
I've found, but I don't think the guy's answer is my issue:
http://stackoverflow.com/questions/8163458/spring-bean-hangs-on-method-with-transactional

Note that this is a single-threaded app, with no other process modifying
any of the data concurrently.

I also tried rewriting the update method to only update the relevant
data, even though I'm not cascading the MERGE, but got the same results:

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void update(ProcessRun aProcessRun)
{
// simple copy of only key fields rather than full merge
ProcessRun pr = _entityManager.find(ProcessRun.class,
aProcessRun.getId());
pr.setBatchRunId(aProcessRun.getBatchRunId());
pr.setProjectId(aProcessRun.getProjectId());
pr.setUser(aProcessRun.getUser());
pr.setStatus(aProcessRun.getStatus());
pr.setStartTime(aProcessRun.getStartTime());
pr.setEndTime(aProcessRun.getEndTime());
pr.setRowsProcessed(aProcessRun.getRowsProcessed());
}


Output just before it hangs:

[2012-01-23 01:10:06,029] DEBUG AnnotationTransactionAttributeSource
Adding transactional method [update] with attribute
[PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT]
[2012-01-23 01:10:06,851] DEBUG Slf4jSessionLog SELECT PROCESS_RUN_ID,
RUN_START_TIME, RUN_STATUS, PROCESS_TYPE_CODE, PROJ_ID, RUN_END_TIME,
RUN_BY_USER, ROWS_PROCESSED, BATCH_RUN_ID FROM CILSTAGE.PROCESS_RUN
WHERE (PROCESS_RUN_ID = ?)
bind => [210150]
[2012-01-23 01:10:06,936] DEBUG Slf4jSessionLog SELECT
PROCESS_TYPE_CODE, CREATE_USER, PROCESS_NAME, ACTIVE_FLAG, CREATE_DATE,
UPDATE_DATE, UPDATE_USER FROM CILSTAGE.PROCESS_TYPE WHERE
(PROCESS_TYPE_CODE = ?)
bind => [RMS]
[2012-01-23 01:10:07,029] DEBUG Slf4jSessionLog UPDATE
CILSTAGE.PROCESS_RUN SET RUN_STATUS = ?, RUN_START_TIME = ?,
RUN_END_TIME = ?, ROWS_PROCESSED = ? WHERE (PROCESS_RUN_ID = ?)
[2012-01-23 01:10:07,030] DEBUG Slf4jSessionLog bind =>
[COMPLETED_WITH_ERRORS, 2012-01-23 01:07:19.744, 2012-01-23
01:10:06.028, 24, 210150]


I'm attaching the 2 entities for your review.

Thanks,
Ari





package mil.army.usace.p2.entity;

import mil.army.usace.p2.constants.ProcessRunStatus;
import mil.army.usace.p2.util.ExtendedEqualsBuilder;
import mil.army.usace.p2.util.JpaUtils;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;


/**
* @version $Revision: 1275 $
* @author $Author: u4iesabm $
* Last Modified: $Date: 2011-07-28 18:16:54 -0500 (Thu, 28 Jul 2011) $
*/
@Entity
@Table(name="PROCESS_RUN", schema="CILSTAGE")
@SequenceGenerator(name="PROCESS_RUN_GEN", sequenceName="PROCESS_RUN_ID_SEQ", allocationSize=1)
public class ProcessRun
implements Serializable, Comparable<ProcessRun>
{
private static final long serialVersionUID = 1L;

@Id
@Column(name = "PROCESS_RUN_ID")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PROCESS_RUN_GEN")
private Long id;

@Column(name = "PROCESS_TYPE_CODE")
private String processTypeCode = null;

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="PROCESS_TYPE_CODE", insertable=false, updatable=false)
private ProcessType processType = null;

@Column(name="BATCH_RUN_ID")
private Long batchRunId;

@Column(name="PROJ_ID")
private Long projectId;

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="PROJ_ID", insertable=false, updatable=false)
private UsaceProject project = null;

@Column(name = "ROWS_PROCESSED")
private int rowsProcessed = 0;

@Column(name = "RUN_BY_USER")
private String user;

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

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

@Column(name = "RUN_STATUS")
@Enumerated(EnumType.STRING)
private ProcessRunStatus status;

@Transient
private boolean scheduleChangesMade = false;

// bi-directional many-to-one association to ProcessRunLog
@OneToMany(
cascade={CascadeType.REMOVE, CascadeType.REFRESH, CascadeType.DETACH},
mappedBy = "processRun",
orphanRemoval=true)
@OrderBy
private List<ProcessRunLog> processRunLogs;


public ProcessRun()
{
super();
}


// initializes required fields
public ProcessRun(ProcessType aProcessType, String user)
{
this();

this.setProcessType(aProcessType);
this.user = user;

this.startTime = new Timestamp(System.currentTimeMillis()); // current timestamp
this.status = ProcessRunStatus.STARTED;
}


public ProcessRun(ProcessType aProcessType, String user, UsaceProject aUsaceProject)
{
this(aProcessType, user);

this.setProject(aUsaceProject);
}


public ProcessRun(ProcessType aProcessType, String user, Long batchRunId)
{
this(aProcessType, user);

this.setBatchRunId(batchRunId);
}


public ProcessRun(ProcessType aProcessType, String user, Long projectId, Long batchRunId)
{
this(aProcessType, user);

this.setProjectId(projectId);
this.setBatchRunId(batchRunId);
}


@Override
public boolean equals(Object obj)
{
ExtendedEqualsBuilder anExtendedEqualsBuilder = new ExtendedEqualsBuilder(this, obj);
if (! anExtendedEqualsBuilder.isEquals())
return false;

ProcessRun aProcessRun = (ProcessRun) obj;

return anExtendedEqualsBuilder
.append(this.id, aProcessRun.id)
.isEquals();
}


@Override
public int hashCode()
{
return new HashCodeBuilder()
.append(this.id)
.toHashCode();
}


@Override
public int compareTo(ProcessRun aProcessRun)
{
return new CompareToBuilder()
.append(this.id, aProcessRun.id)
.toComparison();
}


@Override
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", this.id)
.append("processTypeCode", this.processTypeCode)
.append("batchRunId", this.batchRunId)
.append("projectId", this.projectId)
.append("status", this.status)
.append("scheduleChangesMade", this.scheduleChangesMade)
.append("rowsProcessed", this.rowsProcessed)
.append("user", this.user)
.append("startTime", this.startTime)
.append("endTime", this.endTime)
.toString();
}


public Long getId()
{
return this.id;
}


public void setId(Long id)
{
this.id = id;
}


public String getProcessTypeCode()
{
return this.processTypeCode;
}


public void setProcessTypeCode(String processTypeCode)
{
this.processTypeCode = processTypeCode;
}


public ProcessType getProcessType()
{
return this.processType;
}


public void setProcessType(ProcessType processType)
{
if (processType != null)
{
this.processType = processType;
this.processTypeCode = processType.getCode();
}
}


public Long getBatchRunId()
{
return this.batchRunId;
}


public void setBatchRunId(Long batchRunId)
{
this.batchRunId = batchRunId;
}


public Long getProjectId()
{
return this.projectId;
}


public void setProjectId(Long projectId)
{
this.projectId = projectId;
}


public UsaceProject getProject()
{
return this.project;
}


public void setProject(UsaceProject project)
{
if (project != null)
{
this.project = project;
this.projectId = project.getId();
}
}


public int getRowsProcessed()
{
return this.rowsProcessed;
}


public void setRowsProcessed(int rowsProcessed)
{
this.rowsProcessed = rowsProcessed;
}


public String getUser()
{
return this.user;
}


public void setUser(String user)
{
this.user = user;
}


public Date getEndTime()
{
return this.endTime;
}


public void setEndTime(Date endTime)
{
this.endTime = endTime;
}


public Date getStartTime()
{
return this.startTime;
}


public void setStartTime(Date startTime)
{
this.startTime = startTime;
}


public ProcessRunStatus getStatus()
{
return this.status;
}


public void setStatus(ProcessRunStatus status)
{
this.status = status;
}


public boolean isScheduleChangesMade()
{
return this.scheduleChangesMade;
}


public void setScheduleChangesMade(boolean scheduleChangesMade)
{
this.scheduleChangesMade = scheduleChangesMade;
}


public List<ProcessRunLog> getProcessRunLogs()
{
return this.processRunLogs;
}


public void setProcessRunLogs(List<ProcessRunLog> processRunLogs)
{
this.processRunLogs = processRunLogs;
}


public void add(ProcessRunLog aProcessRunLog)
{
this.processRunLogs =
JpaUtils.initialize(this.processRunLogs); // force init/load

this.processRunLogs.add(aProcessRunLog);
}
}






package mil.army.usace.p2.entity;

import mil.army.usace.p2.constants.LogLevel;
import mil.army.usace.p2.constants.RecordSource;
import mil.army.usace.p2.util.ExtendedEqualsBuilder;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;


/**
* @version $Revision: 1270 $
* @author $Author: u4iesabm $
* Last Modified: $Date: 2011-07-28 08:55:14 -0500 (Thu, 28 Jul 2011) $
*/
@Entity
@Table(name="PROCESS_RUN_LOG", schema="CILSTAGE")
@SequenceGenerator(name="PROCESS_RUN_LOG_GEN", sequenceName="LOG_ENTRY_ID_SEQ", allocationSize=1)
public class ProcessRunLog
implements Serializable, Comparable<ProcessRunLog>
{
private static final long serialVersionUID = 1L;

@Id
@Column(name = "LOG_ENTRY_ID")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PROCESS_RUN_LOG_GEN")
private Long id;

@Column(name = "TABLE_NAME")
@Enumerated(EnumType.STRING)
private RecordSource recordSource;

@Column(name = "FK_ID1")
private Long fkId1;

@Column(name = "FK_ID2")
private Long fkId2;

@Column(name = "LOG_LEVEL_TYPE_ID")
@Enumerated(EnumType.ORDINAL)
private LogLevel level;

@Column(name = "LOG_NOTE")
private String note;

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

@Column(name="PROCESS_RUN_ID")
private Long processRunId;

// bi-directional many-to-one association to ProcessRun
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="PROCESS_RUN_ID", insertable=false, updatable=false)
private ProcessRun processRun;


public ProcessRunLog()
{
super();
}


public ProcessRunLog(LogLevel aLogLevel,
ProcessRun aProcessRun,
String note)
{
this();

this.level = aLogLevel;
this.note = note;
this.time = new Timestamp(System.currentTimeMillis()); // current timestamp
this.setProcessRun(aProcessRun);
}


public ProcessRunLog(LogLevel aLogLevel,
ProcessRun aProcessRun,
RecordSource aRecordSource,
Long fkId1,
Long fkId2,
String note)
{
this(aLogLevel, aProcessRun, note);

this.recordSource = aRecordSource;
this.fkId1 = fkId1;
this.fkId2 = fkId2;
}


@Override
public boolean equals(Object obj)
{
ExtendedEqualsBuilder anExtendedEqualsBuilder = new ExtendedEqualsBuilder(this, obj);
if (! anExtendedEqualsBuilder.isEquals())
return false;

ProcessRunLog aProcessRunLog = (ProcessRunLog) obj;

return anExtendedEqualsBuilder
.append(this.time, aProcessRunLog.time)
.append(this.processRunId, aProcessRunLog.processRunId)
.isEquals();
}


@Override
public int hashCode()
{
return new HashCodeBuilder()
.append(this.time)
.append(this.processRunId)
.toHashCode();
}


public int compareTo(ProcessRunLog aProcessRunLog)
{
return new CompareToBuilder()
.append(this.time, aProcessRunLog.time)
.append(this.processRunId, aProcessRunLog.processRunId)
.toComparison();
}


@Override
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", this.id)
.append("time", this.time)
.append("level", this.level)
.append("processRunId", this.processRunId)
.append("recordSource", this.recordSource)
.append("fkId1", this.fkId1)
.append("fkId2", this.fkId2)
.append("note", this.note)
.toString();
}


public Long getId()
{
return this.id;
}


public void setId(Long recordId)
{
this.id = recordId;
}


public RecordSource getRecordSource()
{
return this.recordSource;
}


public void setRecordSource(RecordSource recordSource)
{
this.recordSource = recordSource;
}


public Long getFkId1()
{
return this.fkId1;
}


public void setFkId1(Long fkId1)
{
this.fkId1 = fkId1;
}


public Long getFkId2()
{
return this.fkId2;
}


public void setFkId2(Long fkId2)
{
this.fkId2 = fkId2;
}


public LogLevel getLevel()
{
return this.level;
}


public void setLevel(LogLevel logLevel)
{
this.level = logLevel;
}


public String getNote()
{
return this.note;
}


public void setNote(String note)
{
this.note = note;
}


public Date getTime()
{
return this.time;
}


public void setTime(Date time)
{
this.time = time;
}


public Long getProcessRunId()
{
return this.processRunId;
}


public void setProcessRunId(Long processRunId)
{
this.processRunId = processRunId;
}


public ProcessRun getProcessRun()
{
return this.processRun;
}


public void setProcessRun(ProcessRun processRun)
{
if (processRun != null)
{
this.processRun = processRun;
this.processRunId = processRun.getId();

this.processRun.add(this);
}
}
}
Re: update in new transaction hangs program [message #783858 is a reply to message #783694] Thu, 26 January 2012 15:46 Go to previous messageGo to next message
James Sutherland is currently offline James SutherlandFriend
Messages: 1939
Registered: July 2009
Location: Ottawa, Canada
Senior Member

My guess is you have a database deadlock. Your REQUIRES_NEW is going to start a new database transaction, but because you never committed or rolledback the existing one, it still have locks on the database, so your update hangs.

Either commit the first transaction, or do both in the same transaction, or ensure they are not writing the same objects.

A full log of both transactions would make it clear.


James : Wiki : Book : Blog : Twitter
Re: update in new transaction hangs program [message #783983 is a reply to message #783858] Thu, 26 January 2012 21:49 Go to previous messageGo to next message
Ari Meyer is currently offline Ari MeyerFriend
Messages: 136
Registered: July 2009
Senior Member
Thanks, James. There is an outer transaction for the primary method,
but that can't be committed till the end of it. Shouldn't the outer
transaction be properly suspended while this one completes?
Ari
Re: update in new transaction hangs program [message #785424 is a reply to message #783983] Sun, 29 January 2012 00:55 Go to previous messageGo to next message
Ari Meyer is currently offline Ari MeyerFriend
Messages: 136
Registered: July 2009
Senior Member
Any advice would be appreciated -- haven't received a reply on the last
post. I will see if I can generate a full log of both transactions.

Is there any issue with Oracle's local transaction suspension that I
need to know about? It seems pretty straightforward: txn A gets
suspended, txn B starts and completes, then A resumes.

Would moving to XA drivers help here? I can't see why in this case
(single database), but I may be missing something. We need to run this
both in and outside of an app server.

Thanks,
Ari
Re: update in new transaction hangs program [message #786240 is a reply to message #785424] Mon, 30 January 2012 06:44 Go to previous messageGo to next message
Ari Meyer is currently offline Ari MeyerFriend
Messages: 136
Registered: July 2009
Senior Member
I found this: http://java.sys-con.com/node/204688?page=0,1 ("Deadlocks
in J2EE"), wherein the "Cross-Resource Deadlock #2: Single-Thread,
Multiple Conflicting Database Connections", which describes my issue.

So how can write my LoggingService using REQUIRES_NEW (assuming proper
transaction suspension), and consistently avoid deadlocks? As it
stands, I have to restrict usage to only places outside of an enclosing
transaction, which makes LoggingService no longer a service that can be
used anywhere, without further consideration.

I am now considering changing the implementation to use JMS (or perhaps
even just start another thread), hoping that this would effectively
decouple the 2 transactions. Is this an appropriate approach, even
though it seems overkill? How can you implement this with correct
transactional semantics and ensure that deadlocks will be avoided?

Thanks,
Ari
Re: update in new transaction hangs program [message #786592 is a reply to message #786240] Mon, 30 January 2012 16:04 Go to previous messageGo to next message
James Sutherland is currently offline James SutherlandFriend
Messages: 1939
Registered: July 2009
Location: Ottawa, Canada
Senior Member

Your issue seems to be,

- you start transactions A,
- you lock row X
- you start transaction B, without ending transaction A
- you lock row X - (so you are deadlocked, as transaction A still has the lock on X)

So, you need to examine exactly what you are doing an rethink it.
Perhaps don't use a separate transaction, or use a separate thread with its own transaction that does not block the root one.
JMS would work, because it is asynchronous, but anything that runs on its own thread would work.


James : Wiki : Book : Blog : Twitter
Re: update in new transaction hangs program [message #786597 is a reply to message #786240] Mon, 30 January 2012 16:04 Go to previous messageGo to next message
James Sutherland is currently offline James SutherlandFriend
Messages: 1939
Registered: July 2009
Location: Ottawa, Canada
Senior Member

Your issue seems to be,

- you start transactions A,
- you lock row X
- you start transaction B, without ending transaction A
- you lock row X - (so you are deadlocked, as transaction A still has the lock on X)

So, you need to examine exactly what you are doing an rethink it.
Perhaps don't use a separate transaction, or use a separate thread with its own transaction that does not block the root one.
JMS would work, because it is asynchronous, but anything that runs on its own thread would work.

--
James : http://wiki.eclipse.org/EclipseLink : http://en.wikibooks.org/wiki/Java_Persistence : http://java-persistence-performance.blogspot.com/


James : Wiki : Book : Blog : Twitter
Re: update in new transaction hangs program [message #786774 is a reply to message #786592] Mon, 30 January 2012 20:03 Go to previous message
Ari Meyer is currently offline Ari MeyerFriend
Messages: 136
Registered: July 2009
Senior Member
Thanks for confirming that, James. What's unfortunate is that there are
many books that say, 'use REQURES_NEW for cases like logging to a DB',
but don't discuss potential deadlock issues and how to work around them.
And because there was no "SELECT ... FOR UPDATE" happening, I didn't
realize that locking could be going on behind the scenes.

Will try this out -- thanks!
Ari
Previous Topic:is it possible to create a dynamic persistent entity with composite primary key ?
Next Topic:NPE in registerObjectForMergeCloneIntoWorkingCopy
Goto Forum:
  


Current Time: Sat Nov 29 10:22:07 GMT 2014

Powered by FUDForum. Page generated in 0.05485 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software