Java Spring 数据和 mongodb - 在 @Transactional 中使用 spring 简单回滚
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21386449/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring data and mongodb - simple roll back with spring within @Transactional
提问by Urbanleg
I have 2 repositories, one for mongodb (DocumentRepository) and the other for hibernate entity (EntityRepository)
我有 2 个存储库,一个用于 mongodb(DocumentRepository),另一个用于休眠实体(EntityRepository)
I have a simple service:
我有一个简单的服务:
@Transactional
public doSomePersisting() {
try {
this.entityRepository.save(entity);
this.documentRepository.save(document);
}
catch(...) {
//Rollback mongoDB here
}
}
Is it possible to rollback the mongoDB on the "//Rollback mongoDB here" line? I already got a rollback from the entity part (Transactional annotation)
是否可以在“//Rollback mongoDB here”行回滚 mongoDB?我已经从实体部分得到了回滚(事务注释)
回答by dectarin
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..
MongoDB 不支持事务(至少不超出单个文档的范围)。如果您想回滚更改,您需要自己手工制作。如果您在某些情况下确实需要它们,那么有一些资源描述了在 Mongo 中实现您自己的事务的方法。你可以看看..
http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/
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.
这只是对您可以使用的模式的解释。如果您发现您的应用程序中绝对需要事务,您应该考虑 MongoDB 是否非常适合您的需求。
回答by Srini
MongoDB Rollback does not work with @Transactional and MongoTransactionManager. Full code implementation is here.
MongoDB 回滚不适用于 @Transactional 和 MongoTransactionManager。完整的代码实现在这里。
MongoDB 4.0, mongo-java-driver(version 3.8.2), spring-data-mongodb(version 2.1.0)
MongoDB 4.0、mongo-java-driver(3.8.2版)、spring-data-mongodb(2.1.0版)
MongoConfig class
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());
}
}
Service class to insert data in mongodb and postgreSQL(Using mybatis).
在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("[email protected]");
user.setPassword("sdfsdfsdf");
userService.save(user); //This line throws query exception.
}
Data is not getting rolledback at mongodb even exception throwing at this line userService.save(user); //This line throws invalid syntax at insert query exception.
即使在这一行 userService.save(user); 处抛出异常,数据也不会在 mongodb 回滚;//此行在插入查询异常时抛出无效语法。
### 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
回答by Srini
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).
早期的代码允许将数据插入到 MongoDB 中,甚至在数据插入到 PostgreSQL 时抛出查询异常(使用 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.
我已经解决了 MongoDB 和关系数据库之间的数据事务问题,@Transactional 通过在上面的代码中进行这些更改而完美地工作。
@Transactional 管理的解决方案。Mongo Config class
Mongo 配置类
@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("[email protected]");
user.setPassword("sdfsdfsdf");
userService.save(user);
}
}
POM.XML
POM文件
<?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>
回答by Stanislas Klukowski
With MongoDb 4.0.xyou can use transactions. If you use a version below you have to implement a two phase commit.
使用MongoDb 4.0.x,您可以使用事务。如果您使用以下版本,则必须实现两阶段提交。
NB: MongoDb allows you to use transactions only if you have a ReplicaSet.
注意:MongoDb 仅允许您在拥有 ReplicaSet 时使用事务。
In order to use a transaction for both JPA and MongoDb you have to use a ChainedTransactionManager. The process is :
为了对 JPA 和 MongoDb 使用事务,您必须使用ChainedTransactionManager。过程是:
- create Jpa Transaction manager
- create MongoDb transaction manager
- create ChainedTransactionManager which will use the two above
- 创建 Jpa 事务管理器
- 创建 MongoDb 事务管理器
- 创建 ChainedTransactionManager 它将使用上面的两个
My conf looks like this (I don't use spring boot, but it should be equivalent) :
我的 conf 看起来像这样(我不使用 spring boot,但它应该是等效的):
Jpa configuration
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
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
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;
}
}
Example of a mongoDb repo
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);
}
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
}
回答by Rohan Dodeja
MongoDB v4.x.x works perfectly with @Transactional, they have explicit support for this by using the following dependency and repository :-
MongoDB v4.xx 与 @Transactional 完美配合,他们通过使用以下依赖项和存储库明确支持这一点:-
<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:-
还有一个 MongoTransactionConfig 类:-
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):-
在这里,我将 mongo 与 kafka 一起用作 1 个事务,因此如果此处发生任何已检查或未检查的异常,则应回滚 mongo 事务,因此我使用了 @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();
}