diff options
Diffstat (limited to 'src/main/java')
-rw-r--r-- | src/main/java/de/xenoworld/ormpaloompa/Table.java | 126 | ||||
-rw-r--r-- | src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java | 29 | ||||
-rw-r--r-- | src/main/java/de/xenoworld/ormpaloompa/annotations/Column.java (renamed from src/main/java/de/xenoworld/ormpaloompa/annotations/Field.java) | 10 | ||||
-rw-r--r-- | src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java | 2 | ||||
-rw-r--r-- | src/main/java/overview.adoc | 36 |
5 files changed, 140 insertions, 63 deletions
diff --git a/src/main/java/de/xenoworld/ormpaloompa/Table.java b/src/main/java/de/xenoworld/ormpaloompa/Table.java index 96497b6..6d23fcd 100644 --- a/src/main/java/de/xenoworld/ormpaloompa/Table.java +++ b/src/main/java/de/xenoworld/ormpaloompa/Table.java @@ -1,9 +1,10 @@ package de.xenoworld.ormpaloompa; -import de.xenoworld.ormpaloompa.annotations.Field; +import de.xenoworld.ormpaloompa.annotations.Column; import de.xenoworld.ormpaloompa.annotations.TableInfo; import org.apache.commons.lang3.tuple.Pair; +import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -16,10 +17,9 @@ import java.util.stream.Stream; // TODO: Filter out fields where the value is null // TODO: Filter out the identity fields when updating -// (insert identities only when they are not null → auto IDs, but things like the term-database do -// have a string as key) +// (insert identities only when they are not null → auto IDs, but things +// like the term-database do have a string as key) // TODO: Use more Optionals and get rid of exception throwing where possible -// Name: Ormpaloompa /** * A SQLite table of a POJO `T` as a proxy. * @@ -31,10 +31,10 @@ import java.util.stream.Stream; * [source,java] * ---- * class Bird { - * @Field(identity = true) + * @Column(identity = true) * public Integer id; * - * @Field + * @Column * public String name; * } * ---- @@ -57,9 +57,12 @@ public class Table<T> { private final Connection connection; private final String tableName; - private final TableField identity; - private ArrayList<TableField> tableFields; + private final TableColumn identity; + private ArrayList<TableColumn> tableColumns; + /** + * A central location for all query strings. + */ private enum Query { COUNT("SELECT COUNT(*) FROM %s"), GET_BY_ID("SELECT * FROM %s WHERE %s = ?"), @@ -79,23 +82,33 @@ public class Table<T> { } } - - private class TableField { + /** + * Describes a column in the database. + * + * Properties are: + * `field`:: + * The annotated field. + * optional `name`:: + * Can be used to specify an alternative name for a column. + * optional `identity`:: + * Used to mark the primary key. If omitted, the column with the name + * `Id` is used. + */ + private class TableColumn { final String name; - final String dflt; + final Field field; final boolean identity; - private TableField(String name, String dflt, boolean identity) { + private TableColumn(Field field, String name, boolean identity) { + this.field = field; this.name = name; - this.dflt = dflt; this.identity = identity; } @Override public String toString() { - return "TableField{" + + return "TableColumn{" + "name='" + name + '\'' + - ", dflt='" + dflt + '\'' + ", identity=" + identity + '}'; } @@ -105,14 +118,14 @@ public class Table<T> { this.tableClass = tableClass; this.connection = connection; - tableFields = new ArrayList<>(); + tableColumns = new ArrayList<>(); Stream.of(tableClass.getFields()) - .map(field -> Pair.of(field.getName(), field.getAnnotation(Field.class))) - .forEach(p -> tableFields.add( - new TableField( - p.getRight().name().isEmpty() ? p.getLeft() : p.getRight().name(), - p.getRight().dflt(), + .map(field -> Pair.of(field, field.getAnnotation(Column.class))) + .forEach(p -> tableColumns.add( + new TableColumn( + p.getLeft(), + p.getRight().name().isEmpty() ? p.getLeft().getName() : p.getRight().name(), p.getRight().identity()))); this.tableName = discoverTableName(tableClass); @@ -120,12 +133,12 @@ public class Table<T> { } // TODO: This is kind of an idiotic contraption - private TableField discoverIdentity(Class<T> tableClass) { - return tableFields.stream() - .filter(f -> f.identity) + private TableColumn discoverIdentity(Class<T> tableClass) { + return tableColumns.stream() + .filter(c -> c.identity) .findFirst() - .orElseGet(() -> tableFields.stream() - .filter(f -> f.name.toLowerCase().equals("id")) + .orElseGet(() -> tableColumns.stream() + .filter(c -> c.name.toLowerCase().equals("id")) .findFirst() .orElseThrow(() -> new RuntimeException( String.format("%s: No identity given, and no field 'id' present.", @@ -139,8 +152,8 @@ public class Table<T> { * Get name for table from TableInfo annotation or append "s" to the lower * case table name. * - * @param tableClass table to inspect - * @return name of table in database + * @param tableClass table to inspect. + * @return name of table in database. */ private String discoverTableName(Class<T> tableClass) { TableInfo tableInfo = tableClass.getAnnotation(TableInfo.class); @@ -154,7 +167,7 @@ public class Table<T> { /** * Return the number of rows in the table, uses `COUNT(*)`. * - * @return total number of rows + * @return total number of rows. * @throws SQLException */ public Integer count() throws SQLException { @@ -168,7 +181,7 @@ public class Table<T> { /** * Get a `T` by Identity. * - * @param id identity + * @param id identity. * @return `Optional<T>`, `empty()` if no object could be found, or if an * error occurred. */ @@ -208,9 +221,9 @@ public class Table<T> { * * This will create a query for `… WHERE foo = bar …`. * - * @param whereQuery a query - * @param args (optional) arguments to WHERE clause - * @return a Stream of matching `T` + * @param whereQuery a query. + * @param args (optional) arguments to WHERE clause. + * @return a Stream of matching `T`. */ public Stream<T> find(WhereQuery whereQuery, Object... args) { // TODO: Make try-mess better @@ -236,28 +249,44 @@ public class Table<T> { } } + /** + * Create a new `T` from a ResultSet. + * + * @param result ResultSet to construct `T` from. + * @return a new `T`. + * @throws InstantiationException + * @throws IllegalAccessException + */ private T fromResultSet(ResultSet result) throws InstantiationException, IllegalAccessException { T t = tableClass.newInstance(); - tableFields.forEach(f -> { + tableColumns.forEach(c -> { try { - t.getClass().getField(f.name).set(t, result.getObject(f.name)); + t.getClass().getField(c.field.getName()).set(t, result.getObject(c.name)); } catch (IllegalAccessException | NoSuchFieldException | SQLException e) { e.printStackTrace(); } }); + return t; } + /** + * Insert a `T` into the database. + * + * @param t `T` to insert. + * @return an insert id. + * @throws SQLException + */ public Long insert(T t) throws SQLException { - String fields = tableFields + String fields = tableColumns .stream() .map(f -> f.name) .collect(Collectors.joining(",")); StringBuffer placeholder = new StringBuffer("?"); - IntStream.range(0, tableFields.size() - 1).forEach(i -> placeholder.append(", ?")); + IntStream.range(0, tableColumns.size() - 1).forEach(i -> placeholder.append(", ?")); String query = String.format(Query.INSERT.toString(), tableName, fields, placeholder); PreparedStatement stmt = connection.prepareStatement(query); @@ -269,8 +298,13 @@ public class Table<T> { return stmt.getGeneratedKeys().getLong(1); } + /** + * Update an existing `T`. + * + * @param t `T` to update. + */ public void update(T t) { - String fields = tableFields + String fields = tableColumns .stream() .map(f -> f.name) .collect(Collectors.joining(" = ?, ")); @@ -290,9 +324,9 @@ public class Table<T> { private int fillStatementFromObject(T t, PreparedStatement stmt) { final int[] order = {1}; - tableFields.stream().forEach(f -> { + tableColumns.stream().forEach(c -> { try { - Object o = t.getClass().getField(f.name).get(t); + Object o = t.getClass().getField(c.field.getName()).get(t); stmt.setObject(order[0]++, o); } catch (IllegalAccessException | NoSuchFieldException | SQLException e) { e.printStackTrace(); @@ -303,10 +337,10 @@ public class Table<T> { } /** - * Take `t` and insert a new entry, or update an existing entry. + * Insert `T`, or update if it already exists. * - * @param t - * @return Optional of insert Id (a Long) + * @param t `T` to insert or update. + * @return a `Optional<>` of insert id (a `Long`). * @throws NoSuchFieldException * @throws IllegalAccessException * @throws SQLException @@ -328,13 +362,13 @@ public class Table<T> { } /** - * Retrieve the current value of the `@Identity` field. + * Retrieve the current value of the `@Column(identity = true)` field. * * Will return an empty Optional if the value is null, or if there was * an Exception. * - * @param t object to retrieve identity value from - * @return value of identity, or empty on null or error + * @param t object to retrieve identity value from. + * @return value of identity, or empty on null or error. */ public Optional<Object> retrieveIdValue(T t) { try { diff --git a/src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java b/src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java index fcc42eb..4047be4 100644 --- a/src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java +++ b/src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java @@ -3,7 +3,9 @@ package de.xenoworld.ormpaloompa; import java.util.Optional; public class WhereQuery { + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private Optional<Integer> limit = Optional.empty(); + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private Optional<String> orderFields = Optional.empty(); private Order order; private String where; @@ -34,17 +36,40 @@ public class WhereQuery { this.where = where; } + /** + * Set `LIMIT` on `WHERE` clause. + * + * @param limit maximum number of results to return. + * @return same WhereQuery. + */ public WhereQuery limit(Integer limit) { this.limit = Optional.of(limit); return this; } - public WhereQuery orderBy(String fields, Order order) { - orderFields = Optional.of(fields); + /** + * Set `ORDER BY` on `WHERE` clause. + * + * `columns` may be one column name, or multiple names separated by comma. + * + * @param columns one or more columns to `ORDER BY` + * @param order ascending or descending order. + * @return same WhereQuery. + */ + public WhereQuery orderBy(String columns, Order order) { + orderFields = Optional.of(columns); this.order = order; return this; } + /** + * Add the `WHERE` query. + * + * This is something like `foo = 1234` or `bar = ?`. + * + * @param where the actual where query. + * @return same WhereQuery. + */ public static WhereQuery where(String where) { return new WhereQuery(where); } diff --git a/src/main/java/de/xenoworld/ormpaloompa/annotations/Field.java b/src/main/java/de/xenoworld/ormpaloompa/annotations/Column.java index 5cd917b..92a917b 100644 --- a/src/main/java/de/xenoworld/ormpaloompa/annotations/Field.java +++ b/src/main/java/de/xenoworld/ormpaloompa/annotations/Column.java @@ -7,10 +7,14 @@ import java.lang.annotation.RetentionPolicy; * Annotate a public field. */ @Retention(RetentionPolicy.RUNTIME) -public @interface Field { +public @interface Column { + /** + * If non-empty, the column name to be used. + */ String name() default ""; - String dflt() default ""; - + /** + * `true` if the field is the table's identity. + */ boolean identity() default false; }
\ No newline at end of file diff --git a/src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java b/src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java index 5094c5d..04c2d23 100644 --- a/src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java +++ b/src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java @@ -4,7 +4,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Optional table annotation, can be used to set the table name in the database + * Optional table annotation, can be used to set the table name in the database. */ @Retention(RetentionPolicy.RUNTIME) public @interface TableInfo { diff --git a/src/main/java/overview.adoc b/src/main/java/overview.adoc index 90a82f6..90642bb 100644 --- a/src/main/java/overview.adoc +++ b/src/main/java/overview.adoc @@ -1,18 +1,32 @@ -= Eduard XVII — The loyal MUC servant +== Ormpaloompa — Sort-of-ORM for EduardXVII -== Continuous Integration +Because the friendly MUC chat bot EduardXVII needs to deal in several places +with simple-structured SQLite tables, I created this tiny object-relational +mapper. It stinks in several places, but it also gets its job done. Coverage is +over 85% and should go further north. -We currently use the CI of Gitlab.com, but it might be warranted to use a -dedicated runner which could execute the requests much faster. This is work -for Future Homer. +It mainly consists of the class `Table` which can be used as follows. -Currently everything is configured in the file `.gitlab-ci.yml`: +As simple class could look like this: -[source,yaml] +[source,java] ---- -include::../../../.gitlab-ci.yml[] +class Bird { + @Column(identity = true) + public Integer id; + + @Column + public String name; +} ---- -NOTE: Replacing `ec.SunEC` is done here because there were problems in the - Docker container with OpenJDK. Apparently making a HTTPS connection - to the Gradle repositories would not work.
\ No newline at end of file +Then create a `Table<Bird>` object and use it to interact with the database. + +[source,java] +---- +Table<Bird> t = new Table<>(Bird.class, someDatabaseConnection); +Bird newBird = new Bird(); +newBird.name = "A new bird"; +Object id = table.insert(newBird); +Bird storedBird = t.getById(id).get(); +---- |