diff --git a/build.gradle b/build.gradle index 20c8190..aa87b3b 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,8 @@ ext { ]], commons_text: 'org.apache.commons:commons-text:1.9', lombok: 'org.projectlombok:lombok:1.18.12', - reflection_object: 'ru.dmitriymx:reflection-object:1.0-BETA' + reflection_object: 'ru.dmitriymx:reflection-object:1.0-BETA', + mysql: 'mysql:mysql-connector-java:8.0.22' ] } @@ -45,6 +46,7 @@ dependencies { annotationProcessor libs.lombok compileOnly2 libs.bukkit + compileOnly libs.mysql implementation libs.commons_text implementation libs.reflection_object } \ No newline at end of file diff --git a/src/main/java/ghast/database/CannotGetJdbcConnectionException.java b/src/main/java/ghast/database/CannotGetJdbcConnectionException.java new file mode 100644 index 0000000..1d02152 --- /dev/null +++ b/src/main/java/ghast/database/CannotGetJdbcConnectionException.java @@ -0,0 +1,10 @@ +package ghast.database; + +import java.sql.SQLException; + +public class CannotGetJdbcConnectionException extends DataAccessException { + + public CannotGetJdbcConnectionException(String msg, SQLException ex) { + super(msg, ex); + } +} diff --git a/src/main/java/ghast/database/DataAccessException.java b/src/main/java/ghast/database/DataAccessException.java new file mode 100644 index 0000000..78c469b --- /dev/null +++ b/src/main/java/ghast/database/DataAccessException.java @@ -0,0 +1,12 @@ +package ghast.database; + +public class DataAccessException extends RuntimeException { + + public DataAccessException(String msg) { + super(msg); + } + + public DataAccessException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/ghast/database/EmptyResultDataAccessException.java b/src/main/java/ghast/database/EmptyResultDataAccessException.java new file mode 100644 index 0000000..0d42ee1 --- /dev/null +++ b/src/main/java/ghast/database/EmptyResultDataAccessException.java @@ -0,0 +1,8 @@ +package ghast.database; + +public class EmptyResultDataAccessException extends IncorrectResultSizeDataAccessException { + + public EmptyResultDataAccessException(int expectedSize) { + super(expectedSize); + } +} diff --git a/src/main/java/ghast/database/IncorrectResultSizeDataAccessException.java b/src/main/java/ghast/database/IncorrectResultSizeDataAccessException.java new file mode 100644 index 0000000..5acd10d --- /dev/null +++ b/src/main/java/ghast/database/IncorrectResultSizeDataAccessException.java @@ -0,0 +1,8 @@ +package ghast.database; + +public class IncorrectResultSizeDataAccessException extends DataAccessException { + + public IncorrectResultSizeDataAccessException(int expectedSize) { + super("Incorrect result size: expected " + expectedSize); + } +} diff --git a/src/main/java/ghast/database/JdbcOperations.java b/src/main/java/ghast/database/JdbcOperations.java new file mode 100644 index 0000000..89d12ca --- /dev/null +++ b/src/main/java/ghast/database/JdbcOperations.java @@ -0,0 +1,23 @@ +package ghast.database; + +import java.util.List; +import java.util.Map; + +public interface JdbcOperations { + + void execute(String sql) throws DataAccessException; + + T query(String sql, ResultSetExtractor rse) throws DataAccessException; + + List query(String sql, RowMapper rowMapper) throws DataAccessException; + + T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException; + + Map queryForMap(String sql) throws DataAccessException; + + List> queryForList(String sql) throws DataAccessException; + + int update(String sql) throws DataAccessException; + + int delete(String sql) throws DataAccessException; +} diff --git a/src/main/java/ghast/database/JdbcTemplate.java b/src/main/java/ghast/database/JdbcTemplate.java new file mode 100644 index 0000000..0a3ab03 --- /dev/null +++ b/src/main/java/ghast/database/JdbcTemplate.java @@ -0,0 +1,238 @@ +package ghast.database; + +import ghast.XLog; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@NoArgsConstructor +@Getter +@Setter +public class JdbcTemplate implements JdbcOperations { + + private DataSource dataSource; + + public JdbcTemplate(DataSource dataSource) { + setDataSource(dataSource); + } + + @Override + public void execute(String sql) throws DataAccessException { + XLog.debug("Execute SQL: {0}", sql); + + Connection connection = openConnection(); + Statement statement = null; + try { + statement = connection.createStatement(); + statement.execute(sql); + } catch (SQLException e) { + XLog.error("Error execute SQL: {0}", e.getMessage(), e); + } finally { + closeStatement(statement); + closeConnection(connection); + } + } + + @Override + public T query(String sql, ResultSetExtractor rse) throws DataAccessException { + XLog.debug("Execute SQL: {0}", sql); + + Connection connection = openConnection(); + Statement statement = null; + ResultSet resultSet = null; + try { + statement = connection.createStatement(); + resultSet = statement.executeQuery(sql); + return rse.extractData(resultSet); + } catch (SQLException e) { + XLog.error("Error execute SQL: {0}", e.getMessage(), e); + return null; + } finally { + closeResultSet(resultSet); + closeStatement(statement); + closeConnection(connection); + } + } + + @Override + public List query(String sql, final RowMapper rowMapper) throws DataAccessException { + return query(sql, rs -> { + List resultList = new ArrayList<>(); + int rowNum = 0; + while (rs.next()) { + resultList.add(rowMapper.mapRow(rs, rowNum++)); + } + return resultList; + }); + } + + @Override + public T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException { + return query(sql, rs -> { + if (rs.next()) { + T resultObj = rowMapper.mapRow(rs, 0); + + if (rs.next()) { + throw new IncorrectResultSizeDataAccessException(1); + } + + return resultObj; + } else { + throw new EmptyResultDataAccessException(1); + } + }); + } + + @Override + public Map queryForMap(String sql) throws DataAccessException { + return queryForObject(sql, (rs, rowNum) -> { + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + Map resultMap = new LinkedHashMap<>(columnCount); + + for (int i = 1; i <= columnCount; i++) { + String key = lookupColumnName(metaData, i); + Object value = getResultSetRawValue(rs, i); + resultMap.put(key, value); + } + + return resultMap; + }); + } + + @Override + public List> queryForList(String sql) throws DataAccessException { + return query(sql, (rs, rowNum) -> { + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + Map resultMap = new LinkedHashMap<>(columnCount); + + for (int i = 1; i <= columnCount; i++) { + String key = lookupColumnName(metaData, i); + Object value = getResultSetRawValue(rs, i); + resultMap.put(key, value); + } + + return resultMap; + }); + } + + @Override + public int update(String sql) throws DataAccessException { + XLog.debug("Execute SQL: {0}", sql); + + Connection connection = openConnection(); + Statement statement = null; + try { + statement = connection.createStatement(); + int rows = statement.executeUpdate(sql); + XLog.debug("Affected {0} rows", rows); + return rows; + } catch (SQLException e) { + XLog.error("Error execute SQL: {0}", e.getMessage(), e); + return 0; + } finally { + closeStatement(statement); + closeConnection(connection); + } + } + + @Override + public int delete(String sql) throws DataAccessException { + return update(sql); + } + + private Connection openConnection() { + try { + return getDataSource().getConnection(); + } + catch (SQLException ex) { + throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex); + } + } + + private String lookupColumnName(ResultSetMetaData resultSetMetaData, int columnIndex) throws SQLException { + String name = resultSetMetaData.getColumnLabel(columnIndex); + if (name == null || name.isEmpty()) { + name = resultSetMetaData.getColumnName(columnIndex); + } + return name; + } + + private Object getResultSetRawValue(ResultSet resultSet, int index) throws SQLException { + Object obj = resultSet.getObject(index); + String className = null; + if (obj != null) { + className = obj.getClass().getName(); + } + + if (obj instanceof Blob) { + Blob blob = (Blob) obj; + obj = blob.getBytes(1, (int) blob.length()); + } else if (obj instanceof Clob) { + Clob clob = (Clob) obj; + obj = clob.getSubString(1, (int) clob.length()); + } else if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) { + obj = resultSet.getTimestamp(index); + } else if (className != null && className.startsWith("oracle.sql.DATE")) { + String metaDataClassName = resultSet.getMetaData().getColumnClassName(index); + if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) { + obj = resultSet.getTimestamp(index); + } + else { + obj = resultSet.getDate(index); + } + } else if (obj instanceof Date) { + if ("java.sql.Timestamp".equals(resultSet.getMetaData().getColumnClassName(index))) { + obj = resultSet.getTimestamp(index); + } + } + + return obj; + } + + private void closeResultSet(ResultSet resultSet) { + if (resultSet != null) { + try { + resultSet.close(); + } catch (SQLException e) { + XLog.debug("Could not close JDBC ResultSet", e); + } catch (Exception e) { + XLog.debug("Unexpected exception on closing JDBC ResultSet", e); + } + } + } + + private void closeStatement(Statement statement) { + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { + XLog.debug("Could not close JDBC Statement", e); + } catch (Exception e) { + XLog.debug("Unexpected exception on closing JDBC Statement", e); + } + } + } + + private void closeConnection(Connection con) { + if (con == null) { + return; + } + + try { + con.close(); + } catch (SQLException e) { + XLog.debug("Could not close JDBC Connection", e); + } catch (Exception e) { + XLog.debug("Unexpected exception on closing JDBC Connection", e); + } + } +} diff --git a/src/main/java/ghast/database/ResultSetExtractor.java b/src/main/java/ghast/database/ResultSetExtractor.java new file mode 100644 index 0000000..d08e1a7 --- /dev/null +++ b/src/main/java/ghast/database/ResultSetExtractor.java @@ -0,0 +1,9 @@ +package ghast.database; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface ResultSetExtractor { + + T extractData(ResultSet rs) throws SQLException, DataAccessException; +} diff --git a/src/main/java/ghast/database/RowMapper.java b/src/main/java/ghast/database/RowMapper.java new file mode 100644 index 0000000..d8cd575 --- /dev/null +++ b/src/main/java/ghast/database/RowMapper.java @@ -0,0 +1,9 @@ +package ghast.database; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface RowMapper { + + T mapRow(ResultSet rs, int rowNum) throws SQLException; +}