All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.boon.slumberdb.mysql.BaseVersionedMySQLSupport Maven / Gradle / Ivy

There is a newer version: 0.34
Show newest version
package org.boon.slumberdb.mysql;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import org.boon.Exceptions;
import org.boon.Lists;
import org.boon.Logger;
import org.boon.collections.LazyMap;
import org.boon.core.Conversions;
import org.boon.primitive.CharBuf;
import org.boon.slumberdb.*;
import org.boon.slumberdb.config.GlobalConfig;
import org.boon.slumberdb.entries.Entry;
import org.boon.slumberdb.entries.VersionKey;
import org.boon.slumberdb.entries.VersionedEntry;
import org.boon.slumberdb.spi.VersionedStorageProvider;

import java.sql.*;
import java.util.*;

import static org.boon.Boon.configurableLogger;
import static org.boon.Boon.sputs;
import static org.boon.Exceptions.die;

/**
 * Created by Richard on 9/23/14.
 */
public  class BaseVersionedMySQLSupport implements VersionedStorageProvider {


    protected final String sqlColumnType = "LONGBLOB";
    private final boolean debug = GlobalConfig.DEBUG;
    protected String url;
    protected String userName;
    protected String password;
    protected String table;
    protected Connection connection;
    protected String insertStatementSQL;
    protected String selectStatementSQL;
    protected String searchStatementSQL;
    protected String createStatementSQL;
    protected String deleteStatementSQL;
    protected String tableExistsSQL;
    protected PreparedStatement insert;
    protected PreparedStatement delete;
    protected PreparedStatement select;
    protected PreparedStatement search;
    protected PreparedStatement loadAll;
    protected PreparedStatement allKeys;
    protected PreparedStatement loadAllVersionDataByKeys;

    protected Logger logger = configurableLogger(BaseVersionedMySQLSupport.class);
    protected String loadAllSQL;
    protected int batchSize = 100;
    protected String selectKeysSQL;
    protected int loadKeyCount = 100;
    protected PreparedStatement loadAllByKeysPreparedStatement;

    protected String loadAllByKeysSQL;
    protected String loadAllVersionDataByKeysSQL;

    private long totalConnectionOpen;
    private long totalClosedConnections;
    private long totalErrors;
    private boolean closed;

    private int KEY_POS = 1;
    private int VALUE_POS = 2;
    private int VERSION_POS = 3;
    private int UPDATE_POS = 4;
    private int CREATE_POS = 5;


    public BaseVersionedMySQLSupport(String password, String userName, String url, String table,
                                     int writeBatchSize, int readBatch) {
        this.password = password;
        this.userName = userName;
        this.url = url;
        this.table = table;
        this.batchSize = writeBatchSize;
        this.loadKeyCount = readBatch;


        createSQL(table);
        initDB();
    }


    protected void initDB() {

        connect();
        createTableIfNeeded();
        createPreparedStatements();
    }


    /**
     * Creates a new table set up just the way we need.
     * @param table to create
     */
    protected void createTableSQL(String table) {
        this.createStatementSQL = "\n" +
                "CREATE TABLE " + "`" + table + "` (\n" +
                "  `id` bigint(20) NOT NULL AUTO_INCREMENT,\n" +
                "  `create_timestamp` bigint(20) NOT NULL,\n" +
                "  `version` bigint(20) NOT NULL,\n" +
                "  `update_timestamp` bigint(20) NOT NULL,\n" +
                "  `kv_key` varchar(80) NOT NULL,\n" +
                "  `kv_value` " + sqlColumnType + ",\n" +
                "  PRIMARY KEY (`id`),\n" +
                "  UNIQUE KEY  `" + table + "_kv_key_idx` (`kv_key`)\n" +
                ");\n";
    }




    @Override
    public void removeAll(Iterable keys) {
        initIfNeeded();

        try {

            for (String key : keys) {
                delete.setString(1, key);
                delete.addBatch();
            }

            delete.executeBatch();

        } catch (SQLException e) {

            handle("Unable to removeAll values", e);
        }


    }

    @Override
    public void remove(String key) {
        initIfNeeded();

        if (debug) logger.info("REMOVE KEY", key);

        try {
            delete.setString(1, key);
            delete.executeUpdate();
        } catch (SQLException e) {
            delete = null;
            closed = true;
            connection = null;

            handle(sputs("Unable to remove key", key), e);
        }

    }


    @Override
    public KeyValueIterable> search(final String startKey) {

        initIfNeeded();


        if (debug) logger.info("SEARCH", startKey);


        try {
            search.setString(1, startKey);
            final ResultSet resultSet = search.executeQuery();

            return new KeyValueIterable>() {

                @Override
                public void close() {
                    closeResultSet(resultSet);
                }

                @Override
                public Iterator>> iterator() {

                    return new Iterator>>() {
                        @Override
                        public boolean hasNext() {
                            return resultSetNext(resultSet);
                        }

                        @Override
                        public Entry> next() {
                            try {

                                String key = resultSet.getString(1);
                                byte[] value = getValueColumn(VALUE_POS, resultSet);
                                long version = resultSet.getLong(VERSION_POS);
                                long update = resultSet.getLong(UPDATE_POS);
                                long create = resultSet.getLong(CREATE_POS);

                                VersionedEntry ve = new VersionedEntry<>(key, value);
                                ve.setCreateTimestamp(create);
                                ve.setUpdateTimestamp(update);
                                ve.setVersion(version);


                                return new Entry<>(key, ve);
                            } catch (SQLException e) {
                                handle("Unable to extract values for search query for " + startKey, e);
                                return null;
                            }

                        }

                        @Override
                        public void remove() {

                        }
                    };
                }
            };


        } catch (SQLException e) {
            handle(sputs("Unable to search records search key", startKey, "\nquery=", this.searchStatementSQL), e);
            return null;
        }
    }

    protected boolean resultSetNext(ResultSet resultSet) {

        try {
            return resultSet.next();
        } catch (SQLException e) {
            closeResultSet(resultSet);
            handle("Unable to call next() for result set", e);
            return false;
        }
    }



    @Override
    public Collection loadAllKeys() {

        initIfNeeded();


        if (debug) logger.info("LOAD ALL KEYS");

        LinkedHashSet set = new LinkedHashSet<>();
        ResultSet resultSet = null;

        try {

            resultSet = allKeys.executeQuery();

            while (resultSet.next()) {
                String key = resultSet.getString(1);
                set.add(key);
            }
        } catch (SQLException e) {
            handle("Unable to call next() for result set for loadAllByKeysPreparedStatement query", e);
        } finally {
            closeResultSet(resultSet);
        }


        if (debug) logger.debug("LOAD ALL KEYS BEGETS", set);
        return set;

    }


    @Override
    public VersionedEntry load(String key) {

        initIfNeeded();


        VersionedEntry returnValue = null;

        if (debug) logger.info("LOAD KEY", key);

        try {

            select.setString(1, key);
            final ResultSet resultSet = select.executeQuery();


            if (resultSet.next()) {

                byte[] value = getValueColumn(VALUE_POS, resultSet);
                long version = resultSet.getLong(VERSION_POS);
                long update = resultSet.getLong(UPDATE_POS);
                long create = resultSet.getLong(CREATE_POS);

                returnValue = new VersionedEntry<>(key, value);
                returnValue.setCreateTimestamp(create);
                returnValue.setUpdateTimestamp(update);
                returnValue.setVersion(version);
            }

        } catch (SQLException ex) {
            handle("Unable to load " + key, ex);
        }
        return returnValue;
    }


    protected void keyBatch(LazyMap results, List keyLoadList, boolean getValue) {

        while (keyLoadList.size() < this.loadKeyCount) {
            keyLoadList.add(null);
        }
        try {
            int indexToLoad = 1;
            for (String keyToLoad : keyLoadList) {
                loadAllByKeysPreparedStatement.setString(indexToLoad, keyToLoad);
                indexToLoad++;
            }

            final ResultSet resultSet = loadAllByKeysPreparedStatement.executeQuery();

            while (resultSet.next()) {

                String key = resultSet.getString(KEY_POS);
                byte[] value = getValue ? getValueColumn(VALUE_POS, resultSet) : null;



                long version = resultSet.getLong(VERSION_POS);
                long update = resultSet.getLong(UPDATE_POS);
                long create = resultSet.getLong(CREATE_POS);

                if (getValue) {
                    VersionedEntry returnValue = new VersionedEntry<>(key, value);
                    returnValue.setCreateTimestamp(create);
                    returnValue.setUpdateTimestamp(update);
                    returnValue.setVersion(version);


                    results.put(key, returnValue);
                } else {
                    VersionKey versionKey = new VersionKey(key, version, update, create);

                    results.put(key, versionKey);
                }
            }
            resultSet.close();

        } catch (SQLException ex) {
            handle("Unable to load " + keyLoadList, ex);
        }

    }


    @Override
    public void put(String key, VersionedEntry entry) {

        initIfNeeded();


        if (debug) logger.info("PUT KEY", key, entry);

        try {
            insert.setString(KEY_POS, key);
            setValueColumnQueryParam(VALUE_POS, insert, entry.value());
            insert.setLong(CREATE_POS, entry.createdOn());
            insert.setLong(UPDATE_POS, entry.updatedOn());

            insert.executeUpdate();

        } catch (SQLException e) {
            handle(sputs("Unable to insert key", key, "value", entry), e);
        }


    }



    private void initIfNeeded() {
        if (closed) {
            logger.warn("closed detected, reopening connection");
            initDB();
        }
    }


    @Override
    public void putAll(Map> values) {


        initIfNeeded();


        if (debug) logger.info("PUT ALL ", values);



        int count = 0;
        try {

            Set>> entries = values.entrySet();

            for (Map.Entry> entry : entries) {
                String key = entry.getKey();


                insert.setString(KEY_POS, key);
                setValueColumnQueryParam(VALUE_POS, insert, entry.getValue().value());
                insert.setLong(CREATE_POS, entry.getValue().createdOn());
                insert.setLong(UPDATE_POS, entry.getValue().updatedOn());

                insert.addBatch();

                if (count == batchSize) {
                    count = 0;
                    insert.executeBatch();
                } else {
                    count++;
                }
            }

            insert.executeBatch();

        } catch (SQLException e) {

            boolean recover = true;

            if (e instanceof SQLTransactionRollbackException) {


                for (Map.Entry> entry : values.entrySet()) {
                    try {

                        this.put(entry.getKey(), entry.getValue());

                    } catch (Exception ex) {
                        logger.warn(ex, "BaseMySQLSUpport", "Unable to save", entry.getKey());

                        recover = false;
                    }
                }
            }

            if (!recover) {
                handle("BaseMySQLSUpport Unable to putALl values " + values.size(), e);
            }
        }
    }


    @Override
    public Map> loadAllByKeys(Collection keys) {

        if (debug) logger.info("LOAD ALL BY KEYS ", keys);

        initIfNeeded();


        LazyMap results = new LazyMap(keys.size());
        List keyLoadList = new ArrayList<>(this.loadKeyCount);


        for (String key : keys) {
            keyLoadList.add(key);

            if (keyLoadList.size() == loadKeyCount) {
                keyBatch(results, keyLoadList, true);
                keyLoadList.clear();
            }

        }

        keyBatch(results, keyLoadList, true);
        return (Map>) (Object) results;
    }

    @Override
    public List loadAllVersionInfoByKeys(Collection keys) {


        if (debug) logger.info("LOAD ALL BY KEYS ", keys);

        initIfNeeded();


        LazyMap results = new LazyMap(keys.size());
        List keyLoadList = new ArrayList<>(this.loadKeyCount);


        for (String key : keys) {
            keyLoadList.add(key);

            if (keyLoadList.size() == loadKeyCount) {
                keyBatch(results, keyLoadList, true);
                keyLoadList.clear();
            }

        }

        keyBatch(results, keyLoadList, true);

        return Conversions.toList(results.values());

    }

    /*

     */

    @Override
    public KeyValueIterable> loadAll() {

        if (debug) logger.info("LOAD ALL  ");

        initIfNeeded();


        try {
            final ResultSet resultSet = loadAll.executeQuery();

            return new KeyValueIterable>() {

                @Override
                public void close() {
                    closeResultSet(resultSet);
                }

                @Override
                public Iterator>> iterator() {

                    return new Iterator>>() {
                        @Override
                        public boolean hasNext() {
                            return resultSetNext(resultSet);
                        }

                        @Override
                        public Entry> next() {
                            try {



                                String key = resultSet.getString(KEY_POS);
                                byte[] value = getValueColumn(VALUE_POS, resultSet);
                                long version = resultSet.getLong(VERSION_POS);
                                long update = resultSet.getLong(UPDATE_POS);
                                long create = resultSet.getLong(CREATE_POS);

                                VersionedEntry returnValue = new VersionedEntry<>(key, value);
                                returnValue.setCreateTimestamp(create);
                                returnValue.setUpdateTimestamp(update);
                                returnValue.setVersion(version);



                                return new Entry<>(key, returnValue);
                            } catch (SQLException e) {
                                handle("Unable to extract values for loadAllByKeys query", e);
                                return null;
                            }

                        }

                        @Override
                        public void remove() {

                        }
                    };
                }
            };


        } catch (SQLException e) {
            handle("Unable to load all records", e);
            return null;
        }
    }


    @Override
    public boolean isOpen() {
        return !closed;
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    @Override
    public VersionKey loadVersion(String key) {
        final List versionKeys = loadAllVersionInfoByKeys(Lists.list(key));

        if (versionKeys.size()==1) {
            return versionKeys.get(0);
        } else {
            return VersionKey.notFound(key);
        }
    }





    /**
     * Creates a table if needed.
     */
    protected void createTableIfNeeded() {
        if (closed) {
            return;
        }

        try {


            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery(tableExistsSQL);
            resultSet.next();


        } catch (SQLException e) {
            closed = true;
            this.close();
            this.connect();

            try {

                Statement statement = connection.createStatement();
                statement.execute(createStatementSQL);

            } catch (SQLException e1) {
                handle("Unable to create prepare table " + createStatementSQL, e);
            }
        }
    }


    /**
     * Handles an exception
     * @param message status message
     * @param sqlException sql exception
     */
    protected void handle(String message, SQLException sqlException) {

        totalErrors++;


        if (debug) handleSQLException(sqlException);

        try {
            close();
        } catch (Exception ex) {
            logger.warn(ex, "Problem closing connection after sql exception\n", sqlException);
        }


        Exceptions.handle(message, sqlException);


    }




    /**
     * Connects to the DB and tracks if successful so upstream stuff can try to reconnect.
     */
    protected void connect() {

        try {
            MysqlDataSource dataSource = new MysqlDataSource();
            dataSource.setURL(url);
            dataSource.setPassword(password);
            dataSource.setUser(userName);
            connection = dataSource.getConnection();
            connection.setAutoCommit(true);
            closed = false;
            totalConnectionOpen++;
        } catch (SQLException sqlException) {
            this.closed = true;
            connection = null;

            handle("Unable to connect", sqlException);

        }


    }



    /**
     * Handles an Exception.
     * @param ex
     */
    public void handleSQLException(SQLException ex) {

        SQLException next = ex.getNextException();

        while (next != null) {
            logger.warn(next, "BasyMySQLSupport Nested SQL Exception", next.getMessage());
            next = ex.getNextException();

        }


    }


    protected void createPreparedStatements() {
        if (closed) {
            return;
        }

        try {

            insert = connection.prepareStatement(insertStatementSQL);

            delete = connection.prepareStatement(deleteStatementSQL);

            select = connection.prepareStatement(selectStatementSQL);

            search = connection.prepareStatement(searchStatementSQL);

            loadAll = connection.prepareStatement(loadAllSQL);

            allKeys = connection.prepareStatement(selectKeysSQL);

            loadAllByKeysPreparedStatement = connection.prepareStatement(this.loadAllByKeysSQL);

        } catch (SQLException e) {
            handle("Unable to create prepared statements", e);
        }
    }


    @Override
    public long totalConnectionOpen() {
        return totalConnectionOpen;
    }

    @Override
    public long totalClosedConnections() {
        return totalClosedConnections;
    }

    @Override
    public long totalErrors() {
        return totalErrors;
    }


    protected byte[] getValueColumn(int index, ResultSet resultSet) throws SQLException {
        return resultSet.getBytes(index);
    }

    protected void setValueColumnQueryParam(int index, PreparedStatement p, byte[] value) throws SQLException {
        p.setBytes(index, value);
    }



    /**
     * Create SQL statements
     * @param table
     */
    protected void createSQL(String table) {
        this.insertStatementSQL =  "replace into `" + table + "` (kv_key, kv_value, version, update_timestamp, create_timestamp)" +
                " values (?,?);";
        this.selectStatementSQL = "select kv_key, kv_value, version, update_timestamp, create_timestamp from `" + table +
                "` where kv_key = ?;";
        this.searchStatementSQL = "select kv_key, kv_value, version, update_timestamp, create_timestamp from `" + table
                + "` where kv_key >= ?;";
        this.loadAllSQL = "select kv_key, kv_value, version, update_timestamp, create_timestamp  from `" + table + "`;";
        this.selectKeysSQL = "select kv_key from `" + table + "`;";



        createLoadAllKeysSQL(table);


        this.deleteStatementSQL = "delete  from `" + table + "` where kv_key = ?;";

        this.tableExistsSQL = "select * from `" + table + "` where 1!=1;";

        createTableSQL(table);
        createLoadAllVersionDataSQL(table);

        if (debug)
            logger.info("The following SQL statements will be used", "insert", this.insertStatementSQL, "select", this.selectStatementSQL,
                    "search", this.searchStatementSQL, "LOAD", this.loadAllSQL, "SELECT_KEYS", this.selectKeysSQL,
                    "DELETE", this.deleteStatementSQL, "TABLE EXISTS", this.tableExistsSQL, "CREATE_TABLE", this.createStatementSQL);

    }


    /**
     * Create load all keys SQL.
     * @param table
     */
    protected void createLoadAllKeysSQL(String table) {


        CharBuf buf = CharBuf.create(100);
        buf.add("select kv_key, kv_value, version, update_timestamp, create_timestamp from `");
        buf.add(table);
        buf.add("` where kv_key in (");
        buf.multiply("?,", this.loadKeyCount);
        buf.removeLastChar();
        buf.add(");");

        this.loadAllByKeysSQL = buf.toString();
    }


    /**
     * Create load all keys SQL.
     * @param table table
     */
    protected void createLoadAllVersionDataSQL(String table) {


        CharBuf buf = CharBuf.create(100);
        buf.add("select kv_key, 1, version, update_timestamp, create_timestamp from `");
        buf.add(table);
        buf.add("` where kv_key in (");
        buf.multiply("?,", this.loadKeyCount);
        buf.removeLastChar();
        buf.add(");");

        this.loadAllVersionDataByKeysSQL = buf.toString();
    }



    @Override
    public void close() {
        try {
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            logger.warn("Problem closing", e);
        } finally {
            closed = true;
            connection = null;
            totalClosedConnections++;
        }
    }


    protected void closeResultSet(ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                logger.error("Unable to close result set", e);
            }
        }
    }



}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy