'ORM framework for reactive applications

I have been looking at spring-data-r2dbc for reactive DB access. But it seems it doesn't provide any ORM suppport, as stated on the project's page, it is not an ORM framework. Which options there are exist or planned to support ORM in reactive applications? As for today what is the best way to specify column name for a field (@Column in JPA), use class hierarchies (@MappedSuperclass in JPA) and, most importantly, joins when using spring-data-r2dbc?



Solution 1:[1]

You are correct that spring-data-r2dbc is not an ORM. It does not map the relationships in your database. What it can do, is map your database rows to objects though. The following sample snippet gets mapped to the database row below:

Example class in Kotlin:

@Table("song")
class SongRow(
    @Id val id: Long, 
    val name: String, 
    val artist: Long
)

Row:

create table song(
    id integer identity primary key,
    artist integer references artist(id),
    name varchar(100) NOT NULL,
);

For the way the columns get automaticly mapped to your model see: https://docs.spring.io/spring-data/r2dbc/docs/1.0.0.M1/reference/html/#mapping-usage

Also stated in the link above, is that you can use @Column.

As for one of your questions:

Which options there are exist or planned to support ORM in reactive applications? I have no idea

As for:

joins when using spring-data-r2dbc

Using the @Query annotation you can perform joins. If you want to actually map the data that is obtained with joins, you may be better off using the DatabseClient (See https://docs.spring.io/spring-data/r2dbc/docs/1.0.0.M1/reference/html/#r2dbc.datbaseclient)

Solution 2:[2]

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

The mapping rule can be defined in an entity class.

@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(value = "posts")
public class Post {

    @Id
    @Column("id")
    private UUID id;

    @Column("title")
    private String title;

    @Column("content")
    private String content;

    @JsonSerialize(using = PgJsonObjectSerializer.class)
    @JsonDeserialize(using = PgJsonObjectDeserializer.class)
    @Column("metadata")
    private Json metadata;

    @Column("status")
    private Status status;

    @Column("created_at")
    @CreatedDate
    private LocalDateTime createdAt;

    @Column("updated_at")
    @LastModifiedDate
    private LocalDateTime updatedAt;

    @Column("version")
    @Version
    private Long version;

    enum Status {
        DRAFT, PENDING_MODERATION, PUBLISHED;
    }

}

The @Column and @Table are from spring-data-relational project, which is shared between spring-data-r2dbc and spring-data-jdbc.

The Id is from spring-data-commons.

Spring Data R2dbc provides R2dbcEntityTemplate(similar to JdbcTemplate) and R2dbcRepository(a specific Repository for R2dbc). And Spring 5.3, the refactored DatabaseClient is provided.

Check my updated example for R2dbc in Spring 5.3 and Spring Data R2dbc.

But unfortunately, currently, Spring data r2dbc does not support inheritance and protection.

If you are stick on JPA/Hibernate, check the Hibernate Rx, the reactive implementation of Hibernate.

UPDATE: After tried Micronaut Data, I think Micronaut Data R2dbc is the powerful ORM solution for Reactive and RDBMS. It supports associations support as JPA, and also support JPA annotations directly, for type safe query, it support a variant of JPA Specification, see my example projects written in Micronaut Data R2dbc and Kotlin Coroutines.

Solution 3:[3]

May be lc-spring-data-r2dbc can do the job, but you will not find all the features of JPA. It may help while waiting for Hibernate Reactive to be released and integrated in Spring Data.

For example you can declare links like that:

@Table
public class TableWithForeignKey {
  ...
  @ForeignKey(optional = false)
  private LinkedTable myLink;
  ...
}

@Table
public class LinkedTable {
  ...
  @ForeignTable(joinkey = "myLink")
  private List<TableWithForeignKey> links;
  ...
}

When you want the links to be mapped, you can use either lazy loading or select with join.

If you use the methods findBy... (findById, findAll...) of the Spring repository, only the table of the repository will be loaded. In this case, you can use lazy loading. For that you need to declare a methods, with default body, and your method will be automatically implemented:

  public Flux<TableWithForeignKey> lazyGetLinks() {
    return null; // will be implemented
  }

Another way is to make joins directly in the request. There is currently no support to automatically do joins in a repository (like @EntitiGraph in JPA), but you can implement your methods like this:

public interface MyRepository extends LcR2dbcRepository<LinkedTable, Long> {
  
  default Flux<LinkedTable> findAllAndJoin() {
    SelectQuery.from(LinkedTable.class, "root") // SELECT FROM LinkedTable AS root
      .join("root", "links", "link")            // JOIN root.links AS link
      .execute(getLcClient());                  // execute the select and map entities
  }

}

The result will be all LinkedTable instances, with the list of links loaded together from the database.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Seanvd
Solution 2
Solution 3