Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* HA-JDBC: High-Availability JDBC
* Copyright (C) 2012 Paul Ferraro
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package net.sf.hajdbc.sync;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import net.sf.hajdbc.Database;
import net.sf.hajdbc.DatabaseCluster;
import net.sf.hajdbc.ExceptionType;
import net.sf.hajdbc.Messages;
import net.sf.hajdbc.SynchronizationStrategy;
import net.sf.hajdbc.TableProperties;
import net.sf.hajdbc.UniqueConstraint;
import net.sf.hajdbc.logging.Level;
import net.sf.hajdbc.logging.Logger;
import net.sf.hajdbc.logging.LoggerFactory;
import net.sf.hajdbc.util.Objects;
import net.sf.hajdbc.util.Resources;
import net.sf.hajdbc.util.Strings;
/**
* Database-independent synchronization strategy that only updates differences between two databases.
* This strategy is best used when there are few differences between the active database and the inactive database (i.e. barely out of sync).
* The following algorithm is used:
*
*
Drop the foreign keys on the inactive database (to avoid integrity constraint violations)
*
For each database table:
*
*
Drop the unique constraints on the table (to avoid integrity constraint violations)
*
Find the primary key(s) of the table
*
Query all rows in the inactive database table, sorting by the primary key(s)
*
Query all rows on the active database table
*
For each row in table:
*
*
If primary key of the rows are the same, determine whether or not row needs to be updated
*
Otherwise, determine whether row should be deleted, or a new row is to be inserted
*
*
*
Re-create the unique constraints on the table (to avoid integrity constraint violations)
*
*
*
Re-create the foreign keys on the inactive database
*
Synchronize sequences
*
* @author Paul Ferraro
*/
public class DifferentialSynchronizationStrategy implements SynchronizationStrategy, TableSynchronizationStrategy
{
private static final long serialVersionUID = -2785092229503649831L;
static Logger logger = LoggerFactory.getLogger(DifferentialSynchronizationStrategy.class);
private final SynchronizationStrategy strategy = new PerTableSynchronizationStrategy(this);
private int fetchSize = 0;
private int maxBatchSize = 100;
private Pattern versionPattern = null;
@Override
public String getId()
{
return "diff";
}
@Override
public > void synchronize(SynchronizationContext context) throws SQLException
{
this.strategy.synchronize(context);
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.SynchronizationStrategy#init(net.sf.hajdbc.DatabaseCluster)
*/
@Override
public > void init(DatabaseCluster cluster)
{
this.strategy.init(cluster);
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.SynchronizationStrategy#destroy(net.sf.hajdbc.DatabaseCluster)
*/
@Override
public > void destroy(DatabaseCluster cluster)
{
this.strategy.destroy(cluster);
}
@Override
public > void dropConstraints(SynchronizationContext context) throws SQLException
{
SynchronizationSupport support = context.getSynchronizationSupport();
support.dropForeignKeys();
support.dropUniqueConstraints();
}
@Override
public > void restoreConstraints(SynchronizationContext context) throws SQLException
{
SynchronizationSupport support = context.getSynchronizationSupport();
support.restoreUniqueConstraints();
support.restoreForeignKeys();
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.SynchronizationStrategy#synchronize(net.sf.hajdbc.sync.SynchronizationContext)
*/
@Override
public > void synchronize(SynchronizationContext context, TableProperties table) throws SQLException
{
String tableName = table.getName().getDMLName();
UniqueConstraint primaryKey = table.getPrimaryKey();
if (primaryKey == null)
{
throw new SQLException(Messages.PRIMARY_KEY_REQUIRED.getMessage(this.getClass().getName(), tableName));
}
List primaryKeyColumns = primaryKey.getColumnList();
Collection columns = table.getColumns();
List nonPrimaryKeyColumns = new ArrayList(columns.size());
List versionColumns = new ArrayList(columns.size());
for (String column: columns)
{
if (!primaryKeyColumns.contains(column))
{
// Try to find a version column
if ((this.versionPattern != null) && this.versionPattern.matcher(column).matches())
{
versionColumns.add(column);
}
nonPrimaryKeyColumns.add(column);
}
}
// List of columns for select statement - starting with primary key
List allColumns = new ArrayList(columns.size());
allColumns.addAll(primaryKeyColumns);
allColumns.addAll(nonPrimaryKeyColumns);
List selectColumns = allColumns;
if (!versionColumns.isEmpty())
{
selectColumns = new ArrayList(primaryKeyColumns.size() + versionColumns.size());
selectColumns.addAll(primaryKeyColumns);
selectColumns.addAll(versionColumns);
}
// Retrieve table rows in primary key order
final String selectSQL = String.format("SELECT %s FROM %s ORDER BY %s", Strings.join(selectColumns, Strings.PADDED_COMMA), tableName, Strings.join(primaryKeyColumns, Strings.PADDED_COMMA)); //$NON-NLS-1$
String primaryKeyWhereClause = Strings.join(new StringBuilder(), primaryKeyColumns, " = ? AND ").append(" = ?").toString(); //$NON-NLS-1$
String selectAllSQL = !versionColumns.isEmpty() ? String.format("SELECT %s FROM %s WHERE %s", Strings.join(nonPrimaryKeyColumns, Strings.PADDED_COMMA), tableName, primaryKeyWhereClause) : null;
String deleteSQL = String.format("DELETE FROM %s WHERE %s", tableName, primaryKeyWhereClause);
String insertSQL = String.format("INSERT INTO %s (%s) VALUES (%s)", tableName, Strings.join(allColumns, Strings.PADDED_COMMA), Strings.join(Collections.nCopies(allColumns.size(), Strings.QUESTION), Strings.PADDED_COMMA)); //$NON-NLS-1$
String updateSQL = !nonPrimaryKeyColumns.isEmpty() ? String.format("UPDATE %s SET %s = ? WHERE %s", tableName, Strings.join(nonPrimaryKeyColumns, " = ?, "), primaryKeyWhereClause) : null;
Connection targetConnection = context.getConnection(context.getTargetDatabase());
final Statement targetStatement = targetConnection.createStatement();
try
{
targetStatement.setFetchSize(this.fetchSize);
Callable callable = new Callable()
{
@Override
public ResultSet call() throws SQLException
{
logger.log(Level.DEBUG, selectSQL);
return targetStatement.executeQuery(selectSQL);
}
};
Future future = context.getExecutor().submit(callable);
Connection sourceConnection = context.getConnection(context.getSourceDatabase());
Statement sourceStatement = sourceConnection.createStatement();
try
{
sourceStatement.setFetchSize(this.fetchSize);
ResultSet sourceResultSet = sourceStatement.executeQuery(selectSQL);
ResultSet targetResultSet = future.get();
PreparedStatement selectAllStatement = null;
if (!versionColumns.isEmpty())
{
logger.log(Level.DEBUG, selectAllSQL);
selectAllStatement = targetConnection.prepareStatement(selectAllSQL);
}
try
{
logger.log(Level.DEBUG, deleteSQL);
PreparedStatement deleteStatement = targetConnection.prepareStatement(deleteSQL);
try
{
logger.log(Level.DEBUG, insertSQL);
PreparedStatement insertStatement = targetConnection.prepareStatement(insertSQL);
try
{
PreparedStatement updateStatement = null;
if (!nonPrimaryKeyColumns.isEmpty())
{
logger.log(Level.DEBUG, updateSQL);
updateStatement = targetConnection.prepareStatement(updateSQL);
}
try
{
boolean hasMoreSourceResults = sourceResultSet.next();
boolean hasMoreTargetResults = targetResultSet.next();
int insertCount = 0;
int updateCount = 0;
int deleteCount = 0;
while (hasMoreSourceResults || hasMoreTargetResults)
{
int compare = 0;
if (!hasMoreSourceResults)
{
compare = 1;
}
else if (!hasMoreTargetResults)
{
compare = -1;
}
else
{
for (int i = 1; i <= primaryKeyColumns.size(); ++i)
{
Object sourceObject = sourceResultSet.getObject(i);
Object targetObject = targetResultSet.getObject(i);
// We assume that the primary keys column types are Comparable
compare = compare(sourceObject, targetObject);
if (compare != 0)
{
break;
}
}
}
if (compare > 0)
{
deleteStatement.clearParameters();
for (int i = 1; i <= primaryKeyColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(allColumns.get(i - 1)));
deleteStatement.setObject(i, targetResultSet.getObject(i), type);
}
deleteStatement.addBatch();
deleteCount += 1;
if ((deleteCount % this.maxBatchSize) == 0)
{
deleteStatement.executeBatch();
deleteStatement.clearBatch();
}
}
else if (compare < 0)
{
insertStatement.clearParameters();
for (int i = 1; i <= primaryKeyColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(allColumns.get(i - 1)));
insertStatement.setObject(i, sourceResultSet.getObject(i), type);
}
if (versionColumns.isEmpty())
{
for (int i = primaryKeyColumns.size() + 1; i <= allColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(allColumns.get(i - 1)));
Object object = context.getSynchronizationSupport().getObject(sourceResultSet, i, type);
if (sourceResultSet.wasNull())
{
insertStatement.setNull(i, type);
}
else
{
insertStatement.setObject(i, object, type);
}
}
}
else
{
if (selectAllStatement != null)
{
selectAllStatement.clearParameters();
for (int i = 1; i <= primaryKeyColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(allColumns.get(i - 1)));
selectAllStatement.setObject(i, sourceResultSet.getObject(i), type);
}
ResultSet selectAllResultSet = selectAllStatement.executeQuery();
for (int i = primaryKeyColumns.size() + 1; i <= allColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(allColumns.get(i - 1)));
Object object = context.getSynchronizationSupport().getObject(selectAllResultSet, i - primaryKeyColumns.size(), type);
if (selectAllResultSet.wasNull())
{
insertStatement.setNull(i, type);
}
else
{
insertStatement.setObject(i, object, type);
}
}
}
}
insertStatement.addBatch();
insertCount += 1;
if ((insertCount % this.maxBatchSize) == 0)
{
insertStatement.executeBatch();
insertStatement.clearBatch();
}
}
else if (updateStatement != null) // if (compare == 0)
{
updateStatement.clearParameters();
boolean updated = false;
for (int i = primaryKeyColumns.size() + 1; i <= selectColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(selectColumns.get(i - 1)));
Object sourceObject = context.getSynchronizationSupport().getObject(sourceResultSet, i, type);
Object targetObject = context.getSynchronizationSupport().getObject(targetResultSet, i, type);
int index = i - primaryKeyColumns.size();
if (sourceResultSet.wasNull())
{
updateStatement.setNull(index, type);
updated |= !targetResultSet.wasNull();
}
else
{
updateStatement.setObject(index, sourceObject, type);
updated |= targetResultSet.wasNull();
updated |= !Objects.equals(sourceObject, targetObject);
}
}
if (updated)
{
if (selectAllStatement != null)
{
selectAllStatement.clearParameters();
for (int i = 1; i <= primaryKeyColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(allColumns.get(i - 1)));
selectAllStatement.setObject(i, sourceResultSet.getObject(i), type);
}
ResultSet selectAllResultSet = selectAllStatement.executeQuery();
for (int i = primaryKeyColumns.size() + 1; i <= allColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(allColumns.get(i - 1)));
int index = i - primaryKeyColumns.size();
Object object = context.getSynchronizationSupport().getObject(selectAllResultSet, index, type);
if (selectAllResultSet.wasNull())
{
updateStatement.setNull(index, type);
}
else
{
updateStatement.setObject(index, object, type);
}
}
}
for (int i = 1; i <= primaryKeyColumns.size(); ++i)
{
int type = context.getDialect().getColumnType(table.getColumnProperties(allColumns.get(i - 1)));
updateStatement.setObject(i + nonPrimaryKeyColumns.size(), targetResultSet.getObject(i), type);
}
updateStatement.addBatch();
updateCount += 1;
if ((updateCount % this.maxBatchSize) == 0)
{
updateStatement.executeBatch();
updateStatement.clearBatch();
}
}
}
if (hasMoreSourceResults && (compare <= 0))
{
hasMoreSourceResults = sourceResultSet.next();
}
if (hasMoreTargetResults && (compare >= 0))
{
hasMoreTargetResults = targetResultSet.next();
}
}
if ((deleteCount % this.maxBatchSize) > 0)
{
deleteStatement.executeBatch();
}
if ((insertCount % this.maxBatchSize) > 0)
{
insertStatement.executeBatch();
}
if (updateStatement != null)
{
if ((updateCount % this.maxBatchSize) > 0)
{
updateStatement.executeBatch();
}
}
logger.log(Level.INFO, Messages.INSERT_COUNT.getMessage(), insertCount, tableName);
logger.log(Level.INFO, Messages.UPDATE_COUNT.getMessage(), updateCount, tableName);
logger.log(Level.INFO, Messages.DELETE_COUNT.getMessage(), deleteCount, tableName);
}
finally
{
if (updateStatement != null)
{
Resources.close(updateStatement);
}
}
}
finally
{
Resources.close(insertStatement);
}
}
finally
{
Resources.close(deleteStatement);
}
}
finally
{
if (selectAllStatement != null)
{
Resources.close(selectAllStatement);
}
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new SQLException(e);
}
catch (ExecutionException e)
{
throw ExceptionType.SQL.getExceptionFactory().createException(e.getCause());
}
finally
{
Resources.close(sourceStatement);
}
}
finally
{
Resources.close(targetStatement);
}
}
private static int compare(Object object1, Object object2)
{
@SuppressWarnings("unchecked")
Comparable