Skip to content

Commit 8b691e1

Browse files
committed
Added support for paths in foreign keys and join flexibility
1 parent 14f3a23 commit 8b691e1

File tree

10 files changed

+109
-8
lines changed

10 files changed

+109
-8
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Include the Maven artifact:
2020
<dependency>
2121
<groupId>com.github.collinalpert</groupId>
2222
<artifactId>java2db</artifactId>
23-
<version>5.4.0</version>
23+
<version>5.5.0</version>
2424
</dependency>
2525
```
2626
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
121121
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.\
122122
As previously mentioned, to execute the query and retrieve a result, use the `toList`, `toStream`, `toArray` or `toMap` methods.
123123

124+
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.
125+
124126
#### Update
125127
Every service class has support for updating a single as well as multiple entities at once on the database.
126128
Check out the different `update` methods provided by your service class.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.github.collinalpert</groupId>
88
<artifactId>java2db</artifactId>
9-
<version>5.4.0</version>
9+
<version>5.5.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>Java2DB</name>

src/main/java/com/github/collinalpert/java2db/annotations/ForeignKeyEntity.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,22 @@
2323
@Retention(RetentionPolicy.RUNTIME)
2424
public @interface ForeignKeyEntity {
2525
String value();
26+
27+
JoinTypes joinType() default JoinTypes.LEFT;
28+
29+
enum JoinTypes {
30+
LEFT("left"),
31+
INNER("inner"),
32+
RIGHT("right");
33+
34+
private final String sqlKeyword;
35+
36+
JoinTypes(String sqlKeyword) {
37+
this.sqlKeyword = sqlKeyword;
38+
}
39+
40+
public String getSqlKeyword() {
41+
return sqlKeyword;
42+
}
43+
}
2644
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.github.collinalpert.java2db.annotations;
2+
3+
import com.github.collinalpert.java2db.entities.BaseEntity;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* This annotation is used to indicate that only a specific column of a table is supposed to be joined when executing the query.
12+
* It has to be used in conjunction with the {@link ForeignKeyEntity} attribute.
13+
*
14+
* @author Collin Alpert
15+
*/
16+
@Target(ElementType.FIELD)
17+
@Retention(RetentionPolicy.RUNTIME)
18+
public @interface ForeignKeyPath {
19+
20+
/**
21+
* @return The name of the column on the table which will be joined.
22+
*/
23+
String value();
24+
25+
/**
26+
* @return The class which represents the table which will be joined.
27+
*/
28+
Class<? extends BaseEntity> foreignKeyClass();
29+
}

src/main/java/com/github/collinalpert/java2db/database/ForeignKeyReference.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,33 @@ public class ForeignKeyReference extends TableColumnReference {
1515
* The table the foreign key refers to.
1616
*/
1717
private final String foreignKeyTableName;
18+
1819
/**
1920
* The name of the column which references the foreign table. Not to be confused with the column in the foreign table.
2021
*/
2122
private final String foreignKeyColumnName;
23+
2224
/**
2325
* An alias for the foreign key table name.
2426
*/
25-
private String foreignKeyAlias;
27+
private final String foreignKeyAlias;
28+
29+
/**
30+
* The type of join to use.
31+
*/
32+
private final ForeignKeyEntity.JoinTypes joinType;
2633

2734

28-
public ForeignKeyReference(String tableName, String alias, Field column, String foreignKeyTableName, String foreignKeyAlias) {
35+
public ForeignKeyReference(String tableName, String alias, Field column, String foreignKeyTableName, String foreignKeyAlias, ForeignKeyEntity.JoinTypes joinType) {
2936
super(tableName, alias, column);
3037
this.foreignKeyTableName = foreignKeyTableName;
3138
this.foreignKeyColumnName = column.getAnnotation(ForeignKeyEntity.class).value();
3239
this.foreignKeyAlias = foreignKeyAlias;
40+
this.joinType = joinType;
41+
}
42+
43+
public ForeignKeyReference(String tableName, String alias, Field column, String foreignKeyTableName, String foreignKeyAlias) {
44+
this(tableName, alias, column, foreignKeyTableName, foreignKeyAlias, ForeignKeyEntity.JoinTypes.LEFT);
3345
}
3446

3547
public String getForeignKeyTableName() {
@@ -43,4 +55,8 @@ public String getForeignKeyColumnName() {
4355
public String getForeignKeyAlias() {
4456
return foreignKeyAlias;
4557
}
58+
59+
public ForeignKeyEntity.JoinTypes getJoinType() {
60+
return joinType;
61+
}
4662
}

src/main/java/com/github/collinalpert/java2db/entities/SerializableBaseEntity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class SerializableBaseEntity extends BaseEntity implements Serializable {
1010
private long id;
1111

1212
public long getId() {
13-
return id;
13+
return this.id;
1414
}
1515

1616
/**

src/main/java/com/github/collinalpert/java2db/mappers/EntityMapper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.collinalpert.java2db.annotations.ColumnName;
44
import com.github.collinalpert.java2db.annotations.ForeignKeyEntity;
5+
import com.github.collinalpert.java2db.annotations.ForeignKeyPath;
56
import com.github.collinalpert.java2db.contracts.IdentifiableEnum;
67
import com.github.collinalpert.java2db.entities.BaseEntity;
78
import com.github.collinalpert.java2db.modules.AnnotationModule;
@@ -194,6 +195,14 @@ private <TEntity extends BaseEntity> void setFields(ResultSet set, TEntity entit
194195
continue;
195196
}
196197

198+
var foreignKeyPathInfo = AnnotationModule.getInstance().getAnnotationInfo(field, ForeignKeyPath.class);
199+
if (foreignKeyPathInfo.hasAnnotation()) {
200+
var aliasKey = String.join("_", identifier, field.getName());
201+
tryAction(() -> field.set(entity, set.getObject(this.aliases.get(aliasKey) + "_" + foreignKeyPathInfo.getAnnotation().value(), field.getType())));
202+
203+
continue;
204+
}
205+
197206
if (!BaseEntity.class.isAssignableFrom(field.getType())) {
198207
throw new IllegalArgumentException(String.format("Type %s, which is annotated as a foreign key, does not extend BaseEntity.", field.getType().getSimpleName()));
199208
}

src/main/java/com/github/collinalpert/java2db/modules/FieldModule.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.github.collinalpert.java2db.modules;
22

33
import com.github.collinalpert.java2db.annotations.ForeignKeyEntity;
4+
import com.github.collinalpert.java2db.annotations.ForeignKeyPath;
45
import com.github.collinalpert.java2db.annotations.Ignore;
56
import com.github.collinalpert.java2db.database.ForeignKeyReference;
67
import com.github.collinalpert.java2db.database.TableColumnReference;
78
import com.github.collinalpert.java2db.entities.BaseEntity;
9+
import com.github.collinalpert.java2db.utilities.Utilities;
810

911
import java.lang.reflect.Field;
1012
import java.util.Arrays;
@@ -94,8 +96,22 @@ private List<TableColumnReference> getColumnReferences(Class<? extends BaseEntit
9496
}
9597

9698
if (annotationModule.hasAnnotation(field, ForeignKeyEntity.class)) {
99+
var foreignKeyPathInfo = annotationModule.getAnnotationInfo(field, ForeignKeyPath.class);
100+
if (foreignKeyPathInfo.hasAnnotation()) {
101+
var foreignKeyTableName = tableModule.getTableName(foreignKeyPathInfo.getAnnotation().foreignKeyClass());
102+
var tempAlias = foreignKeyTableName.substring(0, 1) + ++aliasCounter;
103+
var joinType = field.getAnnotation(ForeignKeyEntity.class).joinType();
104+
fields.add(new ForeignKeyReference(tableModule.getTableName(instanceClass), alias, field, foreignKeyTableName, tempAlias, joinType));
105+
106+
var foreignKeyField = Utilities.tryGetValue(() -> foreignKeyPathInfo.getAnnotation().foreignKeyClass().getDeclaredField(foreignKeyPathInfo.getAnnotation().value()));
107+
fields.add(new TableColumnReference(foreignKeyTableName, tempAlias, foreignKeyField));
108+
109+
continue;
110+
}
111+
97112
var tempAlias = tableModule.getTableName(field.getType()).substring(0, 1) + ++aliasCounter;
98-
fields.add(new ForeignKeyReference(tableModule.getTableName(instanceClass), alias, field, tableModule.getTableName(field.getType()), tempAlias));
113+
var joinType = field.getAnnotation(ForeignKeyEntity.class).joinType();
114+
fields.add(new ForeignKeyReference(tableModule.getTableName(instanceClass), alias, field, tableModule.getTableName(field.getType()), tempAlias, joinType));
99115
fields.addAll(getColumnReferences((Class<? extends BaseEntity>) field.getType(), tempAlias, aliasCounter++));
100116
} else {
101117
fields.add(new TableColumnReference(tableModule.getTableName(instanceClass), alias, field));

src/main/java/com/github/collinalpert/java2db/queries/SingleEntityQuery.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public String getQuery() {
117117

118118
for (var column : columns) {
119119
var foreignKey = (ForeignKeyReference) column;
120-
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`");
120+
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`");
121121
}
122122

123123
builder.append(getQueryClauses(tableName));

src/main/java/com/github/collinalpert/java2db/services/BaseService.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,13 +772,14 @@ private String getSqlValue(Field entityField, E entityInstance) {
772772
* @param value The value to convert.
773773
* @return The SQL version of a Java value.
774774
*/
775+
//TODO add pattern matching when switching to Java 14
775776
private String convertToSql(Object value) {
776777
if (value == null) {
777778
return "null";
778779
}
779780

780781
if (value instanceof String) {
781-
return "'" + value + "'";
782+
return "'" + escapeString((String) value) + "'";
782783
}
783784

784785
if (value instanceof Boolean) {
@@ -804,6 +805,16 @@ private String convertToSql(Object value) {
804805
return value.toString();
805806
}
806807

808+
/**
809+
* Escapes characters in a String which would break an SQL statement, like a single quote or a backslash.
810+
*
811+
* @param input The string to escape characters in.
812+
* @return The same string but with escaped characters.
813+
*/
814+
private String escapeString(String input) {
815+
return input.replace("\\", "\\\\").replace("'", "\\'");
816+
}
817+
807818
/**
808819
* An overload of the {@link #createPaginationQueries(SqlPredicate, int)} method.
809820
* It creates queries for getting all the values from a table.

0 commit comments

Comments
 (0)