Spring Reactive Transaction With Mongo

2020-05-08 spring-boot mongo reactive transaction kotlin

Reading time: 2 Min(s)

Introduction

In this blog post we will use ReactiveMongoTransactionManager class to manage the transaction. Along with that we shall use the mongo db.

Version

  • Kotlin: 1.3.71
  • Spring-boot: 2.2.6.RELEASEA
  • Java: 11
  • mongo db: 4.2.0-rc2-bionic

Dependencies

You can use Spring Initializer to initialize the project you need following dependencies -

  • Spring Reactive Web
  • Spring Data Reactive MongoDB

Create docker-compose.yml file

I use docker for running the mongo db the docker compose file looks like this -

version: '3.1'
services:
  mongo:
    container_name: spring-reactive
    image: mongo:4.2.0-rc2-bionic
    ports:
      - 27017:27017
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example 

Configure MongoDb to the application

Add the following configurartion in the application.yml file.

spring:
  data:
    mongodb:
      uri: mongodb://root:example@localhost:27017
      database: customer
      authentication-database: admin

Create a User document

Create a User class that would have an age field. You shall not allow the user to create an account if the age of the user is below 18.

@Document
data class User(
    @Id
    val id: String,
    val age: Int 
)

Create a data repository for User document

interface CustomerRepo : ReactiveCrudRepository<User, String>

Create a service class

Now create a service class that would have a method called saveAll. The saveAll method will accept the list of an Int array, create the user object, validate the age and the save data to the database.

fun saveAll(vararg ages: Int): Flux<User> {
  return Flux.fromIterable(ages.asIterable())
      .map {
        User(id = UUID.randomUUID().toString(),
            age = it)
      }
      .doOnNext { Assert.isTrue(it.age >= 18, "Age should be greater or equal to 18") }
      .flatMap { userRepo.save(it) }
}

Make the service transactional

To add the transactional support to the application, provide two beans transactionManager and transactionOperator to enable the transactions.

Create a transaction manager

Create a ReactiveTransactionManager that will manages the client sessions for the given ReactiveMongoDatabaseFactory

@Bean
fun transactionManager(rdbf: ReactiveMongoDatabaseFactory): ReactiveMongoTransactionManager {
  return ReactiveMongoTransactionManager(rdbf)
}

Create a transaction operator

Operator class that simplifies programmatic transaction demarcation and transaction exception handling. Create a new TransactionalOperator using ReactiveTransactionManager, using a default transaction.

@Bean
fun transactionOperator(rtm: ReactiveTransactionManager): TransactionalOperator {
  return TransactionalOperator.create(rtm)
}

Add transaction to the service

You can add the transaction by wrapping the callback in the execute method of transactionOperator like below -

val data = Flux.fromIterable(ages.asIterable())
  .map {
    User(id = UUID.randomUUID().toString(),
        age = it)
  }
  .doOnNext { Assert.isTrue(it.age >= 18, "Age should be greater or equal to 18") }
  .flatMap { userRepo.save(it) }

return transactionalOperator.execute{ data} 

OR

You can use annotation @Transactional at the method, which you want to make transactional

@Transactional
fun saveAll(vararg ages: Int): Flux<User> {

Source Code Full source is available at GitHub