From 31b8c30b000d39bb94ee89d28549c69c7b27a014 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 12:04:31 +0100 Subject: [PATCH 1/4] Fix schema generator when mapped entity contains property placeholder --- .../data/jdbc/config/SchemaGenerator.java | 34 ++++++++++-- .../jdbc/h2/H2DynamicEntityNameSpec.groovy | 52 +++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DynamicEntityNameSpec.groovy 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 2da5d4b836..82f2a4b689 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/H2DynamicEntityNameSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DynamicEntityNameSpec.groovy new file mode 100644 index 0000000000..559df965c4 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DynamicEntityNameSpec.groovy @@ -0,0 +1,52 @@ +package io.micronaut.data.jdbc.h2 + +import io.micronaut.context.annotation.Property +import io.micronaut.data.annotation.GeneratedValue +import io.micronaut.data.annotation.Id +import io.micronaut.data.annotation.MappedEntity +import io.micronaut.data.jdbc.annotation.JdbcRepository +import io.micronaut.data.model.query.builder.sql.Dialect +import io.micronaut.data.repository.CrudRepository +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Shared +import spock.lang.Specification + + +@MicronautTest +@H2DBProperties +@Property(name = "datasources.default.packages", value = "io.micronaut.data.jdbc.h2") +@Property(name = "entity-prefix", value = "simple_robot_") +class H2DynamicEntityNameSpec extends Specification { + + @Inject + @Shared + CustomEntityRepository repository + + void "test cascade save"() { + when: + def entity = repository.save(new CustomEntity(name: "Entity1")) + def opt = repository.findById(entity.id) + then: + opt.present + def loadedEntity = opt.get() + loadedEntity.name == "Entity1" + repository.count() == 1 + !repository.findAll().empty + cleanup: + repository.deleteAll() + } +} + +@MappedEntity("\${entity-prefix}entity") +class CustomEntity { + @Id + @GeneratedValue + Long id + + String name +} + +@JdbcRepository(dialect = Dialect.H2) +interface CustomEntityRepository extends CrudRepository { +} From 9acb4ae808a9938f5a54d12b06be54853c9f708f Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 12:50:20 +0100 Subject: [PATCH 2/4] Fix tests --- .../groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy | 1 + .../io/micronaut/data/jdbc/h2/H2TestPropertyProvider.groovy | 3 ++- .../data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy index 13bf4c630e..2e3c686db0 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy @@ -15,5 +15,6 @@ import java.lang.annotation.RetentionPolicy @Property(name = "datasources.default.url", value = "jdbc:h2:mem:mydb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") @Property(name = "datasources.default.username", value = "") @Property(name = "datasources.default.password", value = "") +@Property(name = "entity-prefix", value = "my_entity_") @interface H2DBProperties { } 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 079b89be2a..35cd411a0b 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,8 @@ trait H2TestPropertyProvider implements TestPropertyProvider { } Map getProperties() { - return shouldAddDefaultDbProperties()? getH2DataSourceProperties("default") : [:] + def properties = shouldAddDefaultDbProperties() ? getH2DataSourceProperties("default") : [:] + return properties + ['entity-prefix': 'my_entity_'] } Map getH2DataSourceProperties(String dataSourceName) { diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy index 079506cf8d..277ebe1e43 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy @@ -24,6 +24,7 @@ import static org.junit.Assert.assertEquals @Property(name = "datasources.default.driver-class-name", value = "org.h2.Driver") @Property(name = "micronaut.multitenancy.tenantresolver.httpheader.enabled", value = StringUtils.TRUE) @Property(name = "spec.name", value = "TenancyBookControllerSpec") +@Property(name = "entity-prefix", value = "my_entity_") @MicronautTest(transactional = false) // <2> class TenancyBookControllerSpec extends Specification { From 60295808cefdc0dd51c14fe0694d232150bf4209 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 13:27:41 +0100 Subject: [PATCH 3/4] Fix tests and move to jdbc-example-java --- .../data/jdbc/h2/H2DBProperties.groovy | 1 - .../jdbc/h2/H2DynamicEntityNameSpec.groovy | 52 ------------------- .../jdbc/h2/H2TestPropertyProvider.groovy | 3 +- .../TenancyBookControllerSpec.groovy | 1 - .../src/main/java/example/CustomEntity.java | 13 +++++ .../java/example/CustomEntityRepository.java | 9 ++++ .../src/main/resources/application.yml | 2 + .../example/CustomEntityRepositorySpec.java | 24 +++++++++ 8 files changed, 49 insertions(+), 56 deletions(-) delete mode 100644 data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DynamicEntityNameSpec.groovy 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/test/groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy index 2e3c686db0..13bf4c630e 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DBProperties.groovy @@ -15,6 +15,5 @@ import java.lang.annotation.RetentionPolicy @Property(name = "datasources.default.url", value = "jdbc:h2:mem:mydb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") @Property(name = "datasources.default.username", value = "") @Property(name = "datasources.default.password", value = "") -@Property(name = "entity-prefix", value = "my_entity_") @interface H2DBProperties { } diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DynamicEntityNameSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DynamicEntityNameSpec.groovy deleted file mode 100644 index 559df965c4..0000000000 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DynamicEntityNameSpec.groovy +++ /dev/null @@ -1,52 +0,0 @@ -package io.micronaut.data.jdbc.h2 - -import io.micronaut.context.annotation.Property -import io.micronaut.data.annotation.GeneratedValue -import io.micronaut.data.annotation.Id -import io.micronaut.data.annotation.MappedEntity -import io.micronaut.data.jdbc.annotation.JdbcRepository -import io.micronaut.data.model.query.builder.sql.Dialect -import io.micronaut.data.repository.CrudRepository -import io.micronaut.test.extensions.spock.annotation.MicronautTest -import jakarta.inject.Inject -import spock.lang.Shared -import spock.lang.Specification - - -@MicronautTest -@H2DBProperties -@Property(name = "datasources.default.packages", value = "io.micronaut.data.jdbc.h2") -@Property(name = "entity-prefix", value = "simple_robot_") -class H2DynamicEntityNameSpec extends Specification { - - @Inject - @Shared - CustomEntityRepository repository - - void "test cascade save"() { - when: - def entity = repository.save(new CustomEntity(name: "Entity1")) - def opt = repository.findById(entity.id) - then: - opt.present - def loadedEntity = opt.get() - loadedEntity.name == "Entity1" - repository.count() == 1 - !repository.findAll().empty - cleanup: - repository.deleteAll() - } -} - -@MappedEntity("\${entity-prefix}entity") -class CustomEntity { - @Id - @GeneratedValue - Long id - - String name -} - -@JdbcRepository(dialect = Dialect.H2) -interface CustomEntityRepository extends CrudRepository { -} 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 35cd411a0b..54d2467b44 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,8 +34,7 @@ trait H2TestPropertyProvider implements TestPropertyProvider { } Map getProperties() { - def properties = shouldAddDefaultDbProperties() ? getH2DataSourceProperties("default") : [:] - return properties + ['entity-prefix': 'my_entity_'] + return shouldAddDefaultDbProperties() ? getH2DataSourceProperties("default") : [:] } Map getH2DataSourceProperties(String dataSourceName) { diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy index 277ebe1e43..079506cf8d 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy @@ -24,7 +24,6 @@ import static org.junit.Assert.assertEquals @Property(name = "datasources.default.driver-class-name", value = "org.h2.Driver") @Property(name = "micronaut.multitenancy.tenantresolver.httpheader.enabled", value = StringUtils.TRUE) @Property(name = "spec.name", value = "TenancyBookControllerSpec") -@Property(name = "entity-prefix", value = "my_entity_") @MicronautTest(transactional = false) // <2> class TenancyBookControllerSpec extends Specification { 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 0000000000..457f82d878 --- /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 0000000000..82a36c74fb --- /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 25a69c4409..a0e33c09e5 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 0000000000..ad08ecc77b --- /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(); + } +} From 3b93ee7847ca6de92b7cac226365585b7cdecc97 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 16:23:32 +0100 Subject: [PATCH 4/4] Try to improve test coverage --- .../data/jdbc/h2/identity/SameIdentityRepositorySpec.groovy | 3 +++ 1 file changed, 3 insertions(+) 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 f36144f751..ee09133b6c 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