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

org.apache.ignite.cache.store.jdbc.CacheJdbcBlobStore Maven / Gradle / Ivy

Go to download

Java-based middleware for in-memory processing of big data in a distributed environment.

There is a newer version: 3.0.0-beta1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ignite.cache.store.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;
import javax.cache.Cache;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriterException;
import javax.sql.DataSource;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.cache.store.CacheStore;
import org.apache.ignite.cache.store.CacheStoreAdapter;
import org.apache.ignite.cache.store.CacheStoreSession;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.marshaller.Marshaller;
import org.apache.ignite.marshaller.jdk.JdkMarshaller;
import org.apache.ignite.resources.CacheStoreSessionResource;
import org.apache.ignite.resources.IgniteInstanceResource;
import org.apache.ignite.resources.LoggerResource;
import org.apache.ignite.transactions.Transaction;
import org.jetbrains.annotations.Nullable;

/**
 * {@link CacheStore} implementation backed by JDBC. This implementation
 * stores objects in underlying database in {@code BLOB} format.
 * 

* Store will create table {@code ENTRIES} in the database to store data. * Table will have {@code key} and {@code val} fields. *

* If custom DDL and DML statements are provided, table and field names have * to be consistent for all statements and sequence of parameters have to be * preserved. *

Configuration

* Sections below describe mandatory and optional configuration settings as well * as providing example using Java and Spring XML. *

Mandatory

* There are no mandatory configuration parameters. *

Optional

*
    *
  • Data source (see {@link #setDataSource(DataSource)}
  • *
  • Connection URL (see {@link #setConnectionUrl(String)})
  • *
  • User name (see {@link #setUser(String)})
  • *
  • Password (see {@link #setPassword(String)})
  • *
  • Create table query (see {@link #setConnectionUrl(String)})
  • *
  • Load entry query (see {@link #setLoadQuery(String)})
  • *
  • Update entry query (see {@link #setUpdateQuery(String)})
  • *
  • Insert entry query (see {@link #setInsertQuery(String)})
  • *
  • Delete entry query (see {@link #setDeleteQuery(String)})
  • *
*

* Use {@link CacheJdbcBlobStoreFactory} factory to pass {@link CacheJdbcBlobStore} to {@link CacheConfiguration}. */ public class CacheJdbcBlobStore extends CacheStoreAdapter { /** Default connection URL (value is jdbc:h2:mem:jdbcCacheStore;DB_CLOSE_DELAY=-1). */ public static final String DFLT_CONN_URL = "jdbc:h2:mem:jdbcCacheStore;DB_CLOSE_DELAY=-1"; /** * Default create table query * (value is create table if not exists ENTRIES (akey binary primary key, val binary)). */ public static final String DFLT_CREATE_TBL_QRY = "create table if not exists ENTRIES " + "(akey binary primary key, val binary)"; /** Default load entry query (value is select * from ENTRIES where akey=?). */ public static final String DFLT_LOAD_QRY = "select * from ENTRIES where akey=?"; /** Default update entry query (value is select * from ENTRIES where akey=?). */ public static final String DFLT_UPDATE_QRY = "update ENTRIES set val=? where akey=?"; /** Default insert entry query (value is insert into ENTRIES (akey, val) values (?, ?)). */ public static final String DFLT_INSERT_QRY = "insert into ENTRIES (akey, val) values (?, ?)"; /** Default delete entry query (value is delete from ENTRIES where akey=?). */ public static final String DFLT_DEL_QRY = "delete from ENTRIES where akey=?"; /** Connection attribute name. */ private static final String ATTR_CONN = "JDBC_STORE_CONNECTION"; /** Marshaller. */ private static final Marshaller marsh = new JdkMarshaller(); /** Connection URL. */ private String connUrl = DFLT_CONN_URL; /** Query to create table. */ private String createTblQry = DFLT_CREATE_TBL_QRY; /** Query to load entry. */ private String loadQry = DFLT_LOAD_QRY; /** Query to update entry. */ private String updateQry = DFLT_UPDATE_QRY; /** Query to insert entries. */ private String insertQry = DFLT_INSERT_QRY; /** Query to delete entries. */ private String delQry = DFLT_DEL_QRY; /** User name for database access. */ private String user; /** Password for database access. */ @GridToStringExclude private String passwd; /** Data source. */ private DataSource dataSrc; /** Flag for schema initialization. */ private boolean initSchema = true; /** Auto-injected store session. */ @CacheStoreSessionResource private CacheStoreSession ses; /** Log. */ @LoggerResource private IgniteLogger log; /** Marshaller. */ @IgniteInstanceResource private Ignite ignite; /** Init guard. */ @GridToStringExclude private final AtomicBoolean initGuard = new AtomicBoolean(); /** Init latch. */ @GridToStringExclude private final CountDownLatch initLatch = new CountDownLatch(1); /** Opened connections. */ @GridToStringExclude private final LongAdder opened = new LongAdder(); /** Closed connections. */ @GridToStringExclude private final LongAdder closed = new LongAdder(); /** Test mode flag. */ @GridToStringExclude private boolean testMode; /** Successful initialization flag. */ private boolean initOk; /** {@inheritDoc} */ @Override public void sessionEnd(boolean commit) { init(); Transaction tx = transaction(); Map props = session().properties(); Connection conn = props.remove(ATTR_CONN); if (conn != null) { try { if (commit) conn.commit(); else conn.rollback(); } catch (SQLException e) { throw new CacheWriterException("Failed to end transaction [xid=" + tx.xid() + ", commit=" + commit + ']', e); } finally { closeConnection(conn); } } if (log.isDebugEnabled()) log.debug("Transaction ended [xid=" + tx.xid() + ", commit=" + commit + ']'); } /** {@inheritDoc} */ @Override public V load(K key) { init(); Transaction tx = transaction(); if (log.isDebugEnabled()) log.debug("Store load [key=" + key + ", tx=" + tx + ']'); Connection conn = null; PreparedStatement stmt = null; try { conn = connection(tx); stmt = conn.prepareStatement(loadQry); stmt.setObject(1, toBytes(key)); ResultSet rs = stmt.executeQuery(); if (rs.next()) return fromBytes(rs.getBytes(2)); } catch (IgniteCheckedException | SQLException e) { throw new CacheLoaderException("Failed to load object: " + key, e); } finally { end(tx, conn, stmt); } return null; } /** {@inheritDoc} */ @Override public void write(Cache.Entry entry) { init(); Transaction tx = transaction(); K key = entry.getKey(); V val = entry.getValue(); if (log.isDebugEnabled()) log.debug(S.toString("Store put", "key", key, true, "val", val, true, "tx", tx, false)); Connection conn = null; PreparedStatement stmt = null; try { conn = connection(tx); stmt = conn.prepareStatement(updateQry); stmt.setObject(1, toBytes(val)); stmt.setObject(2, toBytes(key)); if (stmt.executeUpdate() == 0) { stmt.close(); stmt = conn.prepareStatement(insertQry); stmt.setObject(1, toBytes(key)); stmt.setObject(2, toBytes(val)); stmt.executeUpdate(); } } catch (IgniteCheckedException | SQLException e) { throw new CacheWriterException("Failed to put object [key=" + key + ", val=" + val + ']', e); } finally { end(tx, conn, stmt); } } /** {@inheritDoc} */ @Override public void delete(Object key) { init(); Transaction tx = transaction(); if (log.isDebugEnabled()) log.debug("Store remove [key=" + key + ", tx=" + tx + ']'); Connection conn = null; PreparedStatement stmt = null; try { conn = connection(tx); stmt = conn.prepareStatement(delQry); stmt.setObject(1, toBytes(key)); stmt.executeUpdate(); } catch (IgniteCheckedException | SQLException e) { throw new CacheWriterException("Failed to remove object: " + key, e); } finally { end(tx, conn, stmt); } } /** * @param tx Cache transaction. * @return Connection. * @throws SQLException In case of error. */ private Connection connection(@Nullable Transaction tx) throws SQLException { if (tx != null) { Map props = session().properties(); Connection conn = props.get(ATTR_CONN); if (conn == null) { conn = openConnection(false); // Store connection in session properties, so it can be accessed // for other operations on the same transaction. props.put(ATTR_CONN, conn); } return conn; } // Transaction can be null in case of simple load operation. else return openConnection(true); } /** * Closes allocated resources depending on transaction status. * * @param tx Active transaction, if any. * @param conn Allocated connection. * @param st Created statement, */ private void end(@Nullable Transaction tx, Connection conn, Statement st) { U.closeQuiet(st); if (tx == null) // Close connection right away if there is no transaction. closeConnection(conn); } /** * Gets connection from a pool. * * @param autocommit {@code true} If connection should use autocommit mode. * @return Pooled connection. * @throws SQLException In case of error. */ private Connection openConnection(boolean autocommit) throws SQLException { Connection conn = dataSrc != null ? dataSrc.getConnection() : DriverManager.getConnection(connUrl, user, passwd); if (testMode) opened.increment(); conn.setAutoCommit(autocommit); return conn; } /** * Closes connection. * * @param conn Connection to close. */ private void closeConnection(Connection conn) { U.closeQuiet(conn); if (testMode) closed.increment(); } /** * Initializes store. * * @throws IgniteException If failed to initialize. */ private void init() { if (initLatch.getCount() > 0) { if (initGuard.compareAndSet(false, true)) { if (log.isDebugEnabled()) log.debug("Initializing cache store."); if (F.isEmpty(connUrl)) throw new IgniteException("Failed to initialize cache store (connection URL is not provided)."); if (!initSchema) { initLatch.countDown(); return; } if (F.isEmpty(createTblQry)) throw new IgniteException("Failed to initialize cache store (create table query is not provided)."); Connection conn = null; Statement stmt = null; try { conn = openConnection(false); stmt = conn.createStatement(); stmt.execute(createTblQry); conn.commit(); initOk = true; } catch (SQLException e) { throw new IgniteException("Failed to create database table.", e); } finally { U.closeQuiet(stmt); closeConnection(conn); initLatch.countDown(); } } else { try { U.await(initLatch); } catch (IgniteInterruptedCheckedException e) { throw new IgniteException(e); } } } if (!initOk) throw new IgniteException("Cache store was not properly initialized."); } /** * Flag indicating whether DB schema should be initialized by Ignite (default behaviour) or * was explicitly created by user. * * @param initSchema {@code True} if DB schema should be initialized by Ignite (default behaviour), * {code @false} if schema was explicitly created by user. */ public void setInitSchema(boolean initSchema) { this.initSchema = initSchema; } /** * Sets connection URL. * * @param connUrl Connection URL. */ public void setConnectionUrl(String connUrl) { this.connUrl = connUrl; } /** * Sets create table query. * * @param createTblQry Create table query. */ public void setCreateTableQuery(String createTblQry) { this.createTblQry = createTblQry; } /** * Sets load query. * * @param loadQry Load query */ public void setLoadQuery(String loadQry) { this.loadQry = loadQry; } /** * Sets update entry query. * * @param updateQry Update entry query. */ public void setUpdateQuery(String updateQry) { this.updateQry = updateQry; } /** * Sets insert entry query. * * @param insertQry Insert entry query. */ public void setInsertQuery(String insertQry) { this.insertQry = insertQry; } /** * Sets delete entry query. * * @param delQry Delete entry query. */ public void setDeleteQuery(String delQry) { this.delQry = delQry; } /** * Sets user name for database access. * * @param user User name. */ public void setUser(String user) { this.user = user; } /** * Sets password for database access. * * @param passwd Password. */ public void setPassword(String passwd) { this.passwd = passwd; } /** * Sets data source. Data source should be fully configured and ready-to-use. *

* Note that if data source is provided, all connections will be * acquired via this data source. If data source is not provided, a new connection * will be created for each store call ({@code connectionUrl}, * {@code user} and {@code password} parameters will be used). * * @param dataSrc Data source. */ public void setDataSource(DataSource dataSrc) { this.dataSrc = dataSrc; } /** {@inheritDoc} */ @Override public String toString() { return S.toString(CacheJdbcBlobStore.class, this, "passwd", passwd != null ? "*" : null); } /** * Serialize object to byte array using marshaller. * * @param obj Object to convert to byte array. * @return Byte array. * @throws IgniteCheckedException If failed to convert. */ protected byte[] toBytes(Object obj) throws IgniteCheckedException { return U.marshal(marsh, obj); } /** * Deserialize object from byte array using marshaller. * * @param bytes Bytes to deserialize. * @param Result object type. * @return Deserialized object. * @throws IgniteCheckedException If failed. */ protected X fromBytes(byte[] bytes) throws IgniteCheckedException { if (bytes == null || bytes.length == 0) return null; return U.unmarshal(marsh, bytes, getClass().getClassLoader()); } /** * @return Current transaction. */ @Nullable private Transaction transaction() { CacheStoreSession ses = session(); return ses != null ? ses.transaction() : null; } /** * @return Store session. */ protected CacheStoreSession session() { return ses; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy