From fa9d9f7381cdd690e9fd99dcf2c60a67a11537a5 Mon Sep 17 00:00:00 2001 From: Radovan Radic Date: Mon, 25 Nov 2024 09:33:34 +0100 Subject: [PATCH] Fix schema generator when mapped entity contains property placeholder (#3237) * Fix schema generator when mapped entity contains property placeholder * Fix tests * Fix tests and move to jdbc-example-java * Try to improve test coverage --- .../data/jdbc/config/SchemaGenerator.java | 34 ++++++++++++++++--- .../jdbc/h2/H2TestPropertyProvider.groovy | 2 +- .../SameIdentityRepositorySpec.groovy | 3 ++ .../src/main/java/example/CustomEntity.java | 13 +++++++ .../java/example/CustomEntityRepository.java | 9 +++++ .../src/main/resources/application.yml | 2 ++ .../example/CustomEntityRepositorySpec.java | 24 +++++++++++++ 7 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 doc-examples/jdbc-example-java/src/main/java/example/CustomEntity.java create mode 100644 doc-examples/jdbc-example-java/src/main/java/example/CustomEntityRepository.java create mode 100644 doc-examples/jdbc-example-java/src/test/java/example/CustomEntityRepositorySpec.java diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java index 2da5d4b8369..82f2a4b6890 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java @@ -17,6 +17,8 @@ import io.micronaut.context.BeanLocator; import io.micronaut.context.annotation.Context; +import io.micronaut.context.env.Environment; +import io.micronaut.context.env.PropertyPlaceholderResolver; import io.micronaut.context.exceptions.ConfigurationException; import io.micronaut.context.exceptions.NoSuchBeanException; import io.micronaut.core.annotation.Internal; @@ -61,16 +63,21 @@ public class SchemaGenerator { private final List configurations; private final JdbcSchemaHandler schemaHandler; + private final PropertyPlaceholderResolver propertyPlaceholderResolver; /** * Constructors a schema generator for the given configurations. * * @param configurations The configurations * @param schemaHandler The schema handler + * @param environment The environment */ - public SchemaGenerator(List configurations, JdbcSchemaHandler schemaHandler) { + public SchemaGenerator(List configurations, + JdbcSchemaHandler schemaHandler, + Environment environment) { this.configurations = configurations == null ? Collections.emptyList() : configurations; this.schemaHandler = schemaHandler; + this.propertyPlaceholderResolver = environment.getPlaceholderResolver(); } /** @@ -115,14 +122,14 @@ public void createSchema(BeanLocator beanLocator) { for (String schemaName : configuration.getSchemaGenerateNames()) { schemaHandler.createSchema(connection, dialect, schemaName); schemaHandler.useSchema(connection, dialect, schemaName); - generate(connection, configuration, entities); + generate(connection, configuration, propertyPlaceholderResolver, entities); } } else { if (configuration.getSchemaGenerateName() != null) { schemaHandler.createSchema(connection, dialect, configuration.getSchemaGenerateName()); schemaHandler.useSchema(connection, dialect, configuration.getSchemaGenerateName()); } - generate(connection, configuration, entities); + generate(connection, configuration, propertyPlaceholderResolver, entities); } } catch (SQLException e) { throw new DataAccessException("Unable to create database schema: " + e.getMessage(), e); @@ -136,6 +143,7 @@ public void createSchema(BeanLocator beanLocator) { private static void generate(Connection connection, DataJdbcConfiguration configuration, + PropertyPlaceholderResolver propertyPlaceholderResolver, PersistentEntity[] entities) throws SQLException { Dialect dialect = configuration.getDialect(); SqlQueryBuilder2 builder = new SqlQueryBuilder2(dialect); @@ -143,7 +151,7 @@ private static void generate(Connection connection, switch (configuration.getSchemaGenerate()) { case CREATE_DROP: try { - String sql = builder.buildBatchDropTableStatement(entities); + String sql = resolveSql(propertyPlaceholderResolver, builder.buildBatchDropTableStatement(entities)); if (DataSettings.QUERY_LOG.isDebugEnabled()) { DataSettings.QUERY_LOG.debug("Dropping Tables: \n{}", sql); } @@ -156,7 +164,7 @@ private static void generate(Connection connection, } } case CREATE: - String sql = builder.buildBatchCreateTableStatement(entities); + String sql = resolveSql(propertyPlaceholderResolver, builder.buildBatchCreateTableStatement(entities)); if (DataSettings.QUERY_LOG.isDebugEnabled()) { DataSettings.QUERY_LOG.debug("Creating Tables: \n{}", sql); } @@ -174,6 +182,7 @@ private static void generate(Connection connection, try { String[] statements = builder.buildDropTableStatements(entity); for (String sql : statements) { + sql = resolveSql(propertyPlaceholderResolver, sql); if (DataSettings.QUERY_LOG.isDebugEnabled()) { DataSettings.QUERY_LOG.debug("Dropping Table: \n{}", sql); } @@ -192,6 +201,7 @@ private static void generate(Connection connection, String[] sql = builder.buildCreateTableStatements(entity); for (String stmt : sql) { + stmt = resolveSql(propertyPlaceholderResolver, stmt); if (DataSettings.QUERY_LOG.isDebugEnabled()) { DataSettings.QUERY_LOG.debug("Executing CREATE statement: \n{}", stmt); } @@ -215,4 +225,18 @@ private static void generate(Connection connection, } } } + + /** + * Resolves property placeholder values if there are any. + * + * @param propertyPlaceholderResolver The property placeholder resolver + * @param sql The SQL to resolve placeholder properties if there are any + * @return The resulting SQL with resolved properties if there were any + */ + private static String resolveSql(PropertyPlaceholderResolver propertyPlaceholderResolver, String sql) { + if (sql.contains(propertyPlaceholderResolver.getPrefix())) { + return propertyPlaceholderResolver.resolveRequiredPlaceholders(sql); + } + return sql; + } } diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2TestPropertyProvider.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2TestPropertyProvider.groovy index 079b89be2a8..54d2467b44b 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2TestPropertyProvider.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2TestPropertyProvider.groovy @@ -34,7 +34,7 @@ trait H2TestPropertyProvider implements TestPropertyProvider { } Map getProperties() { - return shouldAddDefaultDbProperties()? getH2DataSourceProperties("default") : [:] + return shouldAddDefaultDbProperties() ? getH2DataSourceProperties("default") : [:] } Map getH2DataSourceProperties(String dataSourceName) { diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/identity/SameIdentityRepositorySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/identity/SameIdentityRepositorySpec.groovy index f36144f751d..ee09133b6c5 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/identity/SameIdentityRepositorySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/identity/SameIdentityRepositorySpec.groovy @@ -1,11 +1,14 @@ package io.micronaut.data.jdbc.h2.identity +import io.micronaut.context.annotation.Property import io.micronaut.data.jdbc.h2.H2DBProperties import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import spock.lang.Specification @H2DBProperties +@Property(name = "datasources.default.packages", value = "io.micronaut.data.jdbc.h2.identity") +@Property(name = "datasources.default.batch-generate", value = "true") @MicronautTest class SameIdentityRepositorySpec extends Specification { @Inject diff --git a/doc-examples/jdbc-example-java/src/main/java/example/CustomEntity.java b/doc-examples/jdbc-example-java/src/main/java/example/CustomEntity.java new file mode 100644 index 00000000000..457f82d8789 --- /dev/null +++ b/doc-examples/jdbc-example-java/src/main/java/example/CustomEntity.java @@ -0,0 +1,13 @@ +package example; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; + +@MappedEntity("${entity.prefix}entity") +public record CustomEntity( + @Id + @GeneratedValue + Long id, + String name) { +} diff --git a/doc-examples/jdbc-example-java/src/main/java/example/CustomEntityRepository.java b/doc-examples/jdbc-example-java/src/main/java/example/CustomEntityRepository.java new file mode 100644 index 00000000000..82a36c74fb2 --- /dev/null +++ b/doc-examples/jdbc-example-java/src/main/java/example/CustomEntityRepository.java @@ -0,0 +1,9 @@ +package example; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +@JdbcRepository(dialect = Dialect.H2) +public interface CustomEntityRepository extends CrudRepository { +} diff --git a/doc-examples/jdbc-example-java/src/main/resources/application.yml b/doc-examples/jdbc-example-java/src/main/resources/application.yml index 25a69c44097..a0e33c09e55 100644 --- a/doc-examples/jdbc-example-java/src/main/resources/application.yml +++ b/doc-examples/jdbc-example-java/src/main/resources/application.yml @@ -6,3 +6,5 @@ datasources: password: '' schema-generate: CREATE_DROP dialect: H2 +entity: + prefix: demo_ diff --git a/doc-examples/jdbc-example-java/src/test/java/example/CustomEntityRepositorySpec.java b/doc-examples/jdbc-example-java/src/test/java/example/CustomEntityRepositorySpec.java new file mode 100644 index 00000000000..ad08ecc77b4 --- /dev/null +++ b/doc-examples/jdbc-example-java/src/test/java/example/CustomEntityRepositorySpec.java @@ -0,0 +1,24 @@ +package example; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@MicronautTest +class CustomEntityRepositorySpec { + + @Inject + CustomEntityRepository repository; + + @Test + void testSaveAndFind() { + CustomEntity entity = repository.save(new CustomEntity(null, "Entity1")); + CustomEntity found = repository.findById(entity.id()).orElse(null); + Assertions.assertNotNull(found); + Assertions.assertEquals(entity.name(), found.name()); + Assertions.assertEquals(1, repository.count()); + Assertions.assertFalse(repository.findAll().isEmpty()); + repository.deleteAll(); + } +}