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

前端 未结 5 2095
自闭症患者
自闭症患者 2020-12-14 10:43

I have 2 repositories, one for mongodb (DocumentRepository) and the other for hibernate entity (EntityRepository)

I have a simple service:

 @Transact         


        
相关标签:
5条回答
  • 2020-12-14 11:12

    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

    @Configuration
    public 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
    @Component
    public 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
    @Transactional
    public 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>
    
    0 讨论(0)
  • 2020-12-14 11:12

    If anyone is in need of transactional support for reactive style spring boot and MongoDb integration then please go through the answer here

    0 讨论(0)
  • 2020-12-14 11:27

    MongoDB v4.x.x works perfectly with @Transactional, they have explicit support for this by using the following dependency and repository :-

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-releasetrain</artifactId>
        <version>Lovelace-M3</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    

    And a MongoTransactionConfig class:-

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.mongodb.MongoDbFactory;
    import org.springframework.data.mongodb.MongoTransactionManager;
    import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
    import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
    
    import com.mongodb.ConnectionString;
    import com.mongodb.MongoClientSettings;
    import com.mongodb.client.MongoClient;
    import com.mongodb.client.MongoClients;
    
    @Configuration
    @EnableMongoRepositories(basePackages = "com.airtel.africa.caxr.repository")
    public class MongoTransactionConfig extends AbstractMongoClientConfiguration {
    
        @Value("${spring.data.mongodb.host}")
        private String host;
        @Value("${spring.data.mongodb.port}")
        private String port;
        @Value("${spring.data.mongodb.database}")
        private String database;
    
        @Bean
        MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
            return new MongoTransactionManager(dbFactory);
        }
    
        @Override
        protected String getDatabaseName() {
            return database;
        }
        @Override
        public MongoClient mongoClient() {
            String connectionString = "mongodb://"+host+":"+port;
    
            return MongoClients.create(MongoClientSettings.builder()
                .applyConnectionString(new 
            ConnectionString(connectionString)).build());
        }
    }
    

    And here I'm using mongo with kafka as a 1 transaction so if any checked or unchecked exception occurs here then mongo transaction should be rolled back so I used @Transactional(rollbackFor = Exception.class):-

    @Transactional(rollbackFor = Exception.class)
    public void receiveInEventRequest(TransactionDto transactionDto) throws 
        InterruptedException, ExecutionException {
        // db insert    
        TransactionRequest transactionRequest = requestDbDumpService.dumpToDb(transactionDto);
        // kafka insert
        ListenableFuture<SendResult<String, TransactionDto>> kafkaResult = kafkaTemplate.send(kafkaProducerQueueName, “ID”, transactionDto);
        SendResult<String, TransactionDto> kafkaSendResult = kafkaResult.get();
    }
    
    0 讨论(0)
  • 2020-12-14 11:33

    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.

    0 讨论(0)
  • 2020-12-14 11:39

    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

    @Configuration
    public 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

    @Service
    public 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

    @Service
    public 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 
        }
    
    0 讨论(0)
提交回复
热议问题