我有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)回滚了
答案 0 :(得分:11)
MongoDB不支持事务(至少不在单个文档的范围之外)。如果您想要回滚更改,您需要自己手工制作。有一些资源可以描述在Mongo中实现自己的事务的方法,如果你在某些情况下确实需要它们的话。你可以看看..
http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/
这只是您可以使用的模式的解释。如果您发现应用程序中绝对需要事务处理,则应考虑MongoDB是否适合您的需求。
答案 1 :(得分:3)
很抱歉重新发布我的答案。
之前的代码被允许将数据插入到MongoDB中,甚至在将数据插入PostgreSQL时抛出查询异常(使用myBatis)。
我已经解决了MongoDB与关系数据库之间的数据事务问题,并且@Transactional通过在上面的代码中进行了这些更改,可以完美地工作。
@事务管理解决方案。@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);
}
}
<?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。 完整的代码实现在这里。
@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());
}
}
@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。该过程是:
我的conf看起来像这样(我不使用spring boot,但应该等效):
@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;
}
}
@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);
}
}
@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;
}
}
@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