net.sf.hajdbc.sync.FullSynchronizationStrategy Maven / Gradle / Ivy
/*
* 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.Collection;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
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.logging.Level;
import net.sf.hajdbc.logging.Logger;
import net.sf.hajdbc.logging.LoggerFactory;
import net.sf.hajdbc.util.Resources;
import net.sf.hajdbc.util.Strings;
/**
* Database-independent synchronization strategy that does full record transfer between two databases.
* This strategy is best used when there are many differences between the active database and the inactive database (i.e. very much 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:
*
* - Delete all rows in the inactive database table
* - Query all rows on the active database table
* - For each row in active database table:
*
* - Insert new row into inactive database table
*
*
*
*
* - Re-create the foreign keys on the inactive database
* - Synchronize sequences
*
* @author Paul Ferraro
*/
public class FullSynchronizationStrategy implements SynchronizationStrategy, TableSynchronizationStrategy
{
private static final long serialVersionUID = 9190347092842178162L;
static Logger logger = LoggerFactory.getLogger(FullSynchronizationStrategy.class);
private SynchronizationStrategy strategy = new PerTableSynchronizationStrategy(this);
private int maxBatchSize = 100;
private int fetchSize = 0;
@Override
public String getId()
{
return "full";
}
@Override
public > void init(DatabaseCluster cluster)
{
this.strategy.init(cluster);
}
@Override
public > void synchronize(SynchronizationContext context) throws SQLException
{
this.strategy.synchronize(context);
}
@Override
public > void destroy(DatabaseCluster cluster)
{
this.strategy.destroy(cluster);
}
@Override
public > void synchronize(SynchronizationContext context, TableProperties table) throws SQLException
{
final String tableName = table.getName().getDMLName();
final Collection columns = table.getColumns();
final String commaDelimitedColumns = Strings.join(columns, Strings.PADDED_COMMA);
final String selectSQL = String.format("SELECT %s FROM %s", commaDelimitedColumns, tableName);
final String deleteSQL = context.getDialect().getTruncateTableSQL(table);
final String insertSQL = String.format("INSERT INTO %s (%s) VALUES (%s)", tableName, commaDelimitedColumns, Strings.join(Collections.nCopies(columns.size(), Strings.QUESTION), Strings.PADDED_COMMA));
Connection sourceConnection = context.getConnection(context.getSourceDatabase());
final Statement selectStatement = sourceConnection.createStatement();
try
{
selectStatement.setFetchSize(this.fetchSize);
Callable callable = new Callable()
{
@Override
public ResultSet call() throws SQLException
{
logger.log(Level.DEBUG, selectSQL);
return selectStatement.executeQuery(selectSQL);
}
};
Future future = context.getExecutor().submit(callable);
Connection targetConnection = context.getConnection(context.getTargetDatabase());
Statement deleteStatement = targetConnection.createStatement();
try
{
logger.log(Level.DEBUG, deleteSQL);
int deletedRows = deleteStatement.executeUpdate(deleteSQL);
logger.log(Level.INFO, Messages.DELETE_COUNT.getMessage(), deletedRows, tableName);
}
finally
{
Resources.close(deleteStatement);
}
logger.log(Level.DEBUG, insertSQL);
PreparedStatement insertStatement = targetConnection.prepareStatement(insertSQL);
try
{
int statementCount = 0;
ResultSet resultSet = future.get();
while (resultSet.next())
{
int index = 0;
for (String column: table.getColumns())
{
index += 1;
int type = context.getDialect().getColumnType(table.getColumnProperties(column));
Object object = context.getSynchronizationSupport().getObject(resultSet, index, type);
if (resultSet.wasNull())
{
insertStatement.setNull(index, type);
}
else
{
insertStatement.setObject(index, object, type);
}
}
insertStatement.addBatch();
statementCount += 1;
if ((statementCount % this.maxBatchSize) == 0)
{
insertStatement.executeBatch();
insertStatement.clearBatch();
}
insertStatement.clearParameters();
}
if ((statementCount % this.maxBatchSize) > 0)
{
insertStatement.executeBatch();
}
logger.log(Level.INFO, Messages.INSERT_COUNT.getMessage(), statementCount, table);
}
catch (ExecutionException e)
{
throw ExceptionType.SQL.getExceptionFactory().createException(e.getCause());
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new SQLException(e);
}
finally
{
Resources.close(insertStatement);
}
}
finally
{
Resources.close(selectStatement);
}
}
@Override
public > void dropConstraints(SynchronizationContext context) throws SQLException
{
context.getSynchronizationSupport().dropForeignKeys();
}
@Override
public > void restoreConstraints(SynchronizationContext context) throws SQLException
{
context.getSynchronizationSupport().restoreForeignKeys();
}
/**
* @return the fetchSize.
*/
public int getFetchSize()
{
return this.fetchSize;
}
/**
* @param fetchSize the fetchSize to set.
*/
public void setFetchSize(int fetchSize)
{
this.fetchSize = fetchSize;
}
/**
* @return the maxBatchSize.
*/
public int getMaxBatchSize()
{
return this.maxBatchSize;
}
/**
* @param maxBatchSize the maxBatchSize to set.
*/
public void setMaxBatchSize(int maxBatchSize)
{
this.maxBatchSize = maxBatchSize;
}
}