提交 57c231c1 authored 作者: Thomas Mueller's avatar Thomas Mueller

Issue 265: Linked tables: auto-reconnect if the backside connection is lost…

Issue 265: Linked tables: auto-reconnect if the backside connection is lost (workaround for the MySQL problem that disconnects after 8 hours of inactivity).
上级 c0fc2fd5
...@@ -8,6 +8,7 @@ package org.h2.index; ...@@ -8,6 +8,7 @@ package org.h2.index;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.util.ArrayList;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
...@@ -16,6 +17,7 @@ import org.h2.result.SearchRow; ...@@ -16,6 +17,7 @@ import org.h2.result.SearchRow;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.TableLink; import org.h2.table.TableLink;
import org.h2.util.New;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
...@@ -49,6 +51,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -49,6 +51,7 @@ public class LinkedIndex extends BaseIndex {
} }
public void add(Session session, Row row) { public void add(Session session, Row row) {
ArrayList<Value> params = New.arrayList();
StatementBuilder buff = new StatementBuilder("INSERT INTO "); StatementBuilder buff = new StatementBuilder("INSERT INTO ");
buff.append(targetTableName).append(" VALUES("); buff.append(targetTableName).append(" VALUES(");
for (int i = 0; i < row.getColumnCount(); i++) { for (int i = 0; i < row.getColumnCount(); i++) {
...@@ -60,29 +63,21 @@ public class LinkedIndex extends BaseIndex { ...@@ -60,29 +63,21 @@ public class LinkedIndex extends BaseIndex {
buff.append("NULL"); buff.append("NULL");
} else { } else {
buff.append('?'); buff.append('?');
params.add(v);
} }
} }
buff.append(')'); buff.append(')');
String sql = buff.toString(); String sql = buff.toString();
synchronized (link.getConnection()) { try {
try { link.execute(sql, params, true);
PreparedStatement prep = link.getPreparedStatement(sql, false); rowCount++;
for (int i = 0, j = 0; i < row.getColumnCount(); i++) { } catch (Exception e) {
Value v = row.getValue(i); throw TableLink.wrapException(sql, e);
if (v != null && v != ValueNull.INSTANCE) {
v.set(prep, j + 1);
j++;
}
}
prep.executeUpdate();
rowCount++;
} catch (Exception e) {
throw TableLink.wrapException(sql, e);
}
} }
} }
public Cursor find(Session session, SearchRow first, SearchRow last) { public Cursor find(Session session, SearchRow first, SearchRow last) {
ArrayList<Value> params = New.arrayList();
StatementBuilder buff = new StatementBuilder("SELECT * FROM "); StatementBuilder buff = new StatementBuilder("SELECT * FROM ");
buff.append(targetTableName).append(" T"); buff.append(targetTableName).append(" T");
for (int i = 0; first != null && i < first.getColumnCount(); i++) { for (int i = 0; first != null && i < first.getColumnCount(); i++) {
...@@ -97,6 +92,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -97,6 +92,7 @@ public class LinkedIndex extends BaseIndex {
} else { } else {
buff.append(">="); buff.append(">=");
addParameter(buff, col); addParameter(buff, col);
params.add(v);
} }
} }
} }
...@@ -112,33 +108,17 @@ public class LinkedIndex extends BaseIndex { ...@@ -112,33 +108,17 @@ public class LinkedIndex extends BaseIndex {
} else { } else {
buff.append("<="); buff.append("<=");
addParameter(buff, col); addParameter(buff, col);
params.add(v);
} }
} }
} }
String sql = buff.toString(); String sql = buff.toString();
synchronized (link.getConnection()) { try {
try { PreparedStatement prep = link.execute(sql, params, false);
PreparedStatement prep = link.getPreparedStatement(sql, true); ResultSet rs = prep.getResultSet();
int j = 0; return new LinkedCursor(link, rs, session, sql, prep);
for (int i = 0; first != null && i < first.getColumnCount(); i++) { } catch (Exception e) {
Value v = first.getValue(i); throw TableLink.wrapException(sql, e);
if (v != null && v != ValueNull.INSTANCE) {
v.set(prep, j + 1);
j++;
}
}
for (int i = 0; last != null && i < last.getColumnCount(); i++) {
Value v = last.getValue(i);
if (v != null && v != ValueNull.INSTANCE) {
v.set(prep, j + 1);
j++;
}
}
ResultSet rs = prep.executeQuery();
return new LinkedCursor(link, rs, session, sql, prep);
} catch (Exception e) {
throw TableLink.wrapException(sql, e);
}
} }
} }
...@@ -185,6 +165,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -185,6 +165,7 @@ public class LinkedIndex extends BaseIndex {
} }
public void remove(Session session, Row row) { public void remove(Session session, Row row) {
ArrayList<Value> params = New.arrayList();
StatementBuilder buff = new StatementBuilder("DELETE FROM "); StatementBuilder buff = new StatementBuilder("DELETE FROM ");
buff.append(targetTableName).append(" WHERE "); buff.append(targetTableName).append(" WHERE ");
for (int i = 0; i < row.getColumnCount(); i++) { for (int i = 0; i < row.getColumnCount(); i++) {
...@@ -197,25 +178,18 @@ public class LinkedIndex extends BaseIndex { ...@@ -197,25 +178,18 @@ public class LinkedIndex extends BaseIndex {
} else { } else {
buff.append('='); buff.append('=');
addParameter(buff, col); addParameter(buff, col);
params.add(v);
buff.append(' '); buff.append(' ');
} }
} }
String sql = buff.toString(); String sql = buff.toString();
synchronized (link.getConnection()) { try {
try { PreparedStatement prep = link.execute(sql, params, false);
PreparedStatement prep = link.getPreparedStatement(sql, false); int count = prep.executeUpdate();
for (int i = 0, j = 0; i < row.getColumnCount(); i++) { link.reusePreparedStatement(prep, sql);
Value v = row.getValue(i); rowCount -= count;
if (!isNull(v)) { } catch (Exception e) {
v.set(prep, j + 1); throw TableLink.wrapException(sql, e);
j++;
}
}
int count = prep.executeUpdate();
rowCount -= count;
} catch (Exception e) {
throw TableLink.wrapException(sql, e);
}
} }
} }
...@@ -227,6 +201,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -227,6 +201,7 @@ public class LinkedIndex extends BaseIndex {
* @param newRow the new data * @param newRow the new data
*/ */
public void update(Row oldRow, Row newRow) { public void update(Row oldRow, Row newRow) {
ArrayList<Value> params = New.arrayList();
StatementBuilder buff = new StatementBuilder("UPDATE "); StatementBuilder buff = new StatementBuilder("UPDATE ");
buff.append(targetTableName).append(" SET "); buff.append(targetTableName).append(" SET ");
for (int i = 0; i < newRow.getColumnCount(); i++) { for (int i = 0; i < newRow.getColumnCount(); i++) {
...@@ -237,6 +212,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -237,6 +212,7 @@ public class LinkedIndex extends BaseIndex {
buff.append("DEFAULT"); buff.append("DEFAULT");
} else { } else {
buff.append('?'); buff.append('?');
params.add(v);
} }
} }
buff.append(" WHERE "); buff.append(" WHERE ");
...@@ -250,34 +226,15 @@ public class LinkedIndex extends BaseIndex { ...@@ -250,34 +226,15 @@ public class LinkedIndex extends BaseIndex {
buff.append(" IS NULL"); buff.append(" IS NULL");
} else { } else {
buff.append('='); buff.append('=');
params.add(v);
addParameter(buff, col); addParameter(buff, col);
} }
} }
String sql = buff.toString(); String sql = buff.toString();
synchronized (link.getConnection()) { try {
try { link.execute(sql, params, true);
int j = 1; } catch (Exception e) {
PreparedStatement prep = link.getPreparedStatement(sql, false); throw TableLink.wrapException(sql, e);
for (int i = 0; i < newRow.getColumnCount(); i++) {
Value v = newRow.getValue(i);
if (v != null) {
v.set(prep, j);
j++;
}
}
for (int i = 0; i < oldRow.getColumnCount(); i++) {
Value v = oldRow.getValue(i);
if (!isNull(v)) {
v.set(prep, j);
j++;
}
}
int count = prep.executeUpdate();
// this has no effect but at least it allows to debug the update count
rowCount = rowCount + count - count;
} catch (Exception e) {
throw TableLink.wrapException(sql, e);
}
} }
} }
......
...@@ -30,6 +30,7 @@ import org.h2.schema.Schema; ...@@ -30,6 +30,7 @@ import org.h2.schema.Schema;
import org.h2.util.JdbcUtils; import org.h2.util.JdbcUtils;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.value.DataType; import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -43,6 +44,8 @@ import org.h2.value.ValueTimestamp; ...@@ -43,6 +44,8 @@ import org.h2.value.ValueTimestamp;
*/ */
public class TableLink extends Table { public class TableLink extends Table {
private static final int MAX_RETRY = 2;
private static final long ROW_COUNT_APPROXIMATION = 100000; private static final long ROW_COUNT_APPROXIMATION = 100000;
private String driver, url, user, password, originalSchema, originalTable, qualifiedTableName; private String driver, url, user, password, originalSchema, originalTable, qualifiedTableName;
...@@ -72,7 +75,6 @@ public class TableLink extends Table { ...@@ -72,7 +75,6 @@ public class TableLink extends Table {
try { try {
connect(); connect();
} catch (DbException e) { } catch (DbException e) {
connectException = e;
if (!force) { if (!force) {
throw e; throw e;
} }
...@@ -84,15 +86,26 @@ public class TableLink extends Table { ...@@ -84,15 +86,26 @@ public class TableLink extends Table {
} }
private void connect() { private void connect() {
conn = database.getLinkConnection(driver, url, user, password); connectException = null;
synchronized (conn) { for (int retry = 0;; retry++) {
try { try {
readMetaData(); conn = database.getLinkConnection(driver, url, user, password);
} catch (Exception e) { synchronized (conn) {
// could be SQLException or RuntimeException try {
conn.close(); readMetaData();
conn = null; return;
throw DbException.convert(e); } catch (Exception e) {
// could be SQLException or RuntimeException
conn.close(true);
conn = null;
throw DbException.convert(e);
}
}
} catch (DbException e) {
if (retry >= MAX_RETRY) {
connectException = e;
throw e;
}
} }
} }
} }
...@@ -395,7 +408,7 @@ public class TableLink extends Table { ...@@ -395,7 +408,7 @@ public class TableLink extends Table {
public void close(Session session) { public void close(Session session) {
if (conn != null) { if (conn != null) {
try { try {
conn.close(); conn.close(false);
} finally { } finally {
conn = null; conn = null;
} }
...@@ -405,11 +418,12 @@ public class TableLink extends Table { ...@@ -405,11 +418,12 @@ public class TableLink extends Table {
public synchronized long getRowCount(Session session) { public synchronized long getRowCount(Session session) {
String sql = "SELECT COUNT(*) FROM " + qualifiedTableName; String sql = "SELECT COUNT(*) FROM " + qualifiedTableName;
try { try {
PreparedStatement prep = getPreparedStatement(sql, false); PreparedStatement prep = execute(sql, null, false);
ResultSet rs = prep.executeQuery(); ResultSet rs = prep.getResultSet();
rs.next(); rs.next();
long count = rs.getLong(1); long count = rs.getLong(1);
rs.close(); rs.close();
reusePreparedStatement(prep, sql);
return count; return count;
} catch (Exception e) { } catch (Exception e) {
throw wrapException(sql, e); throw wrapException(sql, e);
...@@ -433,33 +447,60 @@ public class TableLink extends Table { ...@@ -433,33 +447,60 @@ public class TableLink extends Table {
} }
/** /**
* Get a prepared statement object for the given statement. Prepared * Execute a SQL statement using the given parameters. Prepared
* statements are kept in a hash map to avoid re-creating them. * statements are kept in a hash map to avoid re-creating them.
* *
* @param sql the SQL statement * @param sql the SQL statement
* @param exclusive if the prepared statement must be removed from the map * @param params the parameters or null
* until reusePreparedStatement is called (only required for queries) * @param reusePrepared if the prepared statement can be re-used immediately
* @return the prepared statement * @return the prepared statement, or null if it is re-used
*/ */
public PreparedStatement getPreparedStatement(String sql, boolean exclusive) { public PreparedStatement execute(String sql, ArrayList<Value> params, boolean reusePrepared) {
if (trace.isDebugEnabled()) { if (conn == null) {
trace.debug("{0} :\n{1}", getName(), sql); throw connectException;
} }
try { for (int retry = 0;; retry++) {
if (conn == null) { try {
throw connectException; synchronized (conn) {
} PreparedStatement prep = preparedMap.remove(sql);
PreparedStatement prep = preparedMap.get(sql); if (prep == null) {
if (prep == null) { prep = conn.getConnection().prepareStatement(sql);
prep = conn.getConnection().prepareStatement(sql); }
preparedMap.put(sql, prep); if (trace.isDebugEnabled()) {
} StatementBuilder buff = new StatementBuilder();
if (exclusive) { buff.append(getName()).append(":\n").append(sql);
preparedMap.remove(sql); if (params != null && params.size() > 0) {
buff.append(" {");
int i = 1;
for (Value v : params) {
buff.appendExceptFirst(", ");
buff.append(i++).append(": ").append(v.getSQL());
}
buff.append('}');
}
buff.append(';');
trace.debug(buff.toString());
}
if (params != null) {
for (int i = 0, size = params.size(); i < size; i++) {
Value v = params.get(i);
v.set(prep, i + 1);
}
}
prep.execute();
if (reusePrepared) {
reusePreparedStatement(prep, sql);
return null;
}
return prep;
}
} catch (SQLException e) {
if (retry >= MAX_RETRY) {
throw DbException.convert(e);
}
conn.close(true);
connect();
} }
return prep;
} catch (SQLException e) {
throw DbException.convert(e);
} }
} }
...@@ -552,10 +593,6 @@ public class TableLink extends Table { ...@@ -552,10 +593,6 @@ public class TableLink extends Table {
this.readOnly = readOnly; this.readOnly = readOnly;
} }
public TableLinkConnection getConnection() {
return conn;
}
public long getRowCountApproximation() { public long getRowCountApproximation() {
return ROW_COUNT_APPROXIMATION; return ROW_COUNT_APPROXIMATION;
} }
...@@ -567,7 +604,9 @@ public class TableLink extends Table { ...@@ -567,7 +604,9 @@ public class TableLink extends Table {
* @param sql the SQL statement * @param sql the SQL statement
*/ */
public void reusePreparedStatement(PreparedStatement prep, String sql) { public void reusePreparedStatement(PreparedStatement prep, String sql) {
preparedMap.put(sql, prep); synchronized (conn) {
preparedMap.put(sql, prep);
}
} }
public boolean isDeterministic() { public boolean isDeterministic() {
......
...@@ -124,11 +124,13 @@ public class TableLinkConnection { ...@@ -124,11 +124,13 @@ public class TableLinkConnection {
/** /**
* Closes the connection if this is the last link to it. * Closes the connection if this is the last link to it.
*
* @param force if the connection needs to be closed even if it is still
* used elsewhere (for example, because the connection is broken)
*/ */
synchronized void close() { synchronized void close(boolean force) {
if (--useCounter <= 0) { if (--useCounter <= 0 || force) {
JdbcUtils.closeSilently(conn); JdbcUtils.closeSilently(conn);
conn = null;
synchronized (map) { synchronized (map) {
map.remove(this); map.remove(this);
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论