Spring data and mongodb - simple roll back with spring within @Transactional Spring data and mongodb - simple roll back with spring within @Transactional spring spring

Spring data and mongodb - simple roll back with spring within @Transactional


MongoDB doesn't support transactions (at least not outside the scope of a single document). If you want to roll back changes you will need to handcraft that yourself. There are a few resources out there that describe ways of implementing your own transactions in Mongo if you really need them in certain circumstances. You could take a look at..

http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/

This is just an explanation of a pattern you could use. If you find that you absolutely need transactions in your application, you should consider whether MongoDB is a good fit for your needs.


Sorry for reposting my answer.

The earlier code was allowed to insert data into MongoDB even query exceptions throwing at data insertion into PostgreSQL(Using myBatis).

I have resolved the data Transaction issue between MongoDB and Relational database and @Transactional perfectly works by making these changes in the above code.

Solution for @Transactional Management.

Mongo Config class

@Configurationpublic class MongoConfig extends AbstractMongoConfiguration{    private static final Logger LOG = LoggerFactory.getLogger(MongoConfig.class);    @Value("${spring.data.mongodb.database}")    private String dbName;    @Value("${spring.data.mongodb.host}")    private String dbHost;    @Value("${spring.data.mongodb.port}")    private int dbPort;    @Override    public String getDatabaseName() {        return dbName;    }    @Bean    public MongoClient mongoClient(){        return new MongoClient(dbHost, dbPort);    }    @Bean    public MongoDbFactory mongoDbFactory(){        return new SimpleMongoDbFactory(mongoClient(),dbName);    }    @Bean    public MongoTemplate mongoTemplate() {        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());        MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());        // Don't save _class to mongo        mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));        MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(),mappingMongoConverter);        mongoTemplate.setSessionSynchronization(SessionSynchronization.ON_ACTUAL_TRANSACTION);        return mongoTemplate;    }    public MongoTemplate fetchMongoTemplate(int projectId) {        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());        MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());        // Don't save _class to mongo        mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));        MongoDbFactory customizedDBFactory = new SimpleMongoDbFactory(mongoClient(), dbName+"_"+projectId);        MongoTemplate mongoTemplate = new MongoTemplate(customizedDBFactory,mappingMongoConverter);        MongoTransactionManager mongoTransactionManager = new MongoTransactionManager(customizedDBFactory);        return mongoTemplate;    }    @Bean    public MongoTransactionManager mongoTransactionManager() {        return new MongoTransactionManager(mongoDbFactory());    }}

Service class for Data insertion

@Service@Componentpublic class TestRepositoryImpl implements TestRepository{    private static final Logger LOG = LoggerFactory.getLogger(TestRepositoryImpl.class);@Autowired MongoConfig mongoConfig;@Autowired MongoTemplate mongoTemplate;@Autowired MongoTransactionManager mongoTransactionManager;@Autowired UserService userService;@Override@Transactionalpublic void save(Test test){    int projectId = 100;    if (projectId != 0) {        mongoTemplate = mongoConfig.fetchMongoTemplate(100);        mongoTemplate.setSessionSynchronization(SessionSynchronization.ALWAYS);    }    mongoTemplate.insert(test);    IdName idName = new IdName();    idName.setName("test");    mongoTemplate.insert(idName);    User user = new User();    user.setName("Demo");    user.setEmail("srini@abspl.in");    user.setPassword("sdfsdfsdf");    userService.save(user);    } }

POM.XML

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0</modelVersion>  <groupId>com.abcplusd.sample.mongoapi</groupId>  <artifactId>sample-mongo-api</artifactId>  <version>1.0-SNAPSHOT</version>  <name>Sample Spring Boot Mongo API</name>  <description>Demo project for Spring Boot Mongo with Spring Data Mongo</description>  <parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.0.1.RELEASE</version>    <relativePath/> <!-- lookup parent from repository -->  </parent>  <properties>    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    <java.version>1.8</java.version>  </properties>  <dependencies>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-actuator</artifactId>    </dependency>    <dependency>      <groupId>org.springframework.data</groupId>      <artifactId>spring-data-mongodb</artifactId>      <version>2.1.0.RELEASE</version>      <exclusions>        <exclusion>          <groupId>org.mongodb</groupId>          <artifactId>mongo-java-driver</artifactId>        </exclusion>      </exclusions>    </dependency>    <dependency>      <groupId>org.springframework.data</groupId>      <artifactId>spring-data-commons</artifactId>      <version>2.1.0.RELEASE</version>    </dependency>    <dependency>      <groupId>org.mongodb</groupId>      <artifactId>mongo-java-driver</artifactId>      <version>3.8.2</version>    </dependency>    <dependency>      <groupId>org.mybatis</groupId>      <artifactId>mybatis-spring</artifactId>      <version>1.3.1</version>    </dependency>    <dependency>      <groupId>org.postgresql</groupId>      <artifactId>postgresql</artifactId>      <version>42.2.2</version>    </dependency>    <dependency>      <groupId>org.mybatis.spring.boot</groupId>      <artifactId>mybatis-spring-boot-starter</artifactId>      <version>1.3.2</version>    </dependency>    <dependency>      <groupId>org.mybatis</groupId>      <artifactId>mybatis</artifactId>      <version>3.4.5</version>    </dependency>  </dependencies>  <build>    <plugins>      <plugin>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-maven-plugin</artifactId>      </plugin>    </plugins>  </build></project>


With MongoDb 4.0.x you can use transactions. If you use a version below you have to implement a two phase commit.

NB: MongoDb allows you to use transactions only if you have a ReplicaSet.

In order to use a transaction for both JPA and MongoDb you have to use a ChainedTransactionManager. The process is :

  • create Jpa Transaction manager
  • create MongoDb transaction manager
  • create ChainedTransactionManager which will use the two above

My conf looks like this (I don't use spring boot, but it should be equivalent) :

Jpa configuration

@Configuration@EnableTransactionManagement@EnableJpaRepositories("com....")public class HibernateConfig {    //define entity manager, data source and all the stuff needed for your DB    @Bean("jpaTransactionManager")    public JpaTransactionManager transactionManager() throws NamingException {         JpaTransactionManager transactionManager = new JpaTransactionManager();        //transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());        return transactionManager;    }}

MongoDb configuration

@Configuration@EnableMongoRepositories(basePackages = "com....")public class MongoDbConf extends AbstractMongoClientConfiguration {    private final Environment environment;    @Autowired    public MongoDbConf(Environment environment) {        this.environment = environment;    }    @Override    public MongoClient mongoClient() {        String connectionString = environment.getProperty("mongodb.connectionString");        if(StringUtils.isBlank(connectionString))            throw new IllegalArgumentException("No connection string to initialize mongo client");        return MongoClients.create(                MongoClientSettings.builder()                        .applyConnectionString(new ConnectionString(connectionString))                        .applicationName("MY_APP")                        .build());    }    @Override    protected String getDatabaseName() {        return environment.getProperty("mongodb.database", "myDB");    }    @Bean("mongoDbTransactionManager")    public MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {        return new MongoTransactionManager(dbFactory);    }}

ChainedTransactionManager configuration

@Configurationpublic class ChainedTransactionConf {    private MongoTransactionManager mongoTransactionManager;    private JpaTransactionManager jpaTransactionManager;    @Autowired    public ChainedTransactionConf(MongoTransactionManager mongoTransactionManager, JpaTransactionManager jpaTransactionManager) {        this.mongoTransactionManager = mongoTransactionManager;        this.jpaTransactionManager = jpaTransactionManager;    }    @Bean("chainedTransactionManager")    public PlatformTransactionManager getTransactionManager() {        ChainedTransactionManager transactionManager = new ChainedTransactionManager(jpaTransactionManager, mongoTransactionManager);        return transactionManager;    }}

Example of a mongoDb repo

@Servicepublic class MongoDbRepositoryImpl implements MongoDbRepository {    private static final Logger logger = Logger.getLogger(MongoDbRepositoryImpl.class);    //MongoOperations will handle a mongo session    private final MongoOperations operations;    @Autowired    public MongoDbRepositoryImpl(MongoOperations operations) {        this.operations = operations;    }    @Override    public void insertData(Document document) {        MongoCollection<Document> collection = operations.getCollection("myCollection");        collection.insertOne(document);    }

Using transaction in your service

@Servicepublic class DocumentServiceImpl implements DocumentService {    private final MongoDbRepository mongoDbRepository;    private final JpaRepository jpaRepository;    @Autowired    public DocumentServiceImpl(MongoDbRepository mongoDbRepository,JpaRepository jpaRepository) {        this.mongoDbRepository = mongoDbRepository;        this.jpaRepository = jpaRepository;    }    @Override    @Transactional("chainedTransactionManager")    public void insertNewDoc(Map<String,Object> rawData) {        //use org.springframework.transaction.annotation.Transactional so you can define used transactionManager        //jpaRepository.insert...        Document mongoDoc = new Document(rawData);        mongoDbRepository.insertData(mongoDoc)        //you can test like this : breakpoint and throw new IllegalStateException()         //to see that data is not commited     }