The de.juniorjacki.SQL.Interface
package provides a robust, type-safe framework for interacting with relational databases in Java. It combines the DatabaseInterface
for basic CRUD operations and the QueryBuilder
for constructing complex SQL queries with support for conditions, joins, grouping, ordering, and limits. The framework leverages Java's record types and generics to ensure type safety and streamline database interactions.
This package offers two main interfaces:
DatabaseInterface
: Provides methods for common database operations such as retrieving, updating, deleting, and checking the existence of records, with built-in SQL injection protection and type validation.QueryBuilder
: Enables the construction of complex SQL queries with a fluent API, supporting single-column, multi-column, and full-row queries, as well as table joins with type-safe bindings.
- Type Safety: Uses Java generics and enums to enforce type compatibility for columns and records.
- Fluent API: Method chaining for intuitive query construction.
- Flexible Queries: Supports single-column (
ColumnQuery
), multi-column (ColumnsQuery
), full-row (RowQuery
), and join queries (BindingRowQuery
,BindingColumnsQuery
). - Join Support: Type-safe table joins using the
Binding
class to ensure column compatibility. - Condition Building:
ConditionQueryBuilder
for constructing complex WHERE clauses with AND/OR conditions. - CRUD Operations: Methods for counting, retrieving, updating, and deleting records with type validation and SQL injection filtering.
- Custom Data Types: Extensible
DatabaseType
enum for adding new data types with custom conversion logic.
This package is ideal for applications requiring dynamic SQL query generation and robust database operations with minimal boilerplate code.
To use this package, include it in your Java project. Ensure the following dependencies are available:
- Java 17 or later (due to the use of records).
- JDBC-compatible database driver (e.g., MySQL, PostgreSQL).
- Project dependencies:
de.juniorjacki.SQL
,de.juniorjacki.SQL.Structure
,de.juniorjacki.SQL.Type
,de.juniorjacki.SQL.Base
.
Clone the repository or include it as a dependency in your build tool (e.g., Maven or Gradle).
To interact with a database table, create a class extending Table
and implement DatabaseInterface
and QueryBuilder
. Define columns using an enum that implements DatabaseProperty
.
package de.juniorjacki.SQL.Structure.DataTable;
import de.juniorjacki.SQL.Interface.DatabaseInterface;
import de.juniorjacki.SQL.Interface.QueryBuilder;
import de.juniorjacki.SQL.Structure.DatabaseProperty;
import de.juniorjacki.SQL.Structure.Table;
import de.juniorjacki.SQL.Type.DatabaseRecord;
import de.juniorjacki.SQL.Type.DatabaseType;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class LicenseTable extends Table<LicenseTable.Property, LicenseTable.License>
implements DatabaseInterface<LicenseTable, LicenseTable.License, LicenseTable.Property>,
QueryBuilder<LicenseTable, LicenseTable.License, LicenseTable.Property> {
@Override
public Class<LicenseTable.License> getTableRecord() {
return LicenseTable.License.class;
}
@Override
public List<LicenseTable.Property> getProperties() {
return Arrays.asList(LicenseTable.Property.values());
}
@Override
public LicenseTable getInstance() {
return this;
}
public record License(UUID uID, String value, Long creationTimestamp)
implements DatabaseRecord<LicenseTable.License, LicenseTable.Property> {
@Override
public LicenseTable.License getInstance() {
return this;
}
}
public enum Property implements DatabaseProperty {
uID(true, DatabaseType.UUID), // Primary key, UUID -> BINARY(16)
value(false, DatabaseType.STRING), // String -> VARCHAR(255)
creationTimestamp(false, DatabaseType.LONG); // Long -> BIGINT
private final boolean key;
private final boolean unique;
private final DatabaseType type;
Property(boolean key, DatabaseType type) {
this(key, false, type);
}
Property(boolean key, boolean unique, DatabaseType type) {
this.key = key;
this.unique = unique;
this.type = type;
}
@Override
public boolean isKey() {
return key;
}
@Override
public boolean isUnique() {
return unique;
}
@Override
public DatabaseType getType() {
return type;
}
@Override
public int extendLength() {
return 0;
}
}
public static LicenseTable Instance = new LicenseTable();
// Custom method example
public Optional<LicenseTable.License> getLatestRecord() {
return getByOrder(LicenseTable.Property.uID, DatabaseInterface.Order.DESCENDING);
}
}
- Extend
Table
:- Create a class extending
Table<E, R>
, whereE
is an enum implementingDatabaseProperty
andR
is a record implementingDatabaseRecord
.
- Create a class extending
- Implement Interfaces:
- Implement
DatabaseInterface
andQueryBuilder
to enable CRUD operations and query building.
- Implement
- Define Columns:
- Create an enum implementing
DatabaseProperty
with columns, specifying theirDatabaseType
, key, and unique constraints.
- Create an enum implementing
- Define Record:
- Create a record implementing
DatabaseRecord
to represent table rows.
- Create a record implementing
- Provide Singleton Instance:
- Create a static instance (e.g.,
Instance
) for easy access.
- Create a static instance (e.g.,
- Override Methods:
- Implement
getTableRecord
,getProperties
, andgetInstance
to define the table's metadata.
- Implement
- Add Table to the Tables Enum:
- To Automatically create and maintain the Table, you need to add an Instance to the
Table Enum
.
- To Automatically create and maintain the Table, you need to add an Instance to the
Use DatabaseInterface
methods to perform database operations:
LicenseTable table = LicenseTable.Instance;
// Check if a record exists
boolean exists = table.existsByKey(LicenseTable.Property.uID, UUID.randomUUID());
// Count records
int count = table.countByValue(LicenseTable.Property.value, "license123");
// Retrieve a single record
Optional<LicenseTable.License> license = table.getFirstByKey(LicenseTable.Property.uID, UUID.randomUUID());
// Update a record
boolean updated = table.update(LicenseTable.Property.uID, UUID.randomUUID(),
LicenseTable.Property.value, "newLicense");
// Upsert a record
LicenseTable.License record = new LicenseTable.License(UUID.randomUUID(), "license123", System.currentTimeMillis());
boolean upserted = table.upsert(record);
// Delete a record
boolean deleted = table.deleteByKeys(
new DatabaseInterface.ColumnValue<>(LicenseTable.Property.uID, UUID.randomUUID())
);
Use QueryBuilder
for complex queries:
// Single-column query
ColumnQuery<LicenseTable, LicenseTable.License, LicenseTable.Property> query =
table.newColumnQuery(LicenseTable.Property.value);
Optional<List<Object>> values = query.execute();
// Multi-column query
ColumnsQuery<LicenseTable, LicenseTable.License, LicenseTable.Property> columnsQuery =
table.newColumnsQuery(LicenseTable.Property.uID, LicenseTable.Property.value);
Optional<List<Map<LicenseTable.Property, Object>>> results = columnsQuery.execute();
// Row query with condition
ConditionQueryBuilder<LicenseTable.Property> condition =
new ConditionQueryBuilder<>(new Condition<>(LicenseTable.Property.value,
QueryBuilder.CompareOperator.EQUALS, "license123"));
RowQuery<LicenseTable, LicenseTable.License, LicenseTable.Property> rowQuery =
table.newRowQuery().setCondition(condition);
Optional<List<LicenseTable.License>> licenses = rowQuery.execute();
Join tables using type-safe bindings:
// Define another table
class UserTable extends Table<UserTable.Property, UserTable.User>
implements DatabaseInterface<UserTable, UserTable.User, UserTable.Property>,
QueryBuilder<UserTable, UserTable.User, UserTable.Property> {
// Similar structure to LicenseTable
public enum Property implements DatabaseProperty {
USER_ID(UUID.class, true),
NAME(String.class, false);
// Implementation
}
public record User(UUID userId, String name) implements DatabaseRecord<User, Property> {
// Implementation
}
// Other required methods
}
// Join LicenseTable with UserTable
Binding<LicenseTable, LicenseTable.License, LicenseTable.Property, UserTable, UserTable.User, UserTable.Property> binding =
new Binding<>(LicenseTable.Property.uID, UserTable.Property.USER_ID);
BindingRowQuery<LicenseTable, LicenseTable.License, LicenseTable.Property, UserTable, UserTable.User, UserTable.Property> joinQuery =
table.newRowQuery().join(UserTable.INSTANCE, binding);
Optional<HashMap<LicenseTable.License, UserTable.User>> joinedResults = joinQuery.execute();
The DatabaseType
enum in de.juniorjacki.SQL.Type
defines supported database types and their conversion logic. To add a new data type (e.g., FLOAT
):
-
Add to
DatabaseType
Enum:-
Define a new enum value with the primary type, alias types, and conversion logic.
-
Example for
FLOAT
:FLOAT(Float.class, List.of(float.class), (sb, value) -> sb.append((Float) value), ResultSet::getFloat, (ps, idx, val) -> ps.setFloat(idx, (Float) val), (extendedLength) -> "FLOAT")
-
-
Parameters:
- Primary Type: The main Java class (e.g.,
Float.class
). - Alias Types: Additional types (e.g.,
float.class
) usingList.of
. - Append Converter: How to append the value to a
StringBuilder
for SQL queries. - Result Set Converter: How to retrieve the value from a
ResultSet
usingTriFunction
. - Parameter Setter: How to set the value in a
PreparedStatement
usingTriConsumer
. - SQL Type Mapper: Maps the type to an SQL type (e.g.,
"FLOAT"
).
- Primary Type: The main Java class (e.g.,
-
Use in Table:
-
Reference the new type in your table's
DatabaseProperty
enum:public enum Property implements DatabaseProperty { TEMPERATURE(false, DatabaseType.FLOAT); // Other fields and methods }
-
FLOAT(Float.class,
List.of(float.class),
(sb, value) -> sb.append((Float) value),
ResultSet::getFloat,
(ps, idx, val) -> ps.setFloat(idx, (Float) val),
(extendedLength) -> "FLOAT")
DatabaseInterface
: Provides CRUD operations likegetByKey
,update
,upsert
,deleteByKeys
, and existence checks.QueryBuilder
: Supports complex query construction withColumnQuery
,ColumnsQuery
,RowQuery
,BindingRowQuery
, andBindingColumnsQuery
.Table
: Abstract base class for defining database tables.DatabaseType
: Enum for mapping Java types to SQL types with conversion logic.Binding
: Ensures type-safe joins between tables.ConditionQueryBuilder
: Builds WHERE clauses with AND/OR conditions.ColumnValue
: Represents key-value pairs for filtering.
- Input Validation: Throws
InvalidParameterException
for invalid inputs (e.g., type mismatches). - SQL Injection Protection: Uses
SQLInputFilter
to sanitize inputs. - Database Errors: Wrapped in
RuntimeException
viathrowDBError
. - Type Mismatches: Logged in
Binding
class, with invalid bindings set to null.
Contributions are welcome! Please follow these steps:
- Fork the repository.
- Create a new branch (
git checkout -b feature/your-feature
). - Commit your changes (
git commit -m 'Add your feature'
). - Push to the branch (
git push origin feature/your-feature
). - Open a Pull Request.
This project is licensed under the MIT License. See the LICENSE file for details.