The previous implementation using 2 parallel list doesn't work well in our field testing, reimplemented using a hashmap cache and working fine in production environment now.

Link to SF Tracker: http://sourceforge.net/support/tracker.php?aid=2913975
This commit is contained in:
Heng Sin Low 2010-03-31 02:58:36 +00:00
parent c123ddfdfa
commit 4b2c766803
1 changed files with 144 additions and 105 deletions

View File

@ -30,6 +30,7 @@ import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -87,9 +88,9 @@ public class GridTable extends AbstractTableModel
implements Serializable implements Serializable
{ {
/** /**
* * generated
*/ */
private static final long serialVersionUID = 9013625748218987868L; private static final long serialVersionUID = -4397161719594270579L;
/** /**
* JDBC Based Buffered Table * JDBC Based Buffered Table
@ -129,7 +130,7 @@ public class GridTable extends AbstractTableModel
m_WindowNo = WindowNo; m_WindowNo = WindowNo;
m_TabNo = TabNo; m_TabNo = TabNo;
m_withAccessControl = withAccessControl; m_withAccessControl = withAccessControl;
m_virtual = virtual && MTable.get(ctx, AD_Table_ID).isHighVolume(); m_virtual = virtual;
} // MTable } // MTable
private static CLogger log = CLogger.getCLogger(GridTable.class.getName()); private static CLogger log = CLogger.getCLogger(GridTable.class.getName());
@ -144,8 +145,6 @@ public class GridTable extends AbstractTableModel
private boolean m_deleteable = true; private boolean m_deleteable = true;
//virtual table state variables //virtual table state variables
private boolean m_virtual; private boolean m_virtual;
private int m_cacheStart;
private int m_cacheEnd;
public static final String CTX_KeyColumnName = "KeyColumnName"; public static final String CTX_KeyColumnName = "KeyColumnName";
// //
@ -168,6 +167,7 @@ public class GridTable extends AbstractTableModel
// The buffer for all data // The buffer for all data
private volatile ArrayList<Object[]> m_buffer = new ArrayList<Object[]>(100); private volatile ArrayList<Object[]> m_buffer = new ArrayList<Object[]>(100);
private volatile ArrayList<MSort> m_sort = new ArrayList<MSort>(100); private volatile ArrayList<MSort> m_sort = new ArrayList<MSort>(100);
private volatile Map<Integer, Object[]> m_virtualBuffer = new HashMap<Integer, Object[]>(100);
/** Original row data */ /** Original row data */
private Object[] m_rowData = null; private Object[] m_rowData = null;
/** Original data [row,col,data] */ /** Original data [row,col,data] */
@ -212,9 +212,13 @@ public class GridTable extends AbstractTableModel
/** Vetoable Change Bean support */ /** Vetoable Change Bean support */
private VetoableChangeSupport m_vetoableChangeSupport = new VetoableChangeSupport(this); private VetoableChangeSupport m_vetoableChangeSupport = new VetoableChangeSupport(this);
private Thread m_loaderThread;
/** Property of Vetoable Bean support "RowChange" */ /** Property of Vetoable Bean support "RowChange" */
public static final String PROPERTY = "MTable-RowSave"; public static final String PROPERTY = "MTable-RowSave";
private final static Integer NEW_ROW_ID = Integer.valueOf(-1);
private static final int DEFAULT_FETCH_SIZE = 200;
/** /**
* Set Table Name * Set Table Name
* @param newTableName table name * @param newTableName table name
@ -591,17 +595,24 @@ public class GridTable extends AbstractTableModel
m_loader = new Loader(); m_loader = new Loader();
m_rowCount = m_loader.open(maxRows); m_rowCount = m_loader.open(maxRows);
if (m_virtual) if (m_virtual)
m_buffer = new ArrayList<Object[]>(210); {
m_buffer = null;
m_virtualBuffer = new HashMap<Integer, Object[]>(210);
}
else else
{
m_buffer = new ArrayList<Object[]>(m_rowCount+10); m_buffer = new ArrayList<Object[]>(m_rowCount+10);
}
m_sort = new ArrayList<MSort>(m_rowCount+10); m_sort = new ArrayList<MSort>(m_rowCount+10);
m_cacheStart = m_cacheEnd = -1;
if (m_rowCount > 0) if (m_rowCount > 0)
{ {
if (m_rowCount < 1000) if (m_rowCount < 1000)
m_loader.run(); m_loader.run();
else else
m_loader.start(); {
m_loaderThread = new Thread(m_loader, "TLoader");
m_loaderThread.start();
}
} }
else else
m_loader.close(); m_loader.close();
@ -638,13 +649,13 @@ public class GridTable extends AbstractTableModel
public void loadComplete() public void loadComplete()
{ {
// Wait for loader // Wait for loader
if (m_loader != null) if (m_loaderThread != null)
{ {
if (m_loader.isAlive()) if (m_loaderThread.isAlive())
{ {
try try
{ {
m_loader.join(); m_loaderThread.join();
} }
catch (InterruptedException ie) catch (InterruptedException ie)
{ {
@ -666,7 +677,7 @@ public class GridTable extends AbstractTableModel
*/ */
public boolean isLoading() public boolean isLoading()
{ {
if (m_loader != null && m_loader.isAlive()) if (m_loaderThread != null && m_loaderThread.isAlive())
return true; return true;
return false; return false;
} // isLoading } // isLoading
@ -705,10 +716,10 @@ public class GridTable extends AbstractTableModel
} }
// Stop loader // Stop loader
while (m_loader != null && m_loader.isAlive()) while (m_loaderThread != null && m_loaderThread.isAlive())
{ {
log.fine("Interrupting Loader ..."); log.fine("Interrupting Loader ...");
m_loader.interrupt(); m_loaderThread.interrupt();
try try
{ {
Thread.sleep(200); // .2 second Thread.sleep(200); // .2 second
@ -721,13 +732,20 @@ public class GridTable extends AbstractTableModel
dataSave(false); // not manual dataSave(false); // not manual
if (m_buffer != null) if (m_buffer != null)
{
m_buffer.clear(); m_buffer.clear();
m_buffer = null; m_buffer = null;
}
if (m_sort != null) if (m_sort != null)
{
m_sort.clear(); m_sort.clear();
m_sort = null; m_sort = null;
}
m_cacheStart = m_cacheEnd = -1; if (m_virtualBuffer != null)
{
m_virtualBuffer.clear();
m_virtualBuffer = null;
}
if (finalCall) if (finalCall)
dispose(); dispose();
@ -757,10 +775,12 @@ public class GridTable extends AbstractTableModel
m_parameterWHERE = null; m_parameterWHERE = null;
// clear data arrays // clear data arrays
m_buffer = null; m_buffer = null;
m_virtualBuffer = null;
m_sort = null; m_sort = null;
m_rowData = null; m_rowData = null;
m_oldValue = null; m_oldValue = null;
m_loader = null; m_loader = null;
m_loaderThread = null;
} // dispose } // dispose
/** /**
@ -837,6 +857,10 @@ public class GridTable extends AbstractTableModel
log.info("#" + col + " " + ascending); log.info("#" + col + " " + ascending);
if (getRowCount() == 0) if (getRowCount() == 0)
return; return;
//cache changed row
Object[] changedRow = m_rowChanged >= 0 ? getDataAtRow(m_rowChanged) : null;
GridField field = getField (col); GridField field = getField (col);
// RowIDs are not sorted // RowIDs are not sorted
if (field.getDisplayType() == DisplayType.RowID) if (field.getDisplayType() == DisplayType.RowID)
@ -864,8 +888,26 @@ public class GridTable extends AbstractTableModel
Collections.sort(m_sort, sort); Collections.sort(m_sort, sort);
if (m_virtual) if (m_virtual)
{ {
m_buffer.clear(); Object[] newRow = m_virtualBuffer.get(NEW_ROW_ID);
m_cacheStart = m_cacheEnd = -1; m_virtualBuffer.clear();
if (newRow != null && newRow.length > 0)
m_virtualBuffer.put(NEW_ROW_ID, newRow);
if (changedRow != null && changedRow.length > 0)
{
if (changedRow[m_indexKeyColumn] != null && (Integer)changedRow[m_indexKeyColumn] > 0)
{
m_virtualBuffer.put((Integer)changedRow[m_indexKeyColumn], changedRow);
for(int i = 0; i < m_sort.size(); i++)
{
if (m_sort.get(i).index == (Integer)changedRow[m_indexKeyColumn])
{
m_rowChanged = i;
break;
}
}
}
}
//release sort memory //release sort memory
for (int i = 0; i < m_sort.size(); i++) for (int i = 0; i < m_sort.size(); i++)
@ -933,7 +975,7 @@ public class GridTable extends AbstractTableModel
// need to wait for data read into buffer // need to wait for data read into buffer
int loops = 0; int loops = 0;
while (row >= m_sort.size() && m_loader.isAlive() && loops < 15) while (row >= m_sort.size() && m_loaderThread != null && m_loaderThread.isAlive() && loops < 15)
{ {
log.fine("Waiting for loader row=" + row + ", size=" + m_sort.size()); log.fine("Waiting for loader row=" + row + ", size=" + m_sort.size());
try try
@ -963,20 +1005,22 @@ public class GridTable extends AbstractTableModel
return rowData[col]; return rowData[col];
} // getValueAt } // getValueAt
private Object[] getDataAtRow(int row) private Object[] getDataAtRow(int row)
{
return getDataAtRow(row, true);
}
private Object[] getDataAtRow(int row, boolean fetchIfNotFound)
{ {
MSort sort = (MSort)m_sort.get(row); MSort sort = (MSort)m_sort.get(row);
Object[] rowData = null; Object[] rowData = null;
if (m_virtual) if (m_virtual)
{ {
int bufferrow = -1; if (sort.index != NEW_ROW_ID && !(m_virtualBuffer.containsKey(sort.index)) && fetchIfNotFound)
if (sort.index != -1 && (row < m_cacheStart || row > m_cacheEnd))
{ {
fillBuffer(row); fillBuffer(row, DEFAULT_FETCH_SIZE);
} }
bufferrow = row - m_cacheStart; rowData = (Object[])m_virtualBuffer.get(sort.index);
rowData = (Object[])m_buffer.get(bufferrow);
} }
else else
{ {
@ -989,13 +1033,11 @@ private Object[] getDataAtRow(int row)
MSort sort = m_sort.get(row); MSort sort = m_sort.get(row);
if (m_virtual) if (m_virtual)
{ {
int bufferrow = -1; if (sort.index != NEW_ROW_ID && !(m_virtualBuffer.containsKey(sort.index)))
if (sort.index != -1 && (row < m_cacheStart || row > m_cacheEnd))
{ {
fillBuffer(row); fillBuffer(row, DEFAULT_FETCH_SIZE);
} }
bufferrow = row - m_cacheStart; m_virtualBuffer.put(sort.index, rowData);
m_buffer.set(bufferrow, rowData);
} }
else else
{ {
@ -1004,14 +1046,14 @@ private Object[] getDataAtRow(int row)
} }
private void fillBuffer(int start) private void fillBuffer(int start, int fetchSize)
{ {
//adjust start if needed //adjust start if needed
if (start > 0) if (start > 0)
{ {
if (start + 200 >= m_sort.size()) if (start + fetchSize >= m_sort.size())
{ {
start = start - (200 - ( m_sort.size() - start )); start = start - (fetchSize - ( m_sort.size() - start ));
if (start < 0) if (start < 0)
start = 0; start = 0;
} }
@ -1021,10 +1063,8 @@ private Object[] getDataAtRow(int row)
.append(" WHERE ") .append(" WHERE ")
.append(getKeyColumnName()) .append(getKeyColumnName())
.append(" IN ("); .append(" IN (");
m_cacheStart = start; Map<Integer, Integer>rowmap = new LinkedHashMap<Integer, Integer>(DEFAULT_FETCH_SIZE);
m_cacheEnd = m_cacheStart - 1; for(int i = start; i < start+fetchSize && i < m_sort.size(); i++)
Map<Integer, Integer>rowmap = new LinkedHashMap<Integer, Integer>(200);
for(int i = start; i < start+200 && i < m_sort.size(); i++)
{ {
if(i > start) if(i > start)
sql.append(","); sql.append(",");
@ -1032,9 +1072,21 @@ private Object[] getDataAtRow(int row)
rowmap.put(m_sort.get(i).index, i); rowmap.put(m_sort.get(i).index, i);
} }
sql.append(")"); sql.append(")");
m_buffer = new ArrayList<Object[]>(210);
for(int i = 0; i < 200; i++) Object[] newRow = m_virtualBuffer.get(NEW_ROW_ID);
m_buffer.add(null); //cache changed row
Object[] changedRow = m_rowChanged >= 0 ? getDataAtRow(m_rowChanged, false) : null;
m_virtualBuffer = new HashMap<Integer, Object[]>(210);
if (newRow != null && newRow.length > 0)
m_virtualBuffer.put(NEW_ROW_ID, newRow);
if (changedRow != null && changedRow.length > 0)
{
if (changedRow[m_indexKeyColumn] != null && (Integer)changedRow[m_indexKeyColumn] > 0)
{
m_virtualBuffer.put((Integer)changedRow[m_indexKeyColumn], changedRow);
}
}
PreparedStatement stmt = null; PreparedStatement stmt = null;
ResultSet rs = null; ResultSet rs = null;
try try
@ -1044,9 +1096,8 @@ private Object[] getDataAtRow(int row)
while(rs.next()) while(rs.next())
{ {
Object[] data = readData(rs); Object[] data = readData(rs);
int row = rowmap.remove(data[m_indexKeyColumn]); rowmap.remove(data[m_indexKeyColumn]);
m_buffer.set(row - m_cacheStart, data); m_virtualBuffer.put((Integer)data[m_indexKeyColumn], data);
m_cacheEnd++;
} }
if (!rowmap.isEmpty()) if (!rowmap.isEmpty())
{ {
@ -1059,7 +1110,6 @@ private Object[] getDataAtRow(int row)
for(Integer row : toremove) for(Integer row : toremove)
{ {
m_sort.remove(row); m_sort.remove(row);
m_buffer.remove(row - m_cacheStart);
} }
} }
} }
@ -1373,7 +1423,7 @@ private Object[] getDataAtRow(int row)
// get updated row data // get updated row data
Object[] rowData = getDataAtRow(m_rowChanged); Object[] rowData = getDataAtRow(m_rowChanged);
// CarlosRuiz - globalqss - fix [1722226] - Usability - Record_ID = 0 on 9 tables can't be modified // CarlosRuiz - globalqss - fix [1722226] - Usability - Record_ID = 0 on 9 tables can't be modified
boolean specialZeroUpdate = false; boolean specialZeroUpdate = false;
if (!m_inserting // not inserting, updating a record if (!m_inserting // not inserting, updating a record
@ -1929,7 +1979,14 @@ private Object[] getDataAtRow(int row)
if (m_virtual) if (m_virtual)
{ {
MSort sort = m_sort.get(m_rowChanged); MSort sort = m_sort.get(m_rowChanged);
sort.index = getKeyID(m_rowChanged); int oldId = sort.index;
int newId = getKeyID(m_rowChanged);
if (newId != oldId)
{
sort.index = newId;
Object[] data = m_virtualBuffer.remove(oldId);
m_virtualBuffer.put(newId, data);
}
} }
fireTableRowsUpdated(m_rowChanged, m_rowChanged); fireTableRowsUpdated(m_rowChanged, m_rowChanged);
} }
@ -2073,6 +2130,19 @@ private Object[] getDataAtRow(int row)
fireDataStatusEEvent(msg, info, true); fireDataStatusEEvent(msg, info, true);
return SAVE_ERROR; return SAVE_ERROR;
} }
else if (m_virtual && po.get_ID() > 0)
{
//update ID
MSort sort = m_sort.get(m_rowChanged);
int oldid = sort.index;
if (oldid != po.get_ID())
{
sort.index = po.get_ID();
Object[] data = m_virtualBuffer.remove(oldid);
data[m_indexKeyColumn] = sort.index;
m_virtualBuffer.put(sort.index, data);
}
}
// Refresh - update buffer // Refresh - update buffer
String whereClause = po.get_WhereClause(true); String whereClause = po.get_WhereClause(true);
@ -2090,11 +2160,6 @@ private Object[] getDataAtRow(int row)
Object[] rowDataDB = readData(rs); Object[] rowDataDB = readData(rs);
// update buffer // update buffer
setDataAtRow(m_rowChanged, rowDataDB); setDataAtRow(m_rowChanged, rowDataDB);
if (m_virtual)
{
MSort sort = m_sort.get(m_rowChanged);
sort.index = getKeyID(m_rowChanged);
}
fireTableRowsUpdated(m_rowChanged, m_rowChanged); fireTableRowsUpdated(m_rowChanged, m_rowChanged);
} }
} }
@ -2392,19 +2457,11 @@ private Object[] getDataAtRow(int row)
// add Data at end of buffer // add Data at end of buffer
MSort newSort = m_virtual MSort newSort = m_virtual
? new MSort(-1, null) ? new MSort(NEW_ROW_ID, null)
: new MSort(m_sort.size(), null); // index : new MSort(m_sort.size(), null); // index
if (m_virtual) if (m_virtual)
{ {
m_buffer.add(m_newRow, rowData); m_virtualBuffer.put(NEW_ROW_ID, rowData);
if (m_cacheStart == -1)
{
m_cacheStart = m_cacheEnd = m_newRow;
}
else if (m_cacheEnd < m_newRow)
{
m_cacheEnd = m_newRow;
}
} }
else else
{ {
@ -2583,11 +2640,15 @@ private Object[] getDataAtRow(int row)
} }
// Get Sort // Get Sort
int bufferRow = m_virtual if (m_virtual)
? row - m_cacheStart {
: sort.index; m_virtualBuffer.remove(sort.index);
// Delete row in Buffer and shifts all below up }
m_buffer.remove(bufferRow); else
{
// Delete row in Buffer and shifts all below up
m_buffer.remove(sort.index);
}
m_rowCount--; m_rowCount--;
// Delete row in Sort // Delete row in Sort
@ -2598,26 +2659,10 @@ private Object[] getDataAtRow(int row)
for (int i = 0; i < m_sort.size(); i++) for (int i = 0; i < m_sort.size(); i++)
{ {
MSort ptr = (MSort)m_sort.get(i); MSort ptr = (MSort)m_sort.get(i);
if (ptr.index > bufferRow) if (ptr.index > sort.index)
ptr.index--; // move up ptr.index--; // move up
} }
} }
else
{
if (m_cacheStart == row)
{
if (m_cacheStart < m_cacheEnd)
m_cacheStart++;
else
m_cacheStart = m_cacheEnd = -1;
}
else
{
m_cacheEnd--;
if (m_cacheStart > m_cacheEnd)
m_cacheStart = m_cacheEnd;
}
}
// inform // inform
m_changed = false; m_changed = false;
@ -2646,23 +2691,18 @@ private Object[] getDataAtRow(int row)
{ {
// Get Sort // Get Sort
MSort sort = (MSort)m_sort.get(m_newRow); MSort sort = (MSort)m_sort.get(m_newRow);
int bufferRow = m_virtual if (m_virtual)
? m_buffer.size() - 1 {
: sort.index; m_virtualBuffer.remove(NEW_ROW_ID);
// Delete row in Buffer and shifts all below up }
m_buffer.remove(bufferRow); else
{
// Delete row in Buffer and shifts all below up
m_buffer.remove(sort.index);
}
m_rowCount--; m_rowCount--;
// Delete row in Sort // Delete row in Sort
m_sort.remove(m_newRow); // pintint to the last column, so no adjustment m_sort.remove(m_newRow); // pintint to the last column, so no adjustment
if (m_virtual)
{
if (m_cacheEnd == m_newRow)
{
m_cacheEnd--;
if (m_cacheStart > m_cacheEnd)
m_cacheStart = m_cacheEnd;
}
}
// //
m_changed = false; m_changed = false;
m_rowData = null; m_rowData = null;
@ -3205,7 +3245,7 @@ private Object[] getDataAtRow(int row)
/************************************************************************** /**************************************************************************
* ASync Loader * ASync Loader
*/ */
class Loader extends Thread implements Serializable class Loader implements Serializable, Runnable
{ {
/** /**
* *
@ -3217,7 +3257,6 @@ private Object[] getDataAtRow(int row)
*/ */
public Loader() public Loader()
{ {
super("TLoader");
} // Loader } // Loader
private PreparedStatement m_pstmt = null; private PreparedStatement m_pstmt = null;
@ -3316,7 +3355,7 @@ private Object[] getDataAtRow(int row)
{ {
while (m_rs.next()) while (m_rs.next())
{ {
if (this.isInterrupted()) if (Thread.interrupted())
{ {
log.fine("Interrupted"); log.fine("Interrupted");
close(); close();
@ -3345,8 +3384,8 @@ private Object[] getDataAtRow(int row)
// give the other processes a chance // give the other processes a chance
try try
{ {
yield(); Thread.yield();
sleep(10); // .01 second Thread.sleep(10); // .01 second
} }
catch (InterruptedException ie) catch (InterruptedException ie)
{ {