Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement OneToMany, ManyToOne and ManyToMany with R2DBC in a project which previously used JPA?

I have to re-implement a few back-end services and one of the main requirement is to make the whole flow reactive. Previously the services used hibernate with PostgreSQL so the mentioned connections were provided by the framework.

Since I have to keep the original DB and just change the service implementation I have to use r2dbc-postgresql. I couldn't find any resource about this topic, but my best guess is to do something similar what I would do with JDBC and introduce some new connection tables between my entities.

  1. Is this would be a correct approach or should I consider some different solutions?
  2. What would be the steps to achieve the mentioned connections?
like image 282
gabtotal Avatar asked Jan 30 '20 12:01

gabtotal


People also ask

Can we use JPA with R2DBC?

Yes, this should work. You could potentialy use R2DBC for all operations and use a library (if available) to connect to the database log (e.g. mysql-binlog-connector-java) in a separate process which will catch database changes and then you persist them in your audit tables.

Is R2DBC a ORM?

Spring Data R2dbc can be considered as a light-weight ORM framework.

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 the difference between many to one and one to many?

The difference between One-to-many , Many-to-one and Many-to-Many is: One-to-many vs Many-to-one is a matter of perspective. Unidirectional vs Bidirectional will not affect the mapping but will make difference on how you can access your data. In Many-to-one the many side will keep reference of the one side.


1 Answers

I am looking into something similar and have come to the same conclusion (1), since there is no support for relations in R2DBC. To migrate a one-to-many relation I first made the collection containing the "many" entities to @Transient in the "one" entity. Persisting the "one" entity is accomplished using the following steps in a reactive sequence:

  1. Persist the "many" entities. This in order to be able to update these in the "one" entity so that the "many" entities are assigned ids.
  2. Persist the "one" entity.
  3. Persist the relationships. I can do this at this stage since I now have the ids of all involved entities. For the relationships I have introduced a helper entity, something along the lines of OneManyRelation and a corresponding repository.

In code it looks like this:

public <S extends Drawing> Mono<S> save(final S inDrawing) {
    final List<Shape> theDrawingShapes = inDrawing.getShapes();

    return Mono.defer(() -> {
        return Flux.fromIterable(theDrawingShapes)
            .log()
            .flatMap(theDrawingShape -> {
                /* Save the shapes contained in the drawing. */
                if (theDrawingShape instanceof Circle) {
                    final Circle theUnsavedCircle = (Circle) theDrawingShape;
                    return mCircleRepository.save(theUnsavedCircle);
                } else if (theDrawingShape instanceof Rectangle) {
                    final Rectangle theUnsavedRectangle = (Rectangle) theDrawingShape;
                    return mRectangleRepository.save(theUnsavedRectangle);
                } else {
                    LOGGER.warn("Unrecognized entity type: {}",
                        theDrawingShape.getClass().getName());
                    return Mono.just(theDrawingShape);
                }
            })
            /* Update the drawing, setting the shapes of the drawing to the saved shapes. */
            .collectList()
            .map(theSavedShapesList -> {
                inDrawing.setShapes(new ArrayList<>(theSavedShapesList));
                return inDrawing;
            })
            /* Save the drawing itself. */
            .flatMap(theDrawing -> super.save(theDrawing))
            .flatMap(theDrawing -> {
                /* Save the relations between the drawing and the shapes of the drawing. */
                return Flux.fromIterable(theDrawing.getShapes())
                    .flatMap(theDrawingShape -> {
                        final var theDrawingShapeRelation = new DrawingShapesRelation();
                        theDrawingShapeRelation.setDrawingId(theDrawing.getId());
                        theDrawingShapeRelation.setShapeId(theDrawingShape.getId());
                        theDrawingShapeRelation.setShapeType(theDrawingShape.getClass()
                            .getName());
                        return mDrawingShapesRelationRepository.save(theDrawingShapeRelation);
                    })
                    .collectList()
                    .map(theDrawingShapesRelationList -> theDrawing);
            });
    });
}

My conclusion this far is that unless you are certain that there are major gains to be made from switching to R2DBC, I would settle for using Spring Data JPA and executing calls to repositories in a separate thread using subscribeOn.
Good luck and happy coding!

like image 83
Ivan Krizsan Avatar answered Sep 28 '22 10:09

Ivan Krizsan