2 * Copyright (C) 2015 Roland Haeder
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org.mxchange.jcore.database.backend.mysql;
19 import java.io.IOException;
20 import java.sql.Connection;
21 import java.sql.DriverManager;
22 import java.sql.PreparedStatement;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.text.MessageFormat;
26 import java.util.Iterator;
27 import java.util.LinkedHashSet;
30 import org.mxchange.jcore.criteria.searchable.SearchableCriteria;
31 import org.mxchange.jcore.database.backend.BaseDatabaseBackend;
32 import org.mxchange.jcore.database.backend.DatabaseBackend;
33 import org.mxchange.jcore.database.frontend.DatabaseFrontend;
34 import org.mxchange.jcore.database.result.DatabaseResult;
35 import org.mxchange.jcore.database.result.Result;
36 import org.mxchange.jcore.database.storage.Storeable;
37 import org.mxchange.jcore.exceptions.UnsupportedDatabaseDriverException;
40 * A backend class for MySQL connections
42 * @author Roland Haeder
44 public class MySqlDatabaseBackend extends BaseDatabaseBackend implements DatabaseBackend {
46 * An instance of a datbase connection
48 private static Connection connection;
51 * Prepared statement for total row count
53 private PreparedStatement totalRows;
56 * Constructor with table name
58 * @param frontend An instance of the frontend
59 * @throws org.mxchange.jcore.exceptions.UnsupportedDatabaseDriverException If the requested driver is not supported
61 public MySqlDatabaseBackend (final DatabaseFrontend frontend) throws UnsupportedDatabaseDriverException {
63 this.getLogger().trace(MessageFormat.format("frontend={0} - CALLED!", frontend)); //NOI18N
66 this.validateDriver("mysql"); //NOI18N
69 String tableName = frontend.getTableName();
72 this.getLogger().debug(MessageFormat.format("tableName={0}", tableName)); //NOI18N
74 // Now that the driver is there, set the table name
75 this.setTableName(tableName);
78 this.setFrontend(frontend);
82 public void connectToDatabase () throws SQLException {
84 this.getLogger().trace("CALLED!"); //NOI18N
86 // Is the connection already there?
87 if (connection instanceof Connection) {
88 // Is the connection really up?
89 if (connection.isClosed()) {
90 // Connection is closed again
91 throw new SQLException("Connection is closed."); //NOI18N
95 this.getLogger().debug("Connection is already established."); //NOI18N
101 // Generate connection string
102 String connect = String.format("jdbc:mysql://%s/%s", //NOI18N
103 this.getProperty("database.mysql.host"), //NOI18N
104 this.getProperty("database.mysql.dbname") //NOI18N
108 this.getLogger().debug(MessageFormat.format("Attempting to connect to {0} ...", connect)); //NOI18N
110 // Now get a connection instance back
111 connection = DriverManager.getConnection(
113 this.getProperty("database.mysql.login"), //NOI18N
114 this.getProperty("database.mysql.password") //NOI18N
118 this.getLogger().debug("Connection is up, preparing some statements ..."); //NOI18N
120 // Set prepared statement
121 this.totalRows = connection.prepareStatement(String.format("SELECT COUNT(`%s`) AS `cnt` FROM `%s` LIMIT 1", this.getFrontend().getIdName(), this.getTableName())); //NOI18N
124 this.getLogger().trace("EXIT!"); //NOI18N
128 * Inserts given dataset instance and returns a Result instance on success.
129 * Please note that this method can insert only a single record into
130 * database. Multiple inserts are not yet supported.
132 * @param dataset A dataset instance
133 * @return An instance of Result
134 * @todo Support more than one record being inserted in a separate method
137 public Result<? extends Storeable> doInsertDataSet (final Map<String, Object> dataset) throws SQLException {
139 this.getLogger().trace(MessageFormat.format("dataset={0} - CALLED!", dataset)); //NOI18N
141 // dataset should not be null and not empty
142 if (dataset == null) {
143 // It is null, so abort here
144 throw new NullPointerException("dataset is null"); //NOI18N
145 } else if (dataset.isEmpty()) {
146 // It is empty, also abort here
147 throw new IllegalArgumentException("dataset is empty"); //NOI18N
151 this.getLogger().debug(MessageFormat.format("Need to parse {0} values ...", dataset.size())); //NOI18N
154 Set<Object> values = new LinkedHashSet<>(dataset.size());
156 // Start with INSERT INTO
157 StringBuilder query = new StringBuilder(String.format("INSERT INTO `%s` (", this.getTableName())); //NOI18N
158 StringBuilder valueQuery = new StringBuilder("("); //NOI18N
160 // Get iterator from it
161 Iterator<Map.Entry<String, Object>> iterator = dataset.entrySet().iterator();
163 // "Walk" over all entries
164 while (iterator.hasNext()) {
166 Map.Entry<String, Object> entry = iterator.next();
168 // Add key as database column to query
169 query.append(String.format("`%s`,", entry.getKey())); //NOI18N
172 Object value = entry.getValue();
175 this.getLogger().debug(MessageFormat.format("value={0} - BEFORE!", value)); //NOI18N
177 // Handle possible empty->null convertion
178 value = this.getFrontend().emptyStringToNull(entry.getKey(), value);
181 this.getLogger().debug(MessageFormat.format("value={0} - AFTER!", value)); //NOI18N
183 // Is the value null?
186 valueQuery.append("NULL,"); //NOI18N
189 valueQuery.append("?,"); //NOI18N
194 // Now put all together
195 query.replace(query.length() - 1, query.length(), ") VALUES "); //NOI18N
196 query.append(valueQuery.substring(0, valueQuery.length() - 1));
197 query.append(")"); //NOI18N
199 // Full statement is complete here, better log it
200 this.getLogger().debug(MessageFormat.format("query={0} is complete.", query)); //NOI18N
202 // Prepare statement instance
203 PreparedStatement statement = this.getPreparedStatement(query, values);
206 this.getLogger().debug(MessageFormat.format("statement={0}", statement));
209 int status = statement.executeUpdate();
212 this.getLogger().debug(MessageFormat.format("status={0}", status)); //NOI18N
214 // The result set needs to be transformed into Result, so initialize a result instance here
215 Result<? extends Storeable> result = new DatabaseResult(status, statement);
218 this.getLogger().trace(MessageFormat.format("result={0} - EXIT!", result)); //NOI18N
225 public Result<? extends Storeable> doSelectByCriteria (final SearchableCriteria critera) throws SQLException {
227 this.getLogger().trace(MessageFormat.format("criteria={0} - CALLED!", critera)); //NOI18N
229 // criteria must not be null
230 if (critera == null) {
232 throw new NullPointerException("criteria is null"); //NOI18N
235 // Start SELECT query
236 StringBuilder query = new StringBuilder(String.format("SELECT * FROM `%s`", this.getTableName())); //NOI18N
239 Set<Map.Entry<String, Object>> set = critera.entrySet();
242 this.getLogger().debug(MessageFormat.format("set.isEmpty()={0}", set.isEmpty())); //NOI18N
245 Set<Object> values = new LinkedHashSet<>(set.size());
247 // Are there conditions?
248 if (!set.isEmpty()) {
249 // Continue with WHERE
250 query.append(" WHERE "); //NOI18N
252 // No more than 1 value currently
253 if (set.size() > 1) {
255 throw new IllegalArgumentException("More than one criteria is not supported yet."); //NOI18N
259 Iterator<Map.Entry<String, Object>> iterator = set.iterator();
262 while (iterator.hasNext()) {
264 Map.Entry<String, Object> entry = iterator.next();
266 // Add key as column name
267 query.append(String.format("`%s`", entry.getKey())); //NOI18N
270 Object value = entry.getValue();
273 this.getLogger().debug(MessageFormat.format("value={0}", value)); //NOI18N
276 query.append("=?"); //NOI18N
282 if (critera.getLimit() > 0) {
284 if (critera.getSkip() > 0) {
286 query.append(String.format(" LIMIT %d,%d", critera.getSkip(), critera.getLimit())); //NOI18N
289 query.append(String.format(" LIMIT %d", critera.getLimit())); //NOI18N
293 // Full statement is complete here, better log it
294 this.getLogger().debug(MessageFormat.format("query={0} is complete.", query)); //NOI18N
296 // Get a prepared instance
297 PreparedStatement statement = this.getPreparedStatement(query, values);
300 this.getLogger().debug(MessageFormat.format("statement={0}", statement));
303 ResultSet resultSet = statement.executeQuery();
306 this.getLogger().debug(MessageFormat.format("resultSet={0}", resultSet));
308 // The result set needs to be transformed into Result, so initialize a result instance here
309 Result<? extends Storeable> result = this.getFrontend().getResultFromSet(resultSet);
312 this.getLogger().trace(MessageFormat.format("result={0} - EXIT!", result)); //NOI18N
319 public void doShutdown () throws SQLException, IOException {
321 this.getLogger().trace("CALLED!"); //NOI18N
323 // Is the connection still there?
324 if (!connection.isClosed()) {
325 // Close down database connection
330 this.getLogger().trace("EXIT!"); //NOI18N
334 public int getTotalRows () throws IOException, SQLException {
336 this.getLogger().trace("CALLED!"); //NOI18N
339 ResultSet set = this.totalRows.executeQuery();
341 // Rewind to beginning
345 int count = set.getInt("cnt"); //NOI18N
348 this.getLogger().trace(MessageFormat.format("count={0} - EXIT!", count)); //NOI18N
355 * Some "getter" for a prepared statement with inserted values
357 * @param query SQL query string
358 * @param values Values set
359 * @return A fully prepared statement
361 private PreparedStatement getPreparedStatement (final StringBuilder query, final Set<Object> values) throws SQLException {
363 this.getLogger().trace(MessageFormat.format("query={0},values={1} - CALLED!", query, values)); //NOI18N
365 // Init prepared statement
366 PreparedStatement statement = connection.prepareStatement(query.toString());
369 this.getLogger().debug(MessageFormat.format("statement={0}", statement)); //NOI18N
371 // Get iterator on values
372 Iterator<Object> valueIterator = values.iterator();
378 while (valueIterator.hasNext()) {
380 Object value = valueIterator.next();
383 this.getLogger().debug(MessageFormat.format("index={1} has value={0}", value, index)); //NOI18N
386 if (value instanceof Boolean) {
388 this.getLogger().debug(MessageFormat.format("Setting boolean value={0} for index={1}", value, index)); //NOI18N
391 statement.setBoolean(index, (boolean) value);
392 } else if (value instanceof String) {
394 this.getLogger().debug(MessageFormat.format("Setting string value={0} for index={1}", value, index)); //NOI18N
397 statement.setString(index, (String) value);
398 } else if (value instanceof Integer) {
400 this.getLogger().debug(MessageFormat.format("Setting integer value={0} for index={1}", value, index)); //NOI18N
403 statement.setInt(index, (int) value);
404 } else if (value instanceof Long) {
406 this.getLogger().debug(MessageFormat.format("Setting long value={0} for index={1}", value, index)); //NOI18N
409 statement.setLong(index, (long) value);
410 } else if (value instanceof Float) {
412 this.getLogger().debug(MessageFormat.format("Setting float value={0} for index={1}", value, index)); //NOI18N
415 statement.setFloat(index, (float) value);
416 } else if (value == null) {
418 this.getLogger().warn(MessageFormat.format("Null value in index={0} is not supported (yet)", index)); //NOI18N
420 // Not parseable type
421 throw new SQLException(MessageFormat.format("Cannot handle value({0})={1} for index={2} in table {3}", value.getClass().getSimpleName(), value, index, this.getTableName())); //NOI18N
429 this.getLogger().trace(MessageFormat.format("statement={0} - EXIT!", statement)); //NOI18N