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 }