diff --git a/README.md b/README.md index 6710368..98dfd55 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Include the Maven artifact: com.github.collinalpert java2db - 5.4.0 + 5.5.0 ``` Or include the [JAR](https://github.com/CollinAlpert/Java2DB/releases/latest) in your project. @@ -121,6 +121,8 @@ The `BaseService` provides a `createQuery` method which allows you to manually b Much rather, use the `getSingle` or `getMultiple` methods. `getMultiple` returns an `EntityQuery` object with a preconfigured WHERE condition and then allows you to chain some additional query options. As of the current `EntityQuery` version, WHERE, LIMIT and ORDER BY are supported. With the ORDER BY functionality, there is also the possibility to coalesce multiple columns when ordering. Effectively, the calls `createQuery().where(predicate)` and `getMultiple(predicate)` are the same. The latter is recommended.\ As previously mentioned, to execute the query and retrieve a result, use the `toList`, `toStream`, `toArray` or `toMap` methods. +As shown in the example above, you can automatically join a table using the `@ForeignKeyEntity` annotation. You also have the option to specify which type of join to use when joining. If you would like only a specific column to be joined, say, the `code` field of the `gender` table, you can additionally specify the `@ForeignKeyPath` annotation. + #### Update Every service class has support for updating a single as well as multiple entities at once on the database. Check out the different `update` methods provided by your service class. diff --git a/pom.xml b/pom.xml index 913578e..221f697 100755 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.collinalpert java2db - 5.4.0 + 5.5.0 jar Java2DB diff --git a/src/main/java/com/github/collinalpert/java2db/annotations/ForeignKeyEntity.java b/src/main/java/com/github/collinalpert/java2db/annotations/ForeignKeyEntity.java index 23e4701..7ef224a 100644 --- a/src/main/java/com/github/collinalpert/java2db/annotations/ForeignKeyEntity.java +++ b/src/main/java/com/github/collinalpert/java2db/annotations/ForeignKeyEntity.java @@ -23,4 +23,22 @@ @Retention(RetentionPolicy.RUNTIME) public @interface ForeignKeyEntity { String value(); + + JoinTypes joinType() default JoinTypes.LEFT; + + enum JoinTypes { + LEFT("left"), + INNER("inner"), + RIGHT("right"); + + private final String sqlKeyword; + + JoinTypes(String sqlKeyword) { + this.sqlKeyword = sqlKeyword; + } + + public String getSqlKeyword() { + return sqlKeyword; + } + } } diff --git a/src/main/java/com/github/collinalpert/java2db/annotations/ForeignKeyPath.java b/src/main/java/com/github/collinalpert/java2db/annotations/ForeignKeyPath.java new file mode 100644 index 0000000..64d0bb9 --- /dev/null +++ b/src/main/java/com/github/collinalpert/java2db/annotations/ForeignKeyPath.java @@ -0,0 +1,29 @@ +package com.github.collinalpert.java2db.annotations; + +import com.github.collinalpert.java2db.entities.BaseEntity; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to indicate that only a specific column of a table is supposed to be joined when executing the query. + * It has to be used in conjunction with the {@link ForeignKeyEntity} attribute. + * + * @author Collin Alpert + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ForeignKeyPath { + + /** + * @return The name of the column on the table which will be joined. + */ + String value(); + + /** + * @return The class which represents the table which will be joined. + */ + Class foreignKeyClass(); +} diff --git a/src/main/java/com/github/collinalpert/java2db/database/ForeignKeyReference.java b/src/main/java/com/github/collinalpert/java2db/database/ForeignKeyReference.java index c901f50..3594d47 100644 --- a/src/main/java/com/github/collinalpert/java2db/database/ForeignKeyReference.java +++ b/src/main/java/com/github/collinalpert/java2db/database/ForeignKeyReference.java @@ -15,21 +15,33 @@ public class ForeignKeyReference extends TableColumnReference { * The table the foreign key refers to. */ private final String foreignKeyTableName; + /** * The name of the column which references the foreign table. Not to be confused with the column in the foreign table. */ private final String foreignKeyColumnName; + /** * An alias for the foreign key table name. */ - private String foreignKeyAlias; + private final String foreignKeyAlias; + + /** + * The type of join to use. + */ + private final ForeignKeyEntity.JoinTypes joinType; - public ForeignKeyReference(String tableName, String alias, Field column, String foreignKeyTableName, String foreignKeyAlias) { + public ForeignKeyReference(String tableName, String alias, Field column, String foreignKeyTableName, String foreignKeyAlias, ForeignKeyEntity.JoinTypes joinType) { super(tableName, alias, column); this.foreignKeyTableName = foreignKeyTableName; this.foreignKeyColumnName = column.getAnnotation(ForeignKeyEntity.class).value(); this.foreignKeyAlias = foreignKeyAlias; + this.joinType = joinType; + } + + public ForeignKeyReference(String tableName, String alias, Field column, String foreignKeyTableName, String foreignKeyAlias) { + this(tableName, alias, column, foreignKeyTableName, foreignKeyAlias, ForeignKeyEntity.JoinTypes.LEFT); } public String getForeignKeyTableName() { @@ -43,4 +55,8 @@ public String getForeignKeyColumnName() { public String getForeignKeyAlias() { return foreignKeyAlias; } + + public ForeignKeyEntity.JoinTypes getJoinType() { + return joinType; + } } diff --git a/src/main/java/com/github/collinalpert/java2db/entities/SerializableBaseEntity.java b/src/main/java/com/github/collinalpert/java2db/entities/SerializableBaseEntity.java index 62048dd..1b6ff45 100644 --- a/src/main/java/com/github/collinalpert/java2db/entities/SerializableBaseEntity.java +++ b/src/main/java/com/github/collinalpert/java2db/entities/SerializableBaseEntity.java @@ -10,7 +10,7 @@ public class SerializableBaseEntity extends BaseEntity implements Serializable { private long id; public long getId() { - return id; + return this.id; } /** diff --git a/src/main/java/com/github/collinalpert/java2db/mappers/EntityMapper.java b/src/main/java/com/github/collinalpert/java2db/mappers/EntityMapper.java index fbc315e..7ce80c4 100755 --- a/src/main/java/com/github/collinalpert/java2db/mappers/EntityMapper.java +++ b/src/main/java/com/github/collinalpert/java2db/mappers/EntityMapper.java @@ -2,6 +2,7 @@ import com.github.collinalpert.java2db.annotations.ColumnName; import com.github.collinalpert.java2db.annotations.ForeignKeyEntity; +import com.github.collinalpert.java2db.annotations.ForeignKeyPath; import com.github.collinalpert.java2db.contracts.IdentifiableEnum; import com.github.collinalpert.java2db.entities.BaseEntity; import com.github.collinalpert.java2db.modules.AnnotationModule; @@ -194,6 +195,14 @@ private void setFields(ResultSet set, TEntity entit continue; } + var foreignKeyPathInfo = AnnotationModule.getInstance().getAnnotationInfo(field, ForeignKeyPath.class); + if (foreignKeyPathInfo.hasAnnotation()) { + var aliasKey = String.join("_", identifier, field.getName()); + tryAction(() -> field.set(entity, set.getObject(this.aliases.get(aliasKey) + "_" + foreignKeyPathInfo.getAnnotation().value(), field.getType()))); + + continue; + } + if (!BaseEntity.class.isAssignableFrom(field.getType())) { throw new IllegalArgumentException(String.format("Type %s, which is annotated as a foreign key, does not extend BaseEntity.", field.getType().getSimpleName())); } diff --git a/src/main/java/com/github/collinalpert/java2db/modules/FieldModule.java b/src/main/java/com/github/collinalpert/java2db/modules/FieldModule.java index 84270c1..8a68c56 100644 --- a/src/main/java/com/github/collinalpert/java2db/modules/FieldModule.java +++ b/src/main/java/com/github/collinalpert/java2db/modules/FieldModule.java @@ -1,10 +1,12 @@ package com.github.collinalpert.java2db.modules; import com.github.collinalpert.java2db.annotations.ForeignKeyEntity; +import com.github.collinalpert.java2db.annotations.ForeignKeyPath; import com.github.collinalpert.java2db.annotations.Ignore; import com.github.collinalpert.java2db.database.ForeignKeyReference; import com.github.collinalpert.java2db.database.TableColumnReference; import com.github.collinalpert.java2db.entities.BaseEntity; +import com.github.collinalpert.java2db.utilities.Utilities; import java.lang.reflect.Field; import java.util.Arrays; @@ -94,8 +96,22 @@ private List getColumnReferences(Class foreignKeyPathInfo.getAnnotation().foreignKeyClass().getDeclaredField(foreignKeyPathInfo.getAnnotation().value())); + fields.add(new TableColumnReference(foreignKeyTableName, tempAlias, foreignKeyField)); + + continue; + } + var tempAlias = tableModule.getTableName(field.getType()).substring(0, 1) + ++aliasCounter; - fields.add(new ForeignKeyReference(tableModule.getTableName(instanceClass), alias, field, tableModule.getTableName(field.getType()), tempAlias)); + var joinType = field.getAnnotation(ForeignKeyEntity.class).joinType(); + fields.add(new ForeignKeyReference(tableModule.getTableName(instanceClass), alias, field, tableModule.getTableName(field.getType()), tempAlias, joinType)); fields.addAll(getColumnReferences((Class) field.getType(), tempAlias, aliasCounter++)); } else { fields.add(new TableColumnReference(tableModule.getTableName(instanceClass), alias, field)); diff --git a/src/main/java/com/github/collinalpert/java2db/queries/SingleEntityQuery.java b/src/main/java/com/github/collinalpert/java2db/queries/SingleEntityQuery.java index 13083a6..36194df 100644 --- a/src/main/java/com/github/collinalpert/java2db/queries/SingleEntityQuery.java +++ b/src/main/java/com/github/collinalpert/java2db/queries/SingleEntityQuery.java @@ -117,7 +117,7 @@ public String getQuery() { for (var column : columns) { var foreignKey = (ForeignKeyReference) column; - builder.append(" left join `").append(foreignKey.getForeignKeyTableName()).append("` ").append(foreignKey.getForeignKeyAlias()).append(" on `").append(foreignKey.getAlias()).append("`.`").append(foreignKey.getForeignKeyColumnName()).append("` = `").append(foreignKey.getForeignKeyAlias()).append("`.`id`"); + builder.append(" ").append(foreignKey.getJoinType().getSqlKeyword()).append(" join `").append(foreignKey.getForeignKeyTableName()).append("` ").append(foreignKey.getForeignKeyAlias()).append(" on `").append(foreignKey.getAlias()).append("`.`").append(foreignKey.getForeignKeyColumnName()).append("` = `").append(foreignKey.getForeignKeyAlias()).append("`.`id`"); } builder.append(getQueryClauses(tableName)); diff --git a/src/main/java/com/github/collinalpert/java2db/services/BaseService.java b/src/main/java/com/github/collinalpert/java2db/services/BaseService.java index 5693fdd..4effac9 100755 --- a/src/main/java/com/github/collinalpert/java2db/services/BaseService.java +++ b/src/main/java/com/github/collinalpert/java2db/services/BaseService.java @@ -772,13 +772,14 @@ private String getSqlValue(Field entityField, E entityInstance) { * @param value The value to convert. * @return The SQL version of a Java value. */ + //TODO add pattern matching when switching to Java 14 private String convertToSql(Object value) { if (value == null) { return "null"; } if (value instanceof String) { - return "'" + value + "'"; + return "'" + escapeString((String) value) + "'"; } if (value instanceof Boolean) { @@ -804,6 +805,16 @@ private String convertToSql(Object value) { return value.toString(); } + /** + * Escapes characters in a String which would break an SQL statement, like a single quote or a backslash. + * + * @param input The string to escape characters in. + * @return The same string but with escaped characters. + */ + private String escapeString(String input) { + return input.replace("\\", "\\\\").replace("'", "\\'"); + } + /** * An overload of the {@link #createPaginationQueries(SqlPredicate, int)} method. * It creates queries for getting all the values from a table.