Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can spring data r2dbc generate a schema?

I am creating a quick project using R2DBC and H2 to familiarize myself with this new reactive stuff. Made a repository that extends ReactiveCrudRepository and all is well with the world, as long as i use the DatabaseClient to issue a CREATE TABLE statement that matches my entity first...

I understand spring data R2DBC is not as fully featured as spring data JPA (yet?) but is there currently a way to generate the schema from the entity classes?

Thanks

like image 818
Martin Avatar asked Nov 03 '19 20:11

Martin


People also ask

What is Spring data R2DBC?

Spring Data R2DBC applies familiar Spring abstractions and repository support for R2DBC. It makes it easier to build Spring-powered applications that use relational data access technologies in a reactive application stack. Spring Data R2DBC aims at being conceptually easy.

Can we use JPA with R2DBC?

JPA cannot deal with reactive repositories such as provided by Spring Data R2DBC. This means you will have to do more things manually when using R2DBC. There are other reactive drivers around such as for example Quarkus Reactive Postgres client (which uses Vert.

Is R2DBC production ready?

Currently there are a few drivers ready for production, check the R2dbc drivers page for the complete list. H2 database is frequently used in development environment, add the following dependency when using either embedded or file-based H2 database.

What is spring boot schema?

Spring Boot can automatically create the schema (DDL scripts) of your DataSource and initialize it (DML scripts). It loads SQL from the standard root classpath locations: schema. sql and data. sql , respectively. In addition, Spring Boot processes the schema-${platform}.


3 Answers

No, there is currently no way to generate schema from entities with Spring Data R2DBC.

I'm using it in a project with Postgres DB and it's complicated to manage database migrations, but I managed to wire in Flyway with synchronous Postgre driver (Flyway doesn't work with reactive drivers yet) at startup to handle schema migrations.

Even though you still have to write your own CREATE TABLE statements which shouldn't be that hard and you could even modify your entities in some simple project to create JPA entities and let Hibernate create schema then copy-paste it into a migration file in your R2DBC project.

like image 148
NikolaB Avatar answered Oct 22 '22 22:10

NikolaB


It is possible for tests and for production.

I production make sure your user has no access to change schema otherwise you may delete tables by mistake!!! or use a migration tool like flyway.

You need to put your schema.sql in the main resources and add the relevant properties

spring.r2dbc.initialization-mode=always

h2 for test and postgres for prod

I use gradle and the versions of driver are:

    implementation 'org.springframework.boot.experimental:spring-boot-actuator-autoconfigure-r2dbc'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'io.r2dbc:r2dbc-h2'
    runtimeOnly 'io.r2dbc:r2dbc-postgresql'
    runtimeOnly 'org.postgresql:postgresql'
    testImplementation 'org.springframework.boot.experimental:spring-boot-test-autoconfigure-r2dbc'

The BOM version is

dependencyManagement {
    imports {
        mavenBom 'org.springframework.boot.experimental:spring-boot-bom-r2dbc:0.1.0.M3'
    }
}
like image 3
Gadi Avatar answered Oct 22 '22 22:10

Gadi


That's how I solved this problem:

Controller:

    @PostMapping(MAP + PATH_DDL_PROC_DB)  //PATH_DDL_PROC_DB = "/database/{db}/{schema}/{table}"
    public Flux<Object> createDbByDb(
            @PathVariable("db") String db,
            @PathVariable("schema") String schema,
            @PathVariable("table") String table) {
        return ddlProcService.createDbByDb(db,schema,table);

Service:

    public Flux<Object> createDbByDb(String db,String schema,String table) {
        return ddl.createDbByDb(db,schema,table);
    }

Repository:

    @Autowired
    PostgresqlConnectionConfiguration.Builder connConfig;

    public Flux<Object> createDbByDb(String db,String schema,String table) {
        return createDb(db).thenMany(
                Mono.from(connFactory(connConfig.database(db)).create())
                    .flatMapMany(
                            connection ->
                                    Flux.from(connection
                                                      .createBatch()
                                                      .add(sqlCreateSchema(db))
                                                      .add(sqlCreateTable(db,table))
                                                      .add(sqlPopulateTable(db,table))
                                                      .execute()
                                             )));
    }

    private Mono<Void> createDb(String db) {

        PostgresqlConnectionFactory
                connectionFactory = connFactory(connConfig);

        DatabaseClient ddl = DatabaseClient.create(connectionFactory);

        return ddl
                .execute(sqlCreateDb(db))
                .then();
    }

Connection Class:

@Slf4j
@Configuration
@EnableR2dbcRepositories
public class Connection extends AbstractR2dbcConfiguration {

    /*
     **********************************************
     * Spring Data JDBC:
     *      DDL: does not support JPA.
     *
     * R2DBC
     *      DDL:
     *          -does no support JPA
     *          -To achieve DDL, uses R2dbc.DataBaseClient
     *
     *      DML:
     *          -it uses R2dbcREpositories
     *          -R2dbcRepositories is different than
     *          R2dbc.DataBaseClient
     * ********************************************
     */
    @Bean
    public PostgresqlConnectionConfiguration.Builder connectionConfig() {
        return PostgresqlConnectionConfiguration
                .builder()
                .host("db-r2dbc")
                .port(5432)
                .username("root")
                .password("root");
    }

    @Bean
    public PostgresqlConnectionFactory connectionFactory() {
        return
                new PostgresqlConnectionFactory(
                        connectionConfig().build()
                );
    }
}

DDL Scripts:

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DDLScripts {

    public static final String SQL_GET_TASK = "select * from tasks";

    public static String sqlCreateDb(String db) {
        String sql = "create database %1$s;";
        String[] sql1OrderedParams = quotify(new String[]{db});
        String finalSql = format(sql,(Object[]) sql1OrderedParams);
        return finalSql;
    }

    public static String sqlCreateSchema(String schema) {
        String sql = "create schema if not exists %1$s;";
        String[] sql1OrderedParams = quotify(new String[]{schema});
        return format(sql,(Object[])  sql1OrderedParams);
    }

    public static String sqlCreateTable(String schema,String table) {

        String sql1 = "create table %1$s.%2$s " +
                "(id serial not null constraint tasks_pk primary key, " +
                "lastname varchar not null); ";
        String[] sql1OrderedParams = quotify(new String[]{schema,table});
        String sql1Final = format(sql1,(Object[])  sql1OrderedParams);

        String sql2 = "alter table %1$s.%2$s owner to root; ";
        String[] sql2OrderedParams = quotify(new String[]{schema,table});
        String sql2Final = format(sql2,(Object[])  sql2OrderedParams);

        return sql1Final + sql2Final;
    }

    public static String sqlPopulateTable(String schema,String table) {

        String sql = "insert into %1$s.%2$s values (1, 'schema-table-%3$s');";
        String[] sql1OrderedParams = quotify(new String[]{schema,table,schema});
        return format(sql,(Object[]) sql1OrderedParams);
    }

    private static String[] quotify(String[] stringArray) {

        String[] returnArray = new String[stringArray.length];

        for (int i = 0; i < stringArray.length; i++) {
            returnArray[i] = "\"" + stringArray[i] + "\"";
        }
        return returnArray;
    }
}
like image 1
GtdDev Avatar answered Oct 23 '22 00:10

GtdDev