Skip to content

Commit

Permalink
Added support for paths in foreign keys and join flexibility
Browse files Browse the repository at this point in the history
  • Loading branch information
CollinAlpert committed Mar 23, 2020
1 parent 14f3a23 commit 8b691e1
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 8 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Include the Maven artifact:
<dependency>
<groupId>com.github.collinalpert</groupId>
<artifactId>java2db</artifactId>
<version>5.4.0</version>
<version>5.5.0</version>
</dependency>
```
Or include the [JAR](https://github.com/CollinAlpert/Java2DB/releases/latest) in your project.
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.github.collinalpert</groupId>
<artifactId>java2db</artifactId>
<version>5.4.0</version>
<version>5.5.0</version>
<packaging>jar</packaging>

<name>Java2DB</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<? extends BaseEntity> foreignKeyClass();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -43,4 +55,8 @@ public String getForeignKeyColumnName() {
public String getForeignKeyAlias() {
return foreignKeyAlias;
}

public ForeignKeyEntity.JoinTypes getJoinType() {
return joinType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class SerializableBaseEntity extends BaseEntity implements Serializable {
private long id;

public long getId() {
return id;
return this.id;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -194,6 +195,14 @@ private <TEntity extends BaseEntity> 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()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -94,8 +96,22 @@ private List<TableColumnReference> getColumnReferences(Class<? extends BaseEntit
}

if (annotationModule.hasAnnotation(field, ForeignKeyEntity.class)) {
var foreignKeyPathInfo = annotationModule.getAnnotationInfo(field, ForeignKeyPath.class);
if (foreignKeyPathInfo.hasAnnotation()) {
var foreignKeyTableName = tableModule.getTableName(foreignKeyPathInfo.getAnnotation().foreignKeyClass());
var tempAlias = foreignKeyTableName.substring(0, 1) + ++aliasCounter;
var joinType = field.getAnnotation(ForeignKeyEntity.class).joinType();
fields.add(new ForeignKeyReference(tableModule.getTableName(instanceClass), alias, field, foreignKeyTableName, tempAlias, joinType));

var foreignKeyField = Utilities.tryGetValue(() -> 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<? extends BaseEntity>) field.getType(), tempAlias, aliasCounter++));
} else {
fields.add(new TableColumnReference(tableModule.getTableName(instanceClass), alias, field));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.
Expand Down

0 comments on commit 8b691e1

Please sign in to comment.