results persistance
This commit is contained in:
parent
b35ffac6fa
commit
c7e9824208
10 changed files with 166 additions and 52 deletions
|
@ -1,11 +1,12 @@
|
||||||
import data.dtos.QueryDTO;
|
import data.dtos.QueryDTO;
|
||||||
import data.enums.CustomPreparedStatementsRead;
|
import data.enums.CustomPreparedStatementsRead;
|
||||||
import data.models.QueryResultsModel;
|
import data.repos.QueryResultsRepository;
|
||||||
import services.AppConfigService;
|
import services.AppConfigService;
|
||||||
import services.DatabaseService;
|
import services.DatabaseService;
|
||||||
|
import services.ReportGenerationService;
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
@ -18,13 +19,33 @@ class App {
|
||||||
public App() {
|
public App() {
|
||||||
databaseService = new DatabaseService();
|
databaseService = new DatabaseService();
|
||||||
appConfigService = new AppConfigService();
|
appConfigService = new AppConfigService();
|
||||||
|
reportGenerationService = new ReportGenerationService();
|
||||||
}
|
}
|
||||||
|
|
||||||
final DatabaseService databaseService;
|
final private DatabaseService databaseService;
|
||||||
final AppConfigService appConfigService;
|
final private AppConfigService appConfigService;
|
||||||
|
final private ReportGenerationService reportGenerationService;
|
||||||
|
|
||||||
|
private static final CustomPreparedStatementsRead[] reportEndpoints = {
|
||||||
|
CustomPreparedStatementsRead.HighestAndLowest10Vaccination,
|
||||||
|
CustomPreparedStatementsRead.HighestInfectionsPer100K,
|
||||||
|
//CustomPreparedStatementsRead.CountriesLaggingBehind,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final CustomPreparedStatementsRead[] queryEndpoints = {
|
||||||
|
CustomPreparedStatementsRead.DailyInfectionsAndDeathAggregate,
|
||||||
|
};
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
//appConfigService.promptUserArguments();
|
//appConfigService.promptUserArguments();
|
||||||
final QueryResultsModel resultSetsQuery1 = databaseService.executeReadReportsEndpoint(new QueryDTO(CustomPreparedStatementsRead.HighestAndLowest10Vaccination));
|
try {
|
||||||
|
QueryResultsRepository resultsRepository;
|
||||||
|
for (final CustomPreparedStatementsRead query : reportEndpoints) {
|
||||||
|
resultsRepository = databaseService.executeReadReportsEndpoint(new QueryDTO(query));
|
||||||
|
reportGenerationService.reportResults(resultsRepository);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,42 @@
|
||||||
package apis;
|
package apis;
|
||||||
|
|
||||||
import data.dtos.QueryDTO;
|
import data.dtos.QueryDTO;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.MissingFormatArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Communicates with the database to perform final CRUD operations, returning any results.
|
||||||
|
* When there is a need for more than a single READ action, the Api can be abstracted, and implement into constituent action-specific APIs.
|
||||||
|
*/
|
||||||
public class DatabaseApi {
|
public class DatabaseApi {
|
||||||
public PreparedStatement performReadQuery(QueryDTO queryDTO, Connection connection) throws SQLException {
|
public PreparedStatement performReadQuery(QueryDTO queryDTO, Connection connection) throws SQLException {
|
||||||
// Set template
|
// Set template
|
||||||
PreparedStatement statement = connection.prepareStatement(queryDTO.statement().statementTemplate);
|
PreparedStatement statement = connection.prepareStatement(queryDTO.statement().statementTemplate);
|
||||||
|
|
||||||
// Add in arguments
|
try {
|
||||||
for(int argIndex = 0; argIndex < queryDTO.templateArgs().length; argIndex++)
|
// Add in arguments
|
||||||
// parameter indexing starts at 1
|
for(int argIndex = 0; argIndex < queryDTO.templateArgs().length; argIndex++) {
|
||||||
statement.setString(argIndex + 1, queryDTO.templateArgs()[argIndex].toString());
|
final Object curentArg = queryDTO.templateArgs()[argIndex];
|
||||||
|
|
||||||
|
// parameter indexing starts at 1
|
||||||
|
if (curentArg instanceof Date)
|
||||||
|
statement.setDate(argIndex + 1, new java.sql.Date(((Date) curentArg).getTime()));
|
||||||
|
else
|
||||||
|
statement.setString(argIndex + 1, curentArg.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.execute();
|
||||||
|
} catch (PSQLException e) {
|
||||||
|
if (e.getMessage().startsWith("No value specified for parameter")) throw new MissingFormatArgumentException("The templates present in the query, were not correctly mapped to their replacement variables.");
|
||||||
|
else if (e.getMessage().startsWith("The column index is out of range:")) throw new MissingFormatArgumentException("No replacement variables needed, but were they were still supplied.");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
statement.execute();
|
|
||||||
return statement;
|
return statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,11 @@ package data.dtos;
|
||||||
|
|
||||||
import data.enums.CustomPreparedStatementsRead;
|
import data.enums.CustomPreparedStatementsRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for storing SQL statements to be executed, along with their aruments.
|
||||||
|
* @param statement
|
||||||
|
* @param templateArgs
|
||||||
|
*/
|
||||||
public record QueryDTO(CustomPreparedStatementsRead statement, Object[] templateArgs) {
|
public record QueryDTO(CustomPreparedStatementsRead statement, Object[] templateArgs) {
|
||||||
public QueryDTO(CustomPreparedStatementsRead customPreparedStatementsRead) {
|
public QueryDTO(CustomPreparedStatementsRead customPreparedStatementsRead) {
|
||||||
this(customPreparedStatementsRead, new Object[]{});
|
this(customPreparedStatementsRead, new Object[]{});
|
||||||
|
|
|
@ -7,15 +7,17 @@ package data.enums;
|
||||||
public enum CustomPreparedStatementsRead {
|
public enum CustomPreparedStatementsRead {
|
||||||
DailyInfectionsAndDeathAggregate("""
|
DailyInfectionsAndDeathAggregate("""
|
||||||
WITH matched_countries AS (
|
WITH matched_countries AS (
|
||||||
SELECT c.name, c.code\s
|
SELECT c.name, c.code
|
||||||
FROM countries c
|
FROM countries c
|
||||||
WHERE c.name LIKE CONCAT('%',?, '%')
|
WHERE c.name LIKE CONCAT('%',?, '%')
|
||||||
)
|
)
|
||||||
SELECT recorded_date, infections, deaths
|
SELECT recorded_date, infections, deaths
|
||||||
FROM cases
|
FROM cases
|
||||||
WHERE iso_country IN (SELECT code FROM matched_countries) AND recorded_date <= ?
|
WHERE iso_country IN (SELECT code FROM matched_countries) AND recorded_date <= ?
|
||||||
ORDER BY recorded_date;
|
ORDER BY recorded_date
|
||||||
"""
|
;
|
||||||
|
""",
|
||||||
|
"10 countries with the highest vaccinations (with count), and countries with lowest number of vaccinations."
|
||||||
),
|
),
|
||||||
|
|
||||||
HighestAndLowest10Vaccination("""
|
HighestAndLowest10Vaccination("""
|
||||||
|
@ -42,7 +44,8 @@ public enum CustomPreparedStatementsRead {
|
||||||
ORDER BY total_vaccinations ASC
|
ORDER BY total_vaccinations ASC
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
;
|
;
|
||||||
"""
|
""",
|
||||||
|
"10 countries with the highest infections per 100k inhabitants (with count)"
|
||||||
),
|
),
|
||||||
|
|
||||||
HighestInfectionsPer100K("""
|
HighestInfectionsPer100K("""
|
||||||
|
@ -53,7 +56,8 @@ public enum CustomPreparedStatementsRead {
|
||||||
ORDER BY infections_per_100k DESC
|
ORDER BY infections_per_100k DESC
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
;
|
;
|
||||||
"""
|
""",
|
||||||
|
""
|
||||||
),
|
),
|
||||||
|
|
||||||
CountriesLaggingBehind("""
|
CountriesLaggingBehind("""
|
||||||
|
@ -63,12 +67,15 @@ public enum CustomPreparedStatementsRead {
|
||||||
GROUP BY c.name
|
GROUP BY c.name
|
||||||
HAVING SUM(v.daily_vaccinations) < c.population
|
HAVING SUM(v.daily_vaccinations) < c.population
|
||||||
;
|
;
|
||||||
"""
|
""",
|
||||||
|
""
|
||||||
);
|
);
|
||||||
|
|
||||||
public final String statementTemplate;
|
public final String statementTemplate;
|
||||||
|
public final String description;
|
||||||
|
|
||||||
private CustomPreparedStatementsRead(String statementTemplate) {
|
private CustomPreparedStatementsRead(String statementTemplate, String description) {
|
||||||
this.statementTemplate = statementTemplate;
|
this.statementTemplate = statementTemplate;
|
||||||
|
this.description = description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
src/data/models/PersistentResultModel.java
Normal file
41
src/data/models/PersistentResultModel.java
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package data.models;
|
||||||
|
|
||||||
|
import java.sql.Array;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class PersistentResultModel {
|
||||||
|
/**
|
||||||
|
* Factory constructor, that adapts a given ResultSet to PersistentResultModel.
|
||||||
|
* @param resultSet
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
public PersistentResultModel (ResultSet resultSet) throws SQLException {
|
||||||
|
final ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
|
||||||
|
final int columnCount = resultSetMetaData.getColumnCount();
|
||||||
|
|
||||||
|
final ArrayList<Object[]> grwingResultsGrid = new ArrayList<>();
|
||||||
|
while (resultSet.next()) {
|
||||||
|
final Object[] currentRow = new Object[columnCount];
|
||||||
|
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
|
||||||
|
currentRow[columnIndex] = resultSet.getObject(columnIndex+1);
|
||||||
|
}
|
||||||
|
grwingResultsGrid.add(currentRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
resultColumns = new ResultColumnModel[columnCount];
|
||||||
|
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
|
||||||
|
int finalColumnIndex = columnIndex;
|
||||||
|
final var entries = grwingResultsGrid.stream().map(row -> row[finalColumnIndex]).toList().toArray();
|
||||||
|
resultColumns[columnIndex] = new ResultColumnModel(resultSetMetaData.getColumnName(columnIndex+1), entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
final public ResultColumnModel[] resultColumns;
|
||||||
|
public record ResultColumnModel(String columnName, Object[] entries){}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
package data.models;
|
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the results obtained from executing an SQL query.
|
|
||||||
* Essentially a type-def, which is not natively supported in Java.
|
|
||||||
* @param resultSets Result of the query
|
|
||||||
*/
|
|
||||||
public record QueryResultsModel(java.util.ArrayList<ResultSet> resultSets) {
|
|
||||||
}
|
|
28
src/data/repos/QueryResultsRepository.java
Normal file
28
src/data/repos/QueryResultsRepository.java
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package data.repos;
|
||||||
|
|
||||||
|
|
||||||
|
import data.models.PersistentResultModel;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the results obtained from executing an SQL query.
|
||||||
|
*/
|
||||||
|
public class QueryResultsRepository {
|
||||||
|
/**
|
||||||
|
* Store for the results
|
||||||
|
*/
|
||||||
|
final private ArrayList<PersistentResultModel> persistentResults = new ArrayList<>();
|
||||||
|
public PersistentResultModel[] getResults () {return persistentResults.toArray(new PersistentResultModel[]{});}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a ResultSet to the store to persist.
|
||||||
|
* @param resultSet
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
public void addResult(ResultSet resultSet) throws SQLException {
|
||||||
|
persistentResults.add(new PersistentResultModel(resultSet));
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,8 @@ import java.util.Scanner;
|
||||||
* Prompts and stores the configuration that the app needs to be run with.
|
* Prompts and stores the configuration that the app needs to be run with.
|
||||||
*/
|
*/
|
||||||
public class AppConfigService {
|
public class AppConfigService {
|
||||||
String countryName;
|
public String countryName;
|
||||||
Date date;
|
public Date date;
|
||||||
|
|
||||||
public void promptUserArguments() {
|
public void promptUserArguments() {
|
||||||
final Scanner scanner = new Scanner(System.in);
|
final Scanner scanner = new Scanner(System.in);
|
||||||
|
|
|
@ -2,14 +2,12 @@ package services;
|
||||||
|
|
||||||
import apis.DatabaseApi;
|
import apis.DatabaseApi;
|
||||||
import data.dtos.QueryDTO;
|
import data.dtos.QueryDTO;
|
||||||
import data.enums.CustomPreparedStatementsRead;
|
import data.repos.QueryResultsRepository;
|
||||||
import data.models.QueryResultsModel;
|
|
||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the active connection between the app and the local SQL Server.
|
* Stores the active connection between the app and the SQL Server.
|
||||||
*/
|
*/
|
||||||
public class DatabaseService {
|
public class DatabaseService {
|
||||||
private static final String url = "jdbc:postgresql://localhost:5432/lunatech_covid";
|
private static final String url = "jdbc:postgresql://localhost:5432/lunatech_covid";
|
||||||
|
@ -20,25 +18,27 @@ public class DatabaseService {
|
||||||
private final Connection connection;
|
private final Connection connection;
|
||||||
|
|
||||||
public DatabaseService() {
|
public DatabaseService() {
|
||||||
connection = connect();
|
connection = getConnection();
|
||||||
databaseApi = new DatabaseApi();
|
databaseApi = new DatabaseApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResultsModel executeReadReportsEndpoint(QueryDTO queryDTO) {
|
/**
|
||||||
try {
|
* Fetches read-query results and packages them to persist them (and so free the connection).
|
||||||
final PreparedStatement resultStatement = databaseApi.performReadQuery(queryDTO, connection);
|
*
|
||||||
|
* @param queryDTO The query with its arguments.
|
||||||
|
* @return Persisting (non-lazy) results from executing the query.
|
||||||
|
*/
|
||||||
|
public QueryResultsRepository executeReadReportsEndpoint(QueryDTO queryDTO) throws SQLException {
|
||||||
|
final PreparedStatement resultStatement = databaseApi.performReadQuery(queryDTO, connection);
|
||||||
|
|
||||||
final ArrayList<ResultSet> resultSets = new ArrayList<>();
|
final QueryResultsRepository resultSets = new QueryResultsRepository();
|
||||||
do {
|
do {
|
||||||
// execute and persist results in a model
|
final ResultSet resultSet = resultStatement.getResultSet();
|
||||||
resultSets.add(resultStatement.getResultSet());
|
resultSets.addResult(resultSet);
|
||||||
}
|
|
||||||
while (resultStatement.getMoreResults());
|
|
||||||
|
|
||||||
return new QueryResultsModel(resultSets);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
while (resultStatement.getMoreResults());
|
||||||
|
|
||||||
|
return resultSets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,13 +46,14 @@ public class DatabaseService {
|
||||||
*
|
*
|
||||||
* @return a Connection object
|
* @return a Connection object
|
||||||
*/
|
*/
|
||||||
private Connection connect() {
|
private Connection getConnection() {
|
||||||
Connection connection = null;
|
Connection connection = null;
|
||||||
try {
|
try {
|
||||||
connection = DriverManager.getConnection(url, user, password);
|
connection = DriverManager.getConnection(url, user, password);
|
||||||
System.out.println("Connected to the PostgreSQL server successfully.");
|
System.out.println("Connected to the PostgreSQL server successfully.");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
System.out.println(e.getMessage());
|
System.out.println("Unable to connect to PostgreSQL server.");
|
||||||
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package services;
|
package services;
|
||||||
|
|
||||||
import data.models.QueryResultsModel;
|
import data.repos.QueryResultsRepository;
|
||||||
|
|
||||||
public class ReportGenerationService {
|
public class ReportGenerationService {
|
||||||
public static void printResults(QueryResultsModel queryResultsModel) {
|
public void reportResults(QueryResultsRepository queryResultsRepository) {
|
||||||
//queryResultsModel.
|
//queryResultsModel.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue