Spring数据和mongodb - 在@Transactional中简单回滚弹簧

时间:2014-01-27 16:41:42

标签: java spring hibernate mongodb rollback

我有2个存储库,一个用于mongodb(DocumentRepository),另一个用于hibernate实体(EntityRepository)

我有一项简单的服务:

 @Transactional
 public doSomePersisting() {
     try {
           this.entityRepository.save(entity);
           this.documentRepository.save(document);
     }
     catch(...) {
         //Rollback mongoDB here
     }
 }

是否可以在“// Rollback mongoDB here”行回滚mongoDB? 我已经从实体部分(Transactional annotation)回滚了

6 个答案:

答案 0 :(得分:11)

MongoDB不支持事务(至少不在单个文档的范围之外)。如果您想要回滚更改,您需要自己手工制作。有一些资源可以描述在Mongo中实现自己的事务的方法,如果你在某些情况下确实需要它们的话。你可以看看..

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

这只是您可以使用的模式的解释。如果您发现应用程序中绝对需要事务处理,则应考虑MongoDB是否适合您的需求。

答案 1 :(得分:3)

很抱歉重新发布我的答案。

之前的代码被允许将数据插入到MongoDB中,甚至在将数据插入PostgreSQL时抛出查询异常(使用myBatis)。

我已经解决了MongoDB与关系数据库之间的数据事务问题,并且@Transactional通过在上面的代码中进行了这些更改,可以完美地工作。

@事务管理解决方案。

Mongo Config类

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

答案 2 :(得分:0)

MongoDB回滚不适用于@Transactional和MongoTransactionManager。 完整的代码实现在这里。

MongoDB 4.0,mongo-java-driver(版本3.8.2),spring-data-mongodb(版本2.1.0)

MongoConfig类

@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);
        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));
        MongoTemplate mongoTemplate = new MongoTemplate(new SimpleMongoDbFactory(mongoClient(), dbName+"_"+projectId),mappingMongoConverter);
        return mongoTemplate;
    }

    @Bean
    public MongoTransactionManager mongoTransactionManager() {
        return new MongoTransactionManager(mongoDbFactory());
    }
}

用于在mongodb和postgreSQL(使用mybatis)中插入数据的服务类。

@Service
@Component
public class TestRepositoryImpl implements TestRepository{
    private static final Logger LOG = LoggerFactory.getLogger(TestRepositoryImpl.class);

    @Autowired MongoTemplate mongoTemplate;
    @Autowired MongoConfig mongoConfig;
    //@Autowired MongoClient mongoClient;

    @Autowired UserService userService;

    @Override
    @Transactional
    public void save(Test test){
        LOG.info("mongoTemplate <{}>", mongoTemplate.getDb().getName());
        int projectId = 100;
        if (projectId != 0) {
            mongoTemplate = mongoConfig.fetchMongoTemplate(100);
            LOG.info("mongoTemplate <{}>", mongoTemplate.getDb().getName());
        }
        //Inserting data to mongodb
        mongoTemplate.insert(test);
        IdName idName = new IdName();
        idName.setName("test");
        mongoTemplate.insert(idName);
        //Inserting data to postgreSQL
        User user = new User();
        user.setName("Demo");
        user.setEmail("XXXX@XXXX.in");
        user.setPassword("sdfsdfsdf");
        userService.save(user); //This line throws query exception.
    }

即使在这一行抛出异常,数据也不会在mongodb上回滚  userService.save(user); //此行在插入查询异常时引发无效语法。

### SQL: insert into test.user(id,name,email,password     values(?,?,?,?)
### Cause: org.postgresql.util.PSQLException: ERROR: syntax error at or near "values"
  Position: 50
; bad SQL grammar []; nested exception is org.postgresql.util.PSQLException: ERROR: syntax error at or near "values"
  Position: 50] with root cause

答案 3 :(得分:0)

通过 MongoDb 4.0.x ,您可以使用交易。如果您使用以下版本,则必须实施两阶段提交。

注意:MongoDb仅允许您在具有副本集的情况下使用事务。

要同时对JPA和MongoDb使用事务,必须使用ChainedTransactionManager。该过程是:

  • 创建Jpa交易管理器
  • 创建MongoDb交易管理器
  • 创建ChainedTransactionManager,将使用上面的两个

我的conf看起来像这样(我不使用spring boot,但应该等效):

Jpa配置

@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
@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
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;
    }

}

mongoDb回购示例

@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);
    }

在您的服务中使用交易

@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 
    }

答案 4 :(得分:0)

MongoDB v4.x.x与@Transactional完美配合,它们通过使用以下依赖项和存储库对此提供了明确的支持:-

ValueError: Can only compare identically-labeled Series objects

还有一个MongoTransactionConfig类:-

<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>

在这里,我将mongo与kafka一起用作1事务,因此,如果此处发生任何已检查或未检查的异常,则应回退mongo事务,因此我使用了@Transactional(rollbackFor = Exception.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());
    }
}

答案 5 :(得分:0)

如果有人需要onRateOneSelected支持反应性样式 spring boot MongoDb 集成,请进行回答here