/** * Copyright 2016 Hortonworks. * <p> * Licensed 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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 com.hortonworks.registries.storage.impl.jdbc.provider.mysql.factory; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; import com.hortonworks.registries.storage.OrderByField; import com.hortonworks.registries.storage.Storable; import com.hortonworks.registries.storage.StorableKey; import com.hortonworks.registries.storage.impl.jdbc.config.ExecutionConfig; import com.hortonworks.registries.storage.impl.jdbc.connection.ConnectionBuilder; import com.hortonworks.registries.storage.impl.jdbc.connection.HikariCPConnectionBuilder; import com.hortonworks.registries.storage.impl.jdbc.provider.mysql.query.MySqlInsertQuery; import com.hortonworks.registries.storage.impl.jdbc.provider.mysql.query.MySqlInsertUpdateDuplicate; import com.hortonworks.registries.storage.impl.jdbc.provider.mysql.query.MySqlSelectQuery; import com.hortonworks.registries.storage.impl.jdbc.provider.sql.factory.AbstractQueryExecutor; import com.hortonworks.registries.storage.impl.jdbc.provider.sql.query.SqlQuery; import com.hortonworks.registries.storage.impl.jdbc.provider.sql.statement.PreparedStatementBuilder; import com.hortonworks.registries.storage.impl.jdbc.util.Util; import com.zaxxer.hikari.HikariConfig; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Properties; /** * SQL query executor for MySQL DB. * * To issue the new ID to insert and get auto issued key in concurrent manner, MySqlExecutor utilizes MySQL's * auto increment feature and JDBC's getGeneratedKeys() which is described to MySQL connector doc: * https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-usagenotes-last-insert-id.html * * If the value of id is null, we let MySQL issue new ID and get the new ID. If the value of id is not null, we just use that value. */ public class MySqlExecutor extends AbstractQueryExecutor { /** * @param config Object that contains arbitrary configuration that may be needed for any of the steps of the query execution process * @param connectionBuilder Object that establishes the connection to the database */ public MySqlExecutor(ExecutionConfig config, ConnectionBuilder connectionBuilder) { super(config, connectionBuilder); } /** * @param config Object that contains arbitrary configuration that may be needed for any of the steps of the query execution process * @param connectionBuilder Object that establishes the connection to the database * @param cacheBuilder Guava cache configuration. The maximum number of entries in cache (open connections) * must not exceed the maximum number of open database connections allowed */ public MySqlExecutor(ExecutionConfig config, ConnectionBuilder connectionBuilder, CacheBuilder<SqlQuery, PreparedStatementBuilder> cacheBuilder) { super(config, connectionBuilder, cacheBuilder); } // ============= Public API methods ============= @Override public void insert(Storable storable) { insertOrUpdateWithUniqueId(storable, new MySqlInsertQuery(storable)); } @Override public void insertOrUpdate(final Storable storable) { insertOrUpdateWithUniqueId(storable, new MySqlInsertUpdateDuplicate(storable)); } @Override public Long nextId(String namespace) { // We intentionally return null. Please refer the class javadoc for more details. return null; } @Override public <T extends Storable> Collection<T> select(String namespace) { return executeQuery(namespace, new MySqlSelectQuery(namespace)); } @Override public <T extends Storable> Collection<T> select(StorableKey storableKey) { return executeQuery(storableKey.getNameSpace(), new MySqlSelectQuery(storableKey)); } @Override public <T extends Storable> Collection<T> select(String namespace, List<OrderByField> orderByFields) { return executeQuery(namespace, new MySqlSelectQuery(namespace, orderByFields)); } @Override public <T extends Storable> Collection<T> select(StorableKey storableKey, List<OrderByField> orderByFields) { return executeQuery(storableKey.getNameSpace(), new MySqlSelectQuery(storableKey, orderByFields)); } public static MySqlExecutor createExecutor(Map<String, Object> jdbcProps) { Util.validateJDBCProperties(jdbcProps, Lists.newArrayList("dataSourceClassName", "dataSource.url")); String dataSourceClassName = (String) jdbcProps.get("dataSourceClassName"); log.info("data source class: [{}]", dataSourceClassName); String jdbcUrl = (String) jdbcProps.get("dataSource.url"); log.info("dataSource.url is: [{}] ", jdbcUrl); int queryTimeOutInSecs = -1; if (jdbcProps.containsKey("queryTimeoutInSecs")) { queryTimeOutInSecs = (Integer) jdbcProps.get("queryTimeoutInSecs"); if (queryTimeOutInSecs < 0) { throw new IllegalArgumentException("queryTimeoutInSecs property can not be negative"); } } Properties properties = new Properties(); properties.putAll(jdbcProps); HikariConfig hikariConfig = new HikariConfig(properties); HikariCPConnectionBuilder connectionBuilder = new HikariCPConnectionBuilder(hikariConfig); ExecutionConfig executionConfig = new ExecutionConfig(queryTimeOutInSecs); return new MySqlExecutor(executionConfig, connectionBuilder); } private void insertOrUpdateWithUniqueId(final Storable storable, final SqlQuery sqlQuery) { try { Long id = storable.getId(); if (id == null) { id = executeUpdateWithReturningGeneratedKey(sqlQuery); storable.setId(id); } else { executeUpdate(sqlQuery); } } catch (UnsupportedOperationException e) { executeUpdate(sqlQuery); } } }