Skip to content

Commit 75f7baf

Browse files
committed
Adding association mappings to generated entities
1 parent 5768117 commit 75f7baf

25 files changed

+375
-78
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# sqlscript2jpa-codegen
2+
23
SQL Schema Script to JPA Code Generator Maven Plugin

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0"
3-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
44
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
55

66
<modelVersion>4.0.0</modelVersion>
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.ngbsn.generator;
2+
3+
import org.apache.commons.text.CaseUtils;
4+
import org.ngbsn.model.Column;
5+
import org.ngbsn.model.ForeignKeyConstraint;
6+
import org.ngbsn.model.Table;
7+
import org.ngbsn.model.annotations.fieldAnnotations.*;
8+
9+
import java.util.HashSet;
10+
import java.util.List;
11+
import java.util.Optional;
12+
import java.util.Set;
13+
14+
import static org.ngbsn.generator.ModelGenerator.tablesMap;
15+
16+
/**
17+
* Is there more than 1 foreign key? - This is a linked table
18+
* Are all fields foreign keys? @ManyToMany and no separate entity needed for linked table
19+
* Are there some non-foreign key fields? - @ManyToOne in a separate entity for linked table
20+
* <p>
21+
* Is there a primary key in the foreign keys list? Mark this as @MapsId
22+
*/
23+
public class AssociationMappingsGenerator {
24+
25+
public static void generateMappings(final Table table) {
26+
27+
List<ForeignKeyConstraint> foreignKeyConstraintList = table.getForeignKeyConstraints();
28+
if (foreignKeyConstraintList.size() == 1) {
29+
//Case: There is only 1 Foreign key or 1 Composite Foreign Key
30+
//Treat this as a regular table and add a new column with for parent field with @ManyToOne
31+
//and add a new column in ReferencedTable for child field with @OneToMany
32+
addBothUnidirectionalMappings(table, foreignKeyConstraintList.get(0));
33+
34+
} else {
35+
//Case: There are multiple Foreign Keys or multiple Composite Foreign Keys
36+
//Treat this as a Link table
37+
List<String> foreignKeyColumns = foreignKeyConstraintList.stream().flatMap(foreignKeyConstraint -> foreignKeyConstraint.getColumns().stream()).toList();
38+
Optional<Column> optionalKey = table.getColumns().stream().filter(column -> !foreignKeyColumns.contains(column.getName())).findAny();
39+
if (optionalKey.isEmpty() && foreignKeyConstraintList.size() == 2) {
40+
//Case: All fields are foreign keys. Also, the relation exits between 2 entities only
41+
//Remove this link entity from the tablesMap as separate entity is not needed to track Link Table. Use @ManyToMany on other 2 entities
42+
tablesMap.remove(table.getName());
43+
Table table1 = tablesMap.get(foreignKeyConstraintList.get(0).getReferencedTableName());
44+
Table table2 = tablesMap.get(foreignKeyConstraintList.get(1).getReferencedTableName());
45+
46+
Column column1 = new Column();
47+
column1.setFieldName(table2.getClassName().toLowerCase());
48+
column1.setType(table2.getClassName());
49+
column1.getAnnotations().add(ManyToManyAnnotation.builder().build().toString());
50+
51+
Set<JoinColumnAnnotation> joinColumnAnnotations = new HashSet<>();
52+
for (String column : foreignKeyConstraintList.get(0).getColumns()) {
53+
joinColumnAnnotations.add(JoinColumnAnnotation.builder().name(column).build());
54+
}
55+
56+
Set<JoinColumnAnnotation> joinInverseColumnAnnotations = new HashSet<>();
57+
for (String column : foreignKeyConstraintList.get(1).getColumns()) {
58+
joinInverseColumnAnnotations.add(JoinColumnAnnotation.builder().name(column).build());
59+
}
60+
61+
column1.getAnnotations().add(JoinTableAnnotation.builder().tableName(table.getName()).joinColumns(joinColumnAnnotations).inverseJoinColumns(joinInverseColumnAnnotations).build().toString());
62+
63+
Column column2 = new Column();
64+
column2.setFieldName(table1.getClassName().toLowerCase());
65+
column2.setType(table1.getClassName());
66+
column2.getAnnotations().add(ManyToManyAnnotation.builder().mappedBy(column1.getFieldName()).toString());
67+
68+
} else {
69+
//Case1: There are some fields that are not foreign keys. So separate entity is needed to track Link Table
70+
//Case2: All fields are foreign keys. But, the relation exits between 2 or more entities
71+
//Add @ManyToOne for each foreignKey and corresponding @OneToMany in referenced Table
72+
foreignKeyConstraintList.forEach(foreignKeyConstraint -> {
73+
addBothUnidirectionalMappings(table, foreignKeyConstraint);
74+
});
75+
}
76+
}
77+
78+
}
79+
80+
private static void addBothUnidirectionalMappings(Table table, ForeignKeyConstraint foreignKeyConstraint) {
81+
Table referencedTable = tablesMap.get(foreignKeyConstraint.getReferencedTableName());
82+
Column foreignKeyColumn = new Column();
83+
foreignKeyColumn.setFieldName(referencedTable.getClassName().toLowerCase());
84+
foreignKeyColumn.setType(referencedTable.getClassName());
85+
foreignKeyColumn.getAnnotations().add(new ManyToOneAnnotation().toString());
86+
table.getColumns().add(foreignKeyColumn);
87+
88+
Column childKeyColumn = new Column();
89+
childKeyColumn.setFieldName(table.getClassName().toLowerCase());
90+
childKeyColumn.setType("Set<" + table.getClassName() + ">");
91+
childKeyColumn.getAnnotations().add(OneToManyAnnotation.builder().mappedBy(foreignKeyColumn.getFieldName()).build().toString());
92+
referencedTable.getColumns().add(childKeyColumn);
93+
94+
if (foreignKeyConstraint.getColumns().size() > 1) {
95+
//Case: Composite Foreign key
96+
Set<JoinColumnAnnotation> joinColumns = new HashSet<>();
97+
for (int i = 0; i < foreignKeyConstraint.getColumns().size(); i++) {
98+
int finalI = i;
99+
Optional<Column> optionalColumn = table.getColumns().stream().filter(column -> column.getName().equals(foreignKeyConstraint.getColumns().get(finalI))).findFirst();
100+
optionalColumn.ifPresent(column -> table.getColumns().remove(column));
101+
102+
JoinColumnAnnotation joinColumnAnnotation = JoinColumnAnnotation.builder().name(foreignKeyConstraint.getColumns().get(i)).referencedColumnName(foreignKeyConstraint.getReferencedColumns().get(i)).build();
103+
joinColumns.add(joinColumnAnnotation);
104+
105+
}
106+
foreignKeyColumn.getAnnotations().add(JoinColumnsAnnotation.builder().joinColumns(joinColumns).build().toString());
107+
108+
} else {
109+
//Case: Single Foreign Key
110+
Optional<Column> optionalColumn = table.getColumns().stream().filter(column -> column.getName().equals(foreignKeyConstraint.getReferencedColumns().get(0))).findFirst();
111+
optionalColumn.ifPresent(column -> table.getColumns().remove(column));
112+
foreignKeyColumn.getAnnotations().add(JoinColumnAnnotation.builder().name(CaseUtils.toCamelCase(foreignKeyConstraint.getReferencedColumns().get(0), false, '_')).referencedColumnName(foreignKeyConstraint.getReferencedColumns().get(0)).build().toString());
113+
}
114+
}
115+
}
Lines changed: 102 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,136 @@
11
package org.ngbsn.generator;
22

3-
43
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
54
import net.sf.jsqlparser.statement.Statements;
65
import net.sf.jsqlparser.statement.create.table.CreateTable;
6+
import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex;
77
import net.sf.jsqlparser.statement.create.table.Index;
88
import org.apache.commons.text.CaseUtils;
99
import org.ngbsn.model.Column;
10+
import org.ngbsn.model.ForeignKeyConstraint;
1011
import org.ngbsn.model.Table;
12+
import org.ngbsn.model.annotations.entityAnnotations.EntityAnnotation;
13+
import org.ngbsn.model.annotations.entityAnnotations.TableAnnotation;
14+
import org.ngbsn.model.annotations.fieldAnnotations.ColumnAnnotation;
15+
import org.ngbsn.model.annotations.fieldAnnotations.NotNullAnnotation;
1116
import org.ngbsn.util.SQLToJavaMapping;
1217
import org.slf4j.Logger;
1318
import org.slf4j.LoggerFactory;
1419

15-
import java.util.ArrayList;
16-
import java.util.List;
17-
import java.util.Optional;
20+
import java.util.*;
21+
22+
import static org.ngbsn.generator.AssociationMappingsGenerator.generateMappings;
1823

24+
/**
25+
* Ignoring these annotations as they are useful only in DDL generation:
26+
* UNIQUE
27+
*/
1928
public class ModelGenerator {
2029
private static final Logger logger = LoggerFactory.getLogger(ModelGenerator.class);
30+
protected static Map<String, Table> tablesMap = new HashMap<>();
2131

2232
public static List<Table> parse(final String sqlScript) {
2333
try {
2434
Statements statements = CCJSqlParserUtil.parseStatements(sqlScript);
25-
List<Table> tables = new ArrayList<>();
2635
statements.getStatements().forEach(statement -> {
36+
//Iterating over all Tables
2737
if (statement instanceof CreateTable parsedTable) {
2838
Table table = new Table();
29-
tables.add(table);
39+
tablesMap.put(parsedTable.getTable().getName(), table);
3040
table.setName(parsedTable.getTable().getName());
3141
table.setClassName(CaseUtils.toCamelCase(table.getName(), true, '_'));
42+
43+
List<String> tableAnnotations = new ArrayList<>();
44+
table.setAnnotations(tableAnnotations);
45+
//Adding @Entity
46+
tableAnnotations.add(new EntityAnnotation().toString());
47+
//Adding @Table
48+
tableAnnotations.add(TableAnnotation.builder().tableName(parsedTable.getTable().getName()).build().toString());
49+
3250
List<Column> columns = new ArrayList<>();
33-
parsedTable.getColumnDefinitions().forEach(columnDefinition -> {
34-
Column column = new Column();
35-
columns.add(column);
36-
column.setName(columnDefinition.getColumnName());
37-
column.setFieldName(CaseUtils.toCamelCase(column.getName(), false, '_'));
38-
column.setType(SQLToJavaMapping.sqlToJavaMap.get(columnDefinition.getColDataType().getDataType()));
39-
column.setNullable(true);
40-
if (columnDefinition.getColumnSpecs() != null) {
41-
String constraints = String.join(" ", columnDefinition.getColumnSpecs());
42-
if (constraints.contains("UNIQUE")) {
43-
column.setUnique(true);
44-
}
45-
if (constraints.contains("NOT NULL")) {
46-
column.setNullable(false);
47-
}
48-
}
49-
});
5051
table.setColumns(columns);
51-
Optional<Index> optionalIndex = parsedTable.getIndexes().stream().filter(index -> index.getType().equals("PRIMARY KEY")).findFirst();
52-
List<Index.ColumnParams> columnParamsList;
53-
columnParamsList = optionalIndex.map(Index::getColumns).orElse(null);
54-
if(columnParamsList != null){
55-
if(columnParamsList.size() > 1){
56-
table.setNumOfPrimaryKeyColumns(columnParamsList.size());
57-
}
58-
List<Column> primaryKeyColumns = table.getColumns().stream().filter(column -> columnParamsList.stream().anyMatch(columnParams -> columnParams.getColumnName().equals(column.getName()))).toList();
59-
primaryKeyColumns.forEach(column -> column.setPrimaryKey(true));
60-
}
52+
extractColumns(parsedTable, columns);
53+
54+
extractPrimaryKeys(parsedTable, table);
55+
56+
extractForeignKeys(parsedTable, table);
57+
58+
generateMappings(table);
59+
6160
}
6261
});
63-
64-
return tables;
62+
return tablesMap.values().stream().toList();
6563
} catch (Exception e) {
66-
logger.error(e.getMessage());
64+
logger.error("Error occurred", e);
6765
}
6866
return null;
6967
}
68+
69+
/**
70+
* Looking for all foreign keys in this table and adding it to our model
71+
*
72+
* @param parsedTable The SQL script parsed table
73+
* @param table Table model
74+
*/
75+
private static void extractForeignKeys(CreateTable parsedTable, Table table) {
76+
List<Index> foreignKeyIndexes = parsedTable.getIndexes().stream().filter(index -> index instanceof ForeignKeyIndex).toList();
77+
if (!foreignKeyIndexes.isEmpty()) {
78+
foreignKeyIndexes.forEach(index -> {
79+
if (index instanceof ForeignKeyIndex foreignKeyIndex) {
80+
ForeignKeyConstraint foreignKeyConstraint = new ForeignKeyConstraint();
81+
foreignKeyConstraint.setColumns(foreignKeyIndex.getColumnsNames());
82+
foreignKeyConstraint.setReferencedColumns(foreignKeyIndex.getReferencedColumnNames());
83+
foreignKeyConstraint.setReferencedTableName(foreignKeyIndex.getTable().getName());
84+
table.getForeignKeyConstraints().add(foreignKeyConstraint);
85+
}
86+
});
87+
}
88+
}
89+
90+
/**
91+
* Looking for all primary keys in this table
92+
*
93+
* @param parsedTable The SQL script parsed table
94+
* @param table Table model
95+
*/
96+
private static void extractPrimaryKeys(CreateTable parsedTable, Table table) {
97+
Optional<Index> optionalIndex = parsedTable.getIndexes().stream().filter(index -> index.getType().equals("PRIMARY KEY")).findFirst();
98+
List<Index.ColumnParams> columnParamsList = optionalIndex.map(Index::getColumns).orElse(null);
99+
if (columnParamsList != null) {
100+
if (columnParamsList.size() > 1) {
101+
table.setNumOfPrimaryKeyColumns(columnParamsList.size());
102+
}
103+
List<Column> primaryKeyColumns = table.getColumns().stream().
104+
filter(column -> columnParamsList.stream().anyMatch(columnParams -> columnParams.getColumnName().equals(column.getName()))).toList();
105+
primaryKeyColumns.forEach(column -> column.setPrimaryKey(true));
106+
}
107+
}
108+
109+
/**
110+
* Generate column models for all the parsed table
111+
*
112+
* @param parsedTable Parsed table
113+
* @param columns List of generated column models
114+
*/
115+
private static void extractColumns(CreateTable parsedTable, List<Column> columns) {
116+
parsedTable.getColumnDefinitions().forEach(columnDefinition -> {
117+
Column column = new Column();
118+
columns.add(column);
119+
List<String> columnAnnotations = new ArrayList<>();
120+
column.setAnnotations(columnAnnotations);
121+
column.setName(columnDefinition.getColumnName());
122+
//Adding @Column
123+
columnAnnotations.add(ColumnAnnotation.builder().columnName(column.getName()).build().toString());
124+
column.setFieldName(CaseUtils.toCamelCase(columnDefinition.getColumnName(), false, '_'));
125+
column.setType(SQLToJavaMapping.sqlToJavaMap.get(columnDefinition.getColDataType().getDataType()));
126+
127+
//Check for NOT NULL
128+
if (columnDefinition.getColumnSpecs() != null) {
129+
String constraints = String.join(" ", columnDefinition.getColumnSpecs());
130+
if (constraints.contains("NOT NULL")) {
131+
columnAnnotations.add(NotNullAnnotation.builder().build().toString());
132+
}
133+
}
134+
});
135+
}
70136
}

src/main/java/org/ngbsn/maven/JPACodeGenMojo.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.ngbsn.maven;
22

33
import lombok.SneakyThrows;
4-
import org.apache.commons.io.FileUtils;
54
import org.apache.maven.plugin.AbstractMojo;
65
import org.apache.maven.plugin.MojoExecutionException;
76
import org.apache.maven.plugin.MojoFailureException;
@@ -11,11 +10,9 @@
1110
import org.slf4j.LoggerFactory;
1211

1312
import java.io.BufferedReader;
14-
import java.io.File;
1513
import java.io.FileInputStream;
1614
import java.io.InputStreamReader;
1715
import java.nio.charset.StandardCharsets;
18-
import java.util.Objects;
1916
import java.util.stream.Collectors;
2017

2118
import static org.ngbsn.generator.JPACodeGenerator.generateCode;
@@ -27,13 +24,13 @@ public class JPACodeGenMojo extends AbstractMojo {
2724
/**
2825
* The path to Schema file.
2926
*/
30-
@Parameter( property = "sqlFilePath")
27+
@Parameter(property = "sqlFilePath")
3128
private String sqlFilePath;
3229

3330
/**
3431
* The package name for generated code
3532
*/
36-
@Parameter( property = "packageName")
33+
@Parameter(property = "packageName")
3734
private String packageName;
3835

3936
@SneakyThrows

src/main/java/org/ngbsn/model/Column.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import lombok.Getter;
44
import lombok.Setter;
5-
import org.ngbsn.model.annotations.Annotation;
65

6+
import java.util.ArrayList;
77
import java.util.List;
88

99
@Setter
@@ -13,9 +13,6 @@ public class Column {
1313
private String name;
1414
private String fieldName;
1515
private String type;
16-
private boolean unique;
17-
private boolean nullable;
1816
private boolean primaryKey;
19-
private boolean foreignKey;
20-
private List<Annotation> annotations;
17+
private List<String> annotations = new ArrayList<>();;
2118
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.ngbsn.model;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
9+
@Setter
10+
@Getter
11+
public class ForeignKeyConstraint {
12+
private List<String> columns = new ArrayList<>();
13+
private List<String> referencedColumns = new ArrayList<>();
14+
private String referencedTableName;
15+
16+
}

src/main/java/org/ngbsn/model/Table.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,8 @@ public class Table {
1414
private String name;
1515
private String className;
1616
private List<Column> columns = new ArrayList<>();
17+
private List<String> annotations = new ArrayList<>();
1718
private int numOfPrimaryKeyColumns;
19+
private List<ForeignKeyConstraint> foreignKeyConstraints = new ArrayList<>();
20+
1821
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.ngbsn.model.annotations.entityAnnotations;
2+
3+
import org.ngbsn.model.annotations.Annotation;
4+
5+
public class EntityAnnotation implements Annotation {
6+
@Override
7+
public String toString() {
8+
return "@Entity";
9+
}
10+
}

0 commit comments

Comments
 (0)