Skip to content

Commit 9136d86

Browse files
kobaeugeneaschauder
authored andcommitted
Add support for foreign keys in schema generation within aggregates.
Closes #1599 See #756, #1600 Original pull request #1629
1 parent 8327810 commit 9136d86

File tree

13 files changed

+686
-37
lines changed

13 files changed

+686
-37
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/DefaultSqlTypeMapping.java

+5
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,9 @@ public DefaultSqlTypeMapping() {
6363
public String getColumnType(RelationalPersistentProperty property) {
6464
return typeMap.get(ClassUtils.resolvePrimitiveIfNecessary(property.getActualType()));
6565
}
66+
67+
@Override
68+
public String getColumnTypeByClass(Class clazz) {
69+
return typeMap.get(clazz);
70+
}
6671
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.springframework.data.jdbc.core.mapping.schema;
2+
3+
import java.util.List;
4+
import java.util.Objects;
5+
6+
/**
7+
* Models a Foreign Key for generating SQL for Schema generation.
8+
*
9+
* @author Evgenii Koba
10+
* @since 3.2
11+
*/
12+
record ForeignKey(String name, String tableName, List<String> columnNames, String referencedTableName,
13+
List<String> referencedColumnNames) {
14+
@Override
15+
public boolean equals(Object o) {
16+
if (this == o)
17+
return true;
18+
if (o == null || getClass() != o.getClass())
19+
return false;
20+
ForeignKey that = (ForeignKey) o;
21+
return Objects.equals(tableName, that.tableName) && Objects.equals(columnNames, that.columnNames) && Objects.equals(
22+
referencedTableName, that.referencedTableName) && Objects.equals(referencedColumnNames,
23+
that.referencedColumnNames);
24+
}
25+
26+
@Override
27+
public int hashCode() {
28+
return Objects.hash(tableName, columnNames, referencedTableName, referencedColumnNames);
29+
}
30+
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriter.java

+63-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import liquibase.change.ColumnConfig;
2222
import liquibase.change.ConstraintsConfig;
2323
import liquibase.change.core.AddColumnChange;
24+
import liquibase.change.core.AddForeignKeyConstraintChange;
2425
import liquibase.change.core.CreateTableChange;
2526
import liquibase.change.core.DropColumnChange;
27+
import liquibase.change.core.DropForeignKeyConstraintChange;
2628
import liquibase.change.core.DropTableChange;
2729
import liquibase.changelog.ChangeLogChild;
2830
import liquibase.changelog.ChangeLogParameters;
@@ -52,6 +54,7 @@
5254
import java.util.Set;
5355
import java.util.function.BiPredicate;
5456
import java.util.function.Predicate;
57+
import java.util.stream.Collectors;
5558

5659
import org.springframework.core.io.Resource;
5760
import org.springframework.data.mapping.context.MappingContext;
@@ -321,15 +324,15 @@ private ChangeSet createChangeSet(ChangeSetMetadata metadata, SchemaDiff differe
321324
private SchemaDiff initial() {
322325

323326
Tables mappedEntities = Tables.from(mappingContext.getPersistentEntities().stream().filter(schemaFilter),
324-
sqlTypeMapping, null);
327+
sqlTypeMapping, null, mappingContext);
325328
return SchemaDiff.diff(mappedEntities, Tables.empty(), nameComparator);
326329
}
327330

328331
private SchemaDiff differenceOf(Database database) throws LiquibaseException {
329332

330333
Tables existingTables = getLiquibaseModel(database);
331334
Tables mappedEntities = Tables.from(mappingContext.getPersistentEntities().stream().filter(schemaFilter),
332-
sqlTypeMapping, database.getDefaultCatalogName());
335+
sqlTypeMapping, database.getDefaultCatalogName(), mappingContext);
333336

334337
return SchemaDiff.diff(mappedEntities, existingTables, nameComparator);
335338
}
@@ -362,6 +365,13 @@ private DatabaseChangeLog getDatabaseChangeLog(File changeLogFile, @Nullable Dat
362365

363366
private void generateTableAdditionsDeletions(ChangeSet changeSet, SchemaDiff difference) {
364367

368+
for (Table table : difference.tableDeletions()) {
369+
for (ForeignKey foreignKey : table.foreignKeys()) {
370+
DropForeignKeyConstraintChange dropForeignKey = dropForeignKey(foreignKey);
371+
changeSet.addChange(dropForeignKey);
372+
}
373+
}
374+
365375
for (Table table : difference.tableAdditions()) {
366376
CreateTableChange newTable = changeTable(table);
367377
changeSet.addChange(newTable);
@@ -373,12 +383,24 @@ private void generateTableAdditionsDeletions(ChangeSet changeSet, SchemaDiff dif
373383
changeSet.addChange(dropTable(table));
374384
}
375385
}
386+
387+
for (Table table : difference.tableAdditions()) {
388+
for (ForeignKey foreignKey : table.foreignKeys()) {
389+
AddForeignKeyConstraintChange addForeignKey = addForeignKey(foreignKey);
390+
changeSet.addChange(addForeignKey);
391+
}
392+
}
376393
}
377394

378395
private void generateTableModifications(ChangeSet changeSet, SchemaDiff difference) {
379396

380397
for (TableDiff table : difference.tableDiffs()) {
381398

399+
for (ForeignKey foreignKey : table.fkToDrop()) {
400+
DropForeignKeyConstraintChange dropForeignKey = dropForeignKey(foreignKey);
401+
changeSet.addChange(dropForeignKey);
402+
}
403+
382404
if (!table.columnsToAdd().isEmpty()) {
383405
changeSet.addChange(addColumns(table));
384406
}
@@ -388,6 +410,11 @@ private void generateTableModifications(ChangeSet changeSet, SchemaDiff differen
388410
if (!deletedColumns.isEmpty()) {
389411
changeSet.addChange(dropColumns(table, deletedColumns));
390412
}
413+
414+
for (ForeignKey foreignKey : table.fkToAdd()) {
415+
AddForeignKeyConstraintChange addForeignKey = addForeignKey(foreignKey);
416+
changeSet.addChange(addForeignKey);
417+
}
391418
}
392419
}
393420

@@ -444,12 +471,27 @@ private Tables getLiquibaseModel(Database targetDatabase) throws LiquibaseExcept
444471
tableModel.columns().add(columnModel);
445472
}
446473

474+
tableModel.foreignKeys().addAll(extractForeignKeys(table));
475+
447476
existingTables.add(tableModel);
448477
}
449478

450479
return new Tables(existingTables);
451480
}
452481

482+
private static List<ForeignKey> extractForeignKeys(liquibase.structure.core.Table table) {
483+
484+
return table.getOutgoingForeignKeys().stream().map(foreignKey -> {
485+
String tableName = foreignKey.getForeignKeyTable().getName();
486+
List<String> columnNames = foreignKey.getForeignKeyColumns().stream()
487+
.map(liquibase.structure.core.Column::getName).toList();
488+
String referencedTableName = foreignKey.getPrimaryKeyTable().getName();
489+
List<String> referencedColumnNames = foreignKey.getPrimaryKeyColumns().stream()
490+
.map(liquibase.structure.core.Column::getName).toList();
491+
return new ForeignKey(foreignKey.getName(), tableName, columnNames, referencedTableName, referencedColumnNames);
492+
}).collect(Collectors.toList());
493+
}
494+
453495
private static AddColumnChange addColumns(TableDiff table) {
454496

455497
AddColumnChange addColumnChange = new AddColumnChange();
@@ -532,6 +574,25 @@ private static DropTableChange dropTable(Table table) {
532574
return change;
533575
}
534576

577+
private static AddForeignKeyConstraintChange addForeignKey(ForeignKey foreignKey) {
578+
579+
AddForeignKeyConstraintChange change = new AddForeignKeyConstraintChange();
580+
change.setConstraintName(foreignKey.name());
581+
change.setBaseTableName(foreignKey.tableName());
582+
change.setBaseColumnNames(String.join(",", foreignKey.columnNames()));
583+
change.setReferencedTableName(foreignKey.referencedTableName());
584+
change.setReferencedColumnNames(String.join(",", foreignKey.referencedColumnNames()));
585+
return change;
586+
}
587+
588+
private static DropForeignKeyConstraintChange dropForeignKey(ForeignKey foreignKey) {
589+
590+
DropForeignKeyConstraintChange change = new DropForeignKeyConstraintChange();
591+
change.setConstraintName(foreignKey.name());
592+
change.setBaseTableName(foreignKey.tableName());
593+
return change;
594+
}
595+
535596
/**
536597
* Metadata for a ChangeSet.
537598
*/

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SchemaDiff.java

+22-24
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jdbc.core.mapping.schema;
1717

1818
import java.util.ArrayList;
19+
import java.util.Collection;
1920
import java.util.Comparator;
2021
import java.util.List;
2122
import java.util.Map;
@@ -91,43 +92,40 @@ private static List<TableDiff> diffTable(Tables mappedEntities, Map<String, Tabl
9192
TableDiff tableDiff = new TableDiff(mappedEntity);
9293

9394
Map<String, Column> mappedColumns = createMapping(mappedEntity.columns(), Column::name, nameComparator);
94-
mappedEntity.keyColumns().forEach(it -> mappedColumns.put(it.name(), it));
95-
9695
Map<String, Column> existingColumns = createMapping(existingTable.columns(), Column::name, nameComparator);
97-
existingTable.keyColumns().forEach(it -> existingColumns.put(it.name(), it));
98-
9996
// Identify deleted columns
100-
Map<String, Column> toDelete = new TreeMap<>(nameComparator);
101-
toDelete.putAll(existingColumns);
102-
mappedColumns.keySet().forEach(toDelete::remove);
103-
104-
tableDiff.columnsToDrop().addAll(toDelete.values());
105-
106-
// Identify added columns
107-
Map<String, Column> addedColumns = new TreeMap<>(nameComparator);
108-
addedColumns.putAll(mappedColumns);
109-
110-
existingColumns.keySet().forEach(addedColumns::remove);
111-
112-
// Add columns in order. This order can interleave with existing columns.
113-
for (Column column : mappedEntity.keyColumns()) {
114-
if (addedColumns.containsKey(column.name())) {
115-
tableDiff.columnsToAdd().add(column);
116-
}
117-
}
118-
97+
tableDiff.columnsToDrop().addAll(findDiffs(mappedColumns, existingColumns, nameComparator));
98+
// Identify added columns and add columns in order. This order can interleave with existing columns.
99+
Collection<Column> addedColumns = findDiffs(existingColumns, mappedColumns, nameComparator);
119100
for (Column column : mappedEntity.columns()) {
120-
if (addedColumns.containsKey(column.name())) {
101+
if (addedColumns.contains(column)) {
121102
tableDiff.columnsToAdd().add(column);
122103
}
123104
}
124105

106+
Map<String, ForeignKey> mappedForeignKeys = createMapping(mappedEntity.foreignKeys(), ForeignKey::name,
107+
nameComparator);
108+
Map<String, ForeignKey> existingForeignKeys = createMapping(existingTable.foreignKeys(), ForeignKey::name,
109+
nameComparator);
110+
// Identify deleted foreign keys
111+
tableDiff.fkToDrop().addAll(findDiffs(mappedForeignKeys, existingForeignKeys, nameComparator));
112+
// Identify added foreign keys
113+
tableDiff.fkToAdd().addAll(findDiffs(existingForeignKeys, mappedForeignKeys, nameComparator));
114+
125115
tableDiffs.add(tableDiff);
126116
}
127117

128118
return tableDiffs;
129119
}
130120

121+
private static <T> Collection<T> findDiffs(Map<String, T> baseMapping, Map<String, T> toCompareMapping,
122+
Comparator<String> nameComparator) {
123+
Map<String, T> diff = new TreeMap<>(nameComparator);
124+
diff.putAll(toCompareMapping);
125+
baseMapping.keySet().forEach(diff::remove);
126+
return diff.values();
127+
}
128+
131129
private static <T> SortedMap<String, T> createMapping(List<T> items, Function<T, String> keyFunction,
132130
Comparator<String> nameComparator) {
133131

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SqlTypeMapping.java

+12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ public interface SqlTypeMapping {
4040
@Nullable
4141
String getColumnType(RelationalPersistentProperty property);
4242

43+
/**
44+
* Determines a column type for Class.
45+
*
46+
* @param clazz class for which the type should be determined.
47+
* @return the SQL type to use, such as {@code VARCHAR} or {@code NUMERIC}. Can be {@literal null} if the strategy
48+
* cannot provide a column type.
49+
*/
50+
@Nullable
51+
default String getColumnTypeByClass(Class clazz) {
52+
return null;
53+
}
54+
4355
/**
4456
* Returns the required column type for a persistent property or throws {@link IllegalArgumentException} if the type
4557
* cannot be determined.

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Table.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.ArrayList;
1919
import java.util.List;
2020

21+
import java.util.stream.Collectors;
2122
import org.springframework.lang.Nullable;
2223
import org.springframework.util.ObjectUtils;
2324

@@ -27,7 +28,7 @@
2728
* @author Kurt Niemi
2829
* @since 3.2
2930
*/
30-
record Table(@Nullable String schema, String name, List<Column> keyColumns, List<Column> columns) {
31+
record Table(@Nullable String schema, String name, List<Column> columns, List<ForeignKey> foreignKeys) {
3132

3233
public Table(@Nullable String schema, String name) {
3334
this(schema, name, new ArrayList<>(), new ArrayList<>());
@@ -37,6 +38,10 @@ public Table(String name) {
3738
this(null, name);
3839
}
3940

41+
public List<Column> getIdColumns() {
42+
return columns().stream().filter(Column::identity).collect(Collectors.toList());
43+
}
44+
4045
@Override
4146
public boolean equals(Object o) {
4247

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/TableDiff.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525
* @author Kurt Niemi
2626
* @since 3.2
2727
*/
28-
record TableDiff(Table table, List<Column> columnsToAdd, List<Column> columnsToDrop) {
28+
record TableDiff(Table table, List<Column> columnsToAdd, List<Column> columnsToDrop, List<ForeignKey> fkToAdd,
29+
List<ForeignKey> fkToDrop) {
2930

3031
public TableDiff(Table table) {
31-
this(table, new ArrayList<>(), new ArrayList<>());
32+
this(table, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
3233
}
3334

3435
}

0 commit comments

Comments
 (0)