commit 429c8ba24386e36881fa267189a3a655a4c934c8 Author: Sambo Chea Date: Tue Jul 5 21:21:27 2022 +0700 Task: Add sql core features with based features and support query and custom parameters for general used with module diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7814f18 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# CUBETIQ DSM Core + +- DataSource +- Core Context for DSM + +# Contributors + +- Sambo Chea \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..343419c --- /dev/null +++ b/build.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' +} + +group 'com.cubetiqs' +version = '1.0.0' + +sourceCompatibility = JavaVersion.VERSION_1_8 + +dependencies {} \ No newline at end of file diff --git a/src/main/java/com/cubetiqs/mapper/MapperProvider.java b/src/main/java/com/cubetiqs/mapper/MapperProvider.java new file mode 100644 index 0000000..cfd1b6d --- /dev/null +++ b/src/main/java/com/cubetiqs/mapper/MapperProvider.java @@ -0,0 +1,6 @@ +package com.cubetiqs.mapper; + +@FunctionalInterface +public interface MapperProvider { + R transformTo(E element, Class clazz); +} diff --git a/src/main/java/com/cubetiqs/mapper/RowMapperProvider.java b/src/main/java/com/cubetiqs/mapper/RowMapperProvider.java new file mode 100644 index 0000000..9dd7de3 --- /dev/null +++ b/src/main/java/com/cubetiqs/mapper/RowMapperProvider.java @@ -0,0 +1,9 @@ +package com.cubetiqs.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@FunctionalInterface +public interface RowMapperProvider { + T map(ResultSet rs, int rowNum) throws SQLException; +} diff --git a/src/main/java/com/cubetiqs/sql/DataFactory.java b/src/main/java/com/cubetiqs/sql/DataFactory.java new file mode 100644 index 0000000..3bad26d --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/DataFactory.java @@ -0,0 +1,44 @@ +package com.cubetiqs.sql; + +import com.cubetiqs.mapper.MapperProvider; + +import java.util.List; +import java.util.Map; + +public final class DataFactory { + public static IExecuteResult> queryForList(JdbcDataQuery manager, String sql) { + return manager.queryForList(sql); + } + + public static IExecuteResult> queryForList(JdbcDataQuery manager, String sql, Object... args) { + return manager.queryForList(sql, args); + } + + public static IExecuteResult queryForObject(JdbcDataQuery manager, String sql) { + return manager.queryForObject(sql); + } + + public static IExecuteResult queryForObject(JdbcDataQuery manager, String sql, Object... args) { + return manager.queryForObject(sql, args); + } + + public static IExecuteResult query(JdbcDataQuery manager, MapperProvider, R> mapper, String sql, Class clazz) { + return manager.query(mapper, sql, clazz); + } + + public static IExecuteResult query(JdbcDataQuery manager, MapperProvider, R> mapper, String sql, Class clazz, Object... args) { + return manager.query(mapper, sql, clazz, args); + } + + public static IExecuteResult> queryForList(JdbcDataQuery manager, ISqlMapParameter parameter) { + return manager.queryForList(parameter); + } + + public static IExecuteResult queryForObject(JdbcDataQuery manager, ISqlMapParameter parameter) { + return manager.queryForObject(parameter); + } + + public static IExecuteResult query(JdbcDataQuery manager, MapperProvider, R> mapper, ISqlMapParameter parameter, Class clazz) { + return manager.query(mapper, parameter, clazz); + } +} diff --git a/src/main/java/com/cubetiqs/sql/ExecuteResult.java b/src/main/java/com/cubetiqs/sql/ExecuteResult.java new file mode 100644 index 0000000..da63286 --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/ExecuteResult.java @@ -0,0 +1,68 @@ +package com.cubetiqs.sql; + +import java.util.Date; + +public class ExecuteResult implements IExecuteResult { + private R data; + private Throwable error; + + public ExecuteResult() { + } + + public ExecuteResult(R data, Throwable error) { + this.data = data; + this.error = error; + } + + private final Date start = new Date(); + + public void setData(R data) { + this.data = data; + } + + public void setError(Throwable error) { + this.error = error; + } + + @Override + public R getData() { + return data; + } + + @Override + public Throwable getError() { + return error; + } + + @Override + public long getDuration() { + return new Date().getTime() - start.getTime(); + } + + public static ExecuteResult createEmpty() { + return builder().build(); + } + + public static class ExecuteResultBuilder { + private R data; + private Throwable error; + + public ExecuteResultBuilder data(R data) { + this.data = data; + return this; + } + + public ExecuteResultBuilder error(Throwable error) { + this.error = error; + return this; + } + + public ExecuteResult build() { + return new ExecuteResult<>(data, error); + } + } + + public static ExecuteResultBuilder builder() { + return new ExecuteResultBuilder<>(); + } +} diff --git a/src/main/java/com/cubetiqs/sql/IDataManager.java b/src/main/java/com/cubetiqs/sql/IDataManager.java new file mode 100644 index 0000000..248edac --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/IDataManager.java @@ -0,0 +1,7 @@ +package com.cubetiqs.sql; + +import java.sql.Connection; + +public interface IDataManager { + Connection getConnection(); +} diff --git a/src/main/java/com/cubetiqs/sql/IExecuteResult.java b/src/main/java/com/cubetiqs/sql/IExecuteResult.java new file mode 100644 index 0000000..f631a77 --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/IExecuteResult.java @@ -0,0 +1,13 @@ +package com.cubetiqs.sql; + +public interface IExecuteResult { + R getData(); + + default Throwable getError() { + return null; + } + + default long getDuration() { + return 0L; + } +} diff --git a/src/main/java/com/cubetiqs/sql/ISqlMapParameter.java b/src/main/java/com/cubetiqs/sql/ISqlMapParameter.java new file mode 100644 index 0000000..715b48d --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/ISqlMapParameter.java @@ -0,0 +1,19 @@ +package com.cubetiqs.sql; + +import java.util.Map; + +public interface ISqlMapParameter { + String getSql(); + + default String getFormatSql() { + return getSql(); + } + + default Map getParams() { + return null; + } + + default Object[] getSqlArgs() { + return null; + } +} diff --git a/src/main/java/com/cubetiqs/sql/JdbcAccessor.java b/src/main/java/com/cubetiqs/sql/JdbcAccessor.java new file mode 100644 index 0000000..bbb1f25 --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/JdbcAccessor.java @@ -0,0 +1,5 @@ +package com.cubetiqs.sql; + +public interface JdbcAccessor { + IDataManager getManager(); +} diff --git a/src/main/java/com/cubetiqs/sql/JdbcDataQuery.java b/src/main/java/com/cubetiqs/sql/JdbcDataQuery.java new file mode 100644 index 0000000..389dcfa --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/JdbcDataQuery.java @@ -0,0 +1,61 @@ +package com.cubetiqs.sql; + +import com.cubetiqs.mapper.MapperProvider; +import com.cubetiqs.mapper.RowMapperProvider; + +import java.util.List; +import java.util.Map; + +public interface JdbcDataQuery extends JdbcAccessor { + IExecuteResult> queryForList(String sql); + + IExecuteResult> queryForList(String sql, Object... args); + + IExecuteResult queryForObject(String sql); + + IExecuteResult queryForObject(String sql, Object... args); + + IExecuteResult query(MapperProvider, R> mapper, String sql, Class clazz); + + IExecuteResult query(MapperProvider, R> mapper, String sql, Class clazz, Object... args); + + IExecuteResult query(String sql, RowMapperProvider mapper); + + IExecuteResult query(String sql, RowMapperProvider mapper, Object... args); + + default IExecuteResult> queryForList(ISqlMapParameter parameter) { + if (parameter.getParams().size() == 0) { + return queryForList(parameter.getSql()); + } + + Object[] args = parameter.getSqlArgs(); + return queryForList(parameter.getFormatSql(), args); + } + + default IExecuteResult queryForObject(ISqlMapParameter parameter) { + if (parameter.getParams().size() == 0) { + return queryForObject(parameter.getSql()); + } + + Object[] args = parameter.getSqlArgs(); + return queryForObject(parameter.getFormatSql(), args); + } + + default IExecuteResult query(MapperProvider, R> mapper, ISqlMapParameter parameter, Class clazz) { + if (parameter.getParams().size() == 0) { + return query(mapper, parameter.getSql(), clazz); + } + + Object[] args = parameter.getSqlArgs(); + return query(mapper, parameter.getFormatSql(), clazz, args); + } + + default IExecuteResult query(ISqlMapParameter parameter, RowMapperProvider mapper) { + if (parameter.getParams().size() == 0) { + return query(parameter.getSql(), mapper); + } + + Object[] args = parameter.getSqlArgs(); + return query(parameter.getFormatSql(), mapper, args); + } +} diff --git a/src/main/java/com/cubetiqs/sql/JdbcDataQueryImpl.java b/src/main/java/com/cubetiqs/sql/JdbcDataQueryImpl.java new file mode 100644 index 0000000..b9c4fbf --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/JdbcDataQueryImpl.java @@ -0,0 +1,115 @@ +package com.cubetiqs.sql; + +import com.cubetiqs.mapper.MapperProvider; +import com.cubetiqs.mapper.RowMapperProvider; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; +import java.util.Map; + +public class JdbcDataQueryImpl implements JdbcDataQuery { + private final IDataManager manager; + + public JdbcDataQueryImpl(IDataManager manager) { + this.manager = manager; + } + + @Override + public IDataManager getManager() { + return manager; + } + + @Override + public IExecuteResult> queryForList(String sql) { + ExecuteResult.ExecuteResultBuilder> builder = ExecuteResult.builder(); + + try (Statement statement = manager.getConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + builder.data(ResultSetUtil.getDataAsList(statement.executeQuery(sql))); + } catch (SQLException ex) { + builder.error(ex); + } + + return builder.build(); + } + + @Override + public IExecuteResult> queryForList(String sql, Object... args) { + ExecuteResult.ExecuteResultBuilder> builder = ExecuteResult.builder(); + + try (PreparedStatement statement = manager.getConnection().prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + ResultSetUtil.applyParameterized(statement, sql, args); + builder.data(ResultSetUtil.getDataAsList(statement.executeQuery())); + } catch (SQLException ex) { + builder.error(ex); + } + + return builder.build(); + } + + @Override + public IExecuteResult queryForObject(String sql) { + ExecuteResult.ExecuteResultBuilder builder = ExecuteResult.builder(); + + try (Statement statement = manager.getConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + builder.data(ResultSetUtil.getDataAsObject(statement.executeQuery(sql))); + } catch (SQLException ex) { + builder.error(ex); + } + + return builder.build(); + } + + @Override + public IExecuteResult queryForObject(String sql, Object... args) { + ExecuteResult.ExecuteResultBuilder builder = ExecuteResult.builder(); + + try (PreparedStatement statement = manager.getConnection().prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + ResultSetUtil.applyParameterized(statement, sql, args); + builder.data(ResultSetUtil.getDataAsObject(statement.executeQuery())); + } catch (SQLException ex) { + builder.error(ex); + } + + return builder.build(); + } + + @Override + public IExecuteResult query(MapperProvider, R> mapper, String sql, Class clazz) { + ExecuteResult.ExecuteResultBuilder builder = ExecuteResult.builder(); + + try (Statement statement = manager.getConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + builder.data(ResultSetUtil.getDataAsType(mapper, statement.executeQuery(sql), clazz)); + } catch (SQLException ex) { + builder.error(ex); + } + + return builder.build(); + } + + @Override + public IExecuteResult query(MapperProvider, R> mapper, String sql, Class clazz, Object... args) { + ExecuteResult.ExecuteResultBuilder builder = ExecuteResult.builder(); + + try (PreparedStatement statement = manager.getConnection().prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + ResultSetUtil.applyParameterized(statement, sql, args); + builder.data(ResultSetUtil.getDataAsType(mapper, statement.executeQuery(), clazz)); + } catch (SQLException ex) { + builder.error(ex); + } + + return builder.build(); + } + + @Override + public IExecuteResult query(String sql, RowMapperProvider mapper) { + return null; + } + + @Override + public IExecuteResult query(String sql, RowMapperProvider mapper, Object... args) { + return null; + } +} diff --git a/src/main/java/com/cubetiqs/sql/ResultSetUtil.java b/src/main/java/com/cubetiqs/sql/ResultSetUtil.java new file mode 100644 index 0000000..dbc28df --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/ResultSetUtil.java @@ -0,0 +1,150 @@ +package com.cubetiqs.sql; + +import com.cubetiqs.mapper.MapperProvider; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +import static java.util.regex.Pattern.compile; + +public final class ResultSetUtil { + public static void applyParameterized(PreparedStatement statement, String sql, Object... args) throws SQLException { + if (args != null && sql.indexOf('?') != -1) { + for (int index = 0; index < args.length; index++) { + statement.setObject(index + 1, args[index]); + } + } + } + + public static int getColumnCount(ResultSet rs) throws SQLException { + return rs.getMetaData().getColumnCount(); + } + + public static List getColumns(ResultSet rs) throws SQLException { + List columns = new ArrayList<>(); + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + IntStream.range(0, columnCount).forEach(i -> { + try { + columns.add(metaData.getColumnName(i + 1)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + + return columns; + } + + public static List getDataAsList(ResultSet rs) throws SQLException { + List rows = new ArrayList<>(); + List columns = ResultSetUtil.getColumns(rs); + + while (rs.next()) { + Map row = new HashMap<>(); + for (String column : columns) { + row.put(column, rs.getObject(column)); + } + rows.add(row); + } + + return rows; + } + + public static Object getDataAsObject(ResultSet rs) throws SQLException { + if (!rs.next()) { + return null; + } + + List columns = getColumns(rs); + if (columns.size() == 0) { + return null; + } + + + if (columns.size() == 1) { + return rs.getObject(columns.get(0)); + } else { + Map row = new HashMap<>(); + for (String column : columns) { + row.put(column, rs.getObject(column)); + } + return row; + } + } + + @SuppressWarnings("unchecked") + public static R getDataAsType(MapperProvider, R> mapper, ResultSet rs, Class clazz) throws SQLException { + if (!rs.next()) { + return null; + } + + List columns = getColumns(rs); + if (columns.size() == 0) { + return null; + } + + if (columns.size() == 1) { + String column = columns.get(0); + if (clazz.isInstance(String.class)) { + return (R) rs.getString(column); + } else if (clazz.isInstance(Integer.class)) { + return (R) Integer.valueOf(rs.getInt(column)); + } else if (clazz.isInstance(Boolean.class)) { + return (R) Boolean.valueOf(rs.getBoolean(column)); + } else if (clazz.isInstance(Date.class)) { + return (R) rs.getDate(column); + } else if (clazz.isInstance(Double.class)) { + return (R) Double.valueOf(rs.getDouble(column)); + } else { + return (R) rs.getObject(column); + } + } else { + Map row = new HashMap<>(); + + for (String column : columns) { + row.put(column, rs.getObject(column)); + } + + return mapper.transformTo(row, clazz); + } + } + + private static CharSequence escapeRegex(CharSequence text) { + if (text == null) return null; + return Pattern.quote(text + ""); + } + + public static List findValues(String template, CharSequence sequence1, CharSequence sequence2) { + List data = new ArrayList<>(); + Pattern pattern; + if (sequence1 != null) { + if (sequence2 != null) { + pattern = compile(escapeRegex(sequence1) + "(.*?)" + escapeRegex(sequence2)); + } else { + pattern = compile(escapeRegex(sequence1) + "([A-Za-z\\d]*)"); + } + } else { + return null; + } + Matcher matcher = pattern.matcher(template); + while (matcher.find()) { + data.add(matcher.group(1)); + } + + return data; + } + + public static String replacerPrefix(String template, Map replacement, CharSequence prefix) { + AtomicReference message = new AtomicReference<>(template); + replacement.forEach((key, value) -> message.set(message.get().replace(prefix + key, value.toString()))); + return message.get(); + } +} diff --git a/src/main/java/com/cubetiqs/sql/SqlMapParameter.java b/src/main/java/com/cubetiqs/sql/SqlMapParameter.java new file mode 100644 index 0000000..d6b0a63 --- /dev/null +++ b/src/main/java/com/cubetiqs/sql/SqlMapParameter.java @@ -0,0 +1,78 @@ +package com.cubetiqs.sql; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SqlMapParameter implements ISqlMapParameter { + private final String sql; + private final Map params = new HashMap<>(); + + // Replace all ":variableName" with "?" + private String formatSql; + + public SqlMapParameter(String sql) { + this.sql = sql; + } + + public SqlMapParameter addParam(String key, Object value) { + params.put(key, value); + return this; + } + + public SqlMapParameter addParams(Map values) { + params.putAll(values); + return this; + } + + @Override + public Map getParams() { + return params; + } + + @Override + public String getSql() { + return sql; + } + + @Override + public String getFormatSql() { + return formatSql; + } + + @Override + public Object[] getSqlArgs() { + if (params.size() == 0) { + return null; + } + + if (sql == null || sql.equals("")) { + return null; + } + + List keys = ResultSetUtil.findValues(sql, ":", null); + if (keys.size() > 1 && params.size() == 0) { + System.out.println("Keys = " + keys); + System.out.println("Params = " + params); + throw new IllegalArgumentException("Parameter not matched with keys size!"); + } + + Object[] args = new Object[keys.size()]; + + for (int i = 0; i < args.length; i++) { + args[i] = params.get(keys.get(i)); + } + + Map replacement = new HashMap<>(); + for (String key : keys) { + replacement.put(key, "?"); + } + formatSql = ResultSetUtil.replacerPrefix(sql, replacement, ":"); + + return args; + } + + public static SqlMapParameter create(String sql) { + return new SqlMapParameter(sql); + } +}