Spring webflux에서 MongoDB를 사용할 때 트랜잭션 처리가 필요하였다. webflux에서 @Transactional을 사용하기 위해서는 별도 설정을 해줘야 한다.
검색을 통해 아래 블로그를 참조하여 구현해보았다. 참고로 MongoDB 4버전 이상부터 트랜잭션 기능을 제공한다고 한다.
Spring Boot MongoDB multi-document transactions
블로그를 참조하여 bean을 등록한 후에 트랜잭션 테스트를 해보았다.
@Configuration
@EnableReactiveMongoRepositories(basePackages = "xxx.xxxxxx.xxx.repository.mongo")
public class MongoConfig {
@Bean
public ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory) {
return new ReactiveMongoTransactionManager(reactiveMongoDatabaseFactory);
}
@Bean
public TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) {
return TransactionalOperator.create(transactionManager);
}
}
@Transactional
public Mono<HelloResDto> saveHello(HelloReqDto reqDto){
Hello req = Hello.builder()
.message(reqDto.getMessage())
.build();
return helloRepository.insert(req)
.map(HelloResDto::from);
}
@Transactional
public Mono<HelloResDto> saveHelloEx(HelloReqDto reqDto){
Hello req = Hello.builder()
.message(reqDto.getMessage())
.build();
return helloRepository.insert(req)
.map(HelloResDto::from)
.doOnSuccess(helloResDto -> {throw new RuntimeException();})
;
}
@Test
void saveTest(){
HelloReqDto test = HelloReqDto
.builder()
.message("save Test777")
.build();
StepVerifier
.create(helloService.saveHello(test))
.assertNext(helloResDto -> assertThat(helloResDto.getMessage()).isEqualTo(test.getMessage()))
.verifyComplete();
}
@Test
void rollbackTest(){
HelloReqDto test = HelloReqDto
.builder()
.message("rollback Test242343242")
.build();
StepVerifier
.create(helloService.saveHelloEx(test))
.assertNext(helloResDto -> assertThat(helloResDto.getMessage()).isEqualTo(test.getMessage()))
.verifyComplete();
}
expectation "assertNext" failed (expected: onNext(); actual: onError(org.springframework.data.mongodb.UncategorizedMongoDbException: This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.; nested exception is com.mongodb.MongoClientException: This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.))
java.lang.AssertionError: expectation "assertNext" failed (expected: onNext(); actual: onError(org.springframework.data.mongodb.UncategorizedMongoDbException: This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.; nested exception is com.mongodb.MongoClientException: This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.))
at reactor.test.MessageFormatter.assertionError(MessageFormatter.java:115)
at reactor.test.MessageFormatter.failPrefix(MessageFormatter.java:104)
at reactor.test.MessageFormatter.fail(MessageFormatter.java:73)
at reactor.test.MessageFormatter.failOptional(MessageFormatter.java:88)
at reactor.test.DefaultStepVerifierBuilder.lambda$consumeNextWith$1(DefaultStepVerifierBuilder.java:276)
at reactor.test.DefaultStepVerifierBuilder$SignalEvent.test(DefaultStepVerifierBuilder.java:2289)
at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onSignal(DefaultStepVerifierBuilder.java:1529)
at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1477)
at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onError(DefaultStepVerifierBuilder.java:1129)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onError(FluxContextWrite.java:121)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onError(FluxContextWrite.java:121)
MongoDB Replica Set (1) - Replication 구성
배보다 배꼽이 큰 상황이다. 백엔드 설정보다 DB 설정을 더해줘야 할 판이다.
어찌하겠는가? 해야지ㅠ
나는 MongoDB를 windows 로컬에서 설치하여 테스트했기 때문에 window버전 replica-set 레퍼런스를 찾아 구현했다.
Create a MongoDB replica set in Windows
일단 로컬에서 서비스되고 있는 몽고DB를 서비스 중지한다.
서비스가 중지되면 몽고DB가 설치된 경로로 찾아가 mongod.cfg 파일을 오픈하여 repliSetName을 rs0(아무이름 상관없음) 설정해준다.
C:\Program Files\MongoDB\Server\6.0\bin\mongod.cfg
mongod.cfg 파일은 mongoDB를 서비스할 때 설정해주는 옵션들을 정의해준 파일같다. 수정한 후 mongoDB 서비스를 시작한다.
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# Where and how to store data.
storage:
dbPath: C:\Program Files\MongoDB\Server\6.0\data
journal:
enabled: true
# engine:
# wiredTiger:
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: C:\Program Files\MongoDB\Server\6.0\log\mongod.log
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1
#processManagement:
#security:
#operationProfiling:
###이부분임############################
replication:
replSetName: "rs0"
###############################
#sharding:
## Enterprise-Only Options:
#auditLog:
#snmp:
primary노드로 접속해서 아래 명령어를 실행해서 replica-set을 초기화해준다.
rs.initiate()
그런 후 내가 생성할 secondary 노드를 등록해준다.
rs.add( { host: "127.0.0.1:27027", priority: 0, votes: 0 } )
그리고 아래 명령어를 실행하면 replica-set이 잘 구성된 것을 확인할 수 있다.
rs.status()
{
set: 'rs0',
date: 2023-01-06T10:14:39.675Z,
myState: 1,
term: Long("1"),
syncSourceHost: '',
syncSourceId: -1,
heartbeatIntervalMillis: Long("2000"),
majorityVoteCount: 1,
writeMajorityCount: 1,
votingMembersCount: 1,
writableVotingMembersCount: 1,
optimes: {
lastCommittedOpTime: { ts: Timestamp({ t: 1673000069, i: 1 }), t: Long("1") },
lastCommittedWallTime: 2023-01-06T10:14:29.706Z,
readConcernMajorityOpTime: { ts: Timestamp({ t: 1673000069, i: 1 }), t: Long("1") },
appliedOpTime: { ts: Timestamp({ t: 1673000069, i: 1 }), t: Long("1") },
durableOpTime: { ts: Timestamp({ t: 1673000069, i: 1 }), t: Long("1") },
lastAppliedWallTime: 2023-01-06T10:14:29.706Z,
lastDurableWallTime: 2023-01-06T10:14:29.706Z
},
lastStableRecoveryTimestamp: Timestamp({ t: 1673000059, i: 1 }),
electionCandidateMetrics: {
lastElectionReason: 'electionTimeout',
lastElectionDate: 2023-01-05T07:46:15.087Z,
electionTerm: Long("1"),
lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1672904775, i: 1 }), t: Long("-1") },
lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1672904775, i: 1 }), t: Long("-1") },
numVotesNeeded: 1,
priorityAtElection: 1,
electionTimeoutMillis: Long("10000"),
newTermStartDate: 2023-01-05T07:46:15.125Z,
wMajorityWriteAvailabilityDate: 2023-01-05T07:46:15.145Z
},
members: [
{
_id: 0,
name: '127.0.0.1:27017',
health: 1,
state: 1,
stateStr: 'PRIMARY',
uptime: 95313,
optime: [Object],
optimeDate: 2023-01-06T10:14:29.000Z,
lastAppliedWallTime: 2023-01-06T10:14:29.706Z,
lastDurableWallTime: 2023-01-06T10:14:29.706Z,
syncSourceHost: '',
syncSourceId: -1,
infoMessage: '',
electionTime: Timestamp({ t: 1672904775, i: 2 }),
electionDate: 2023-01-05T07:46:15.000Z,
configVersion: 2,
configTerm: 1,
self: true,
lastHeartbeatMessage: ''
},
{
_id: 1,
name: '127.0.0.1:27027',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 15166,
optime: [Object],
optimeDurable: [Object],
optimeDate: 2023-01-06T10:14:29.000Z,
optimeDurableDate: 2023-01-06T10:14:29.000Z,
lastAppliedWallTime: 2023-01-06T10:14:29.706Z,
lastDurableWallTime: 2023-01-06T10:14:29.706Z,
lastHeartbeat: 2023-01-06T10:14:39.370Z,
lastHeartbeatRecv: 2023-01-06T10:14:39.497Z,
pingMs: Long("0"),
lastHeartbeatMessage: '',
syncSourceHost: '127.0.0.1:27017',
syncSourceId: 0,
infoMessage: '',
configVersion: 2,
configTerm: 1
}
],
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1673000069, i: 1 }),
signature: {
hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
keyId: Long("0")
}
},
operationTime: Timestamp({ t: 1673000069, i: 1 })
자 이제 설정은 다 맞췄고 아래 명령어를 실행해서 두 번째 노드를 실행해보자.
mongod --port 27027 --replSet rs0 --dbpath="C:\Program Files\MongoDB\Server\6.0\data1"
첫 번째 노드처럼 mongod.cfg 파일을 이용하여 설정할 수도 있지만 이렇게 옵션을 주어서 설정할 수도 있다.
자 이제 모든 준비가 완료됬다. 이제 다시 한번 테스트를 수행해보자.
2023-01-05 17:11:16.501 INFO 14380 --- [ntLoopGroup-2-3] org.mongodb.driver.connection : Opened connection [connectionId{localValue:3, serverValue:29}] to localhost:27017
expectation "assertNext" failed (expected: onNext(); actual: onError(org.springframework.data.mongodb.MongoTransactionException: Command failed with error 263 (OperationNotSupportedInTransaction): 'Cannot run command against the 'local' database in a transaction.' on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "Cannot run command against the 'local' database in a transaction.", "code": 263, "codeName": "OperationNotSupportedInTransaction", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1672906268, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1672906268, "i": 1}}}; nested exception is com.mongodb.MongoCommandException: Command failed with error 263 (OperationNotSupportedInTransaction): 'Cannot run command against the 'local' database in a transaction.' on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "Cannot run command against the 'local' database in a transaction.", "code": 263, "codeName": "OperationNotSupportedInTransaction", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1672906268, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1672906268, "i": 1}}}))
java.lang.AssertionError: expectation "assertNext" failed (expected: onNext(); actual: onError(org.springframework.data.mongodb.MongoTransactionException: Command failed with error 263 (OperationNotSupportedInTransaction): 'Cannot run command against the 'local' database in a transaction.' on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "Cannot run command against the 'local' database in a transaction.", "code": 263, "codeName": "OperationNotSupportedInTransaction", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1672906268, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1672906268, "i": 1}}}; nested exception is com.mongodb.MongoCommandException: Command failed with error 263 (OperationNotSupportedInTransaction): 'Cannot run command against the 'local' database in a transaction.' on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "Cannot run command against the 'local' database in a transaction.", "code": 263, "codeName": "OperationNotSupportedInTransaction", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1672906268, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1672906268, "i": 1}}}))
at reactor.test.MessageFormatter.assertionError(MessageFormatter.java:115)
at reactor.test.MessageFormatter.failPrefix(MessageFormatter.java:104)
at reactor.test.MessageFormatter.fail(MessageFormatter.java:73)
at reactor.test.MessageFormatter.failOptional(MessageFormatter.java:88)
at reactor.test.DefaultStepVerifierBuilder.lambda$consumeNextWith$1(DefaultStepVerifierBuilder.java:276)
at reactor.test.DefaultStepVerifierBuilder$SignalEvent.test(DefaultStepVerifierBuilder.java:2289)
at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onSignal(DefaultStepVerifierBuilder.java:1529)
at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1477)
at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onError(DefaultStepVerifierBuilder.java:1129)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onError(FluxContextWrite.java:121)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onError(FluxContextWrite.java:121)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondError(MonoFlatMap.java:192)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onError(MonoFlatMap.java:259)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondError(MonoFlatMap.java:192)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onError(MonoFlatMap.java:259)
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:106)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onError(MonoIgnoreThen.java:278)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:231)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:196)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:268)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:209)
at reactor.core.publisher.MonoRunnable.subscribe(MonoRunnable.java:50)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:240)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:209)
at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:89)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onComplete(FluxContextWrite.java:126)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:368)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onComplete(FluxConcatMap.java:276)
at reactor.core.publisher.Operators.complete(Operators.java:137)
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:148)
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:87)
at reactor.core.publisher.MonoFromFluxOperator.subscribe(MonoFromFluxOperator.java:81)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:240)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:209)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058)
at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058)
at reactor.core.publisher.Operators.complete(Operators.java:137)
at reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:46)
at reactor.core.publisher.Mono.subscribe(Mono.java:4455)
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
at reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:93)
at reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:93)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondError(MonoFlatMap.java:192)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onError(MonoFlatMap.java:259)
at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onError(MonoPeekTerminal.java:258)
at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:201)
at com.mongodb.reactivestreams.client.internal.MongoOperationPublisher.lambda$sinkToCallback$30(MongoOperationPublisher.java:545)
at com.mongodb.reactivestreams.client.internal.OperationExecutorImpl.lambda$execute$9(OperationExecutorImpl.java:124)
at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at com.mongodb.internal.operation.CommandOperationHelper.lambda$exceptionTransformingCallback$23(CommandOperationHelper.java:632)
at com.mongodb.internal.async.function.AsyncCallbackSupplier.lambda$whenComplete$1(AsyncCallbackSupplier.java:95)
at com.mongodb.internal.async.function.RetryingAsyncCallbackSupplier$RetryingCallback.onResult(RetryingAsyncCallbackSupplier.java:109)
at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at com.mongodb.internal.async.function.AsyncCallbackSupplier.lambda$whenComplete$1(AsyncCallbackSupplier.java:95)
at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at com.mongodb.internal.async.function.AsyncCallbackSupplier.lambda$whenComplete$1(AsyncCallbackSupplier.java:95)
at com.mongodb.internal.operation.CommandOperationHelper.lambda$transformingWriteCallback$11(CommandOperationHelper.java:340)
at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor$2.onResult(DefaultServer.java:272)
at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at com.mongodb.internal.connection.CommandProtocolImpl$1.onResult(CommandProtocolImpl.java:82)
at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection$1.onResult(DefaultConnectionPool.java:684)
at com.mongodb.internal.connection.UsageTrackingInternalConnection$2.onResult(UsageTrackingInternalConnection.java:159)
at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:523)
at com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:498)
at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback$MessageCallback.onResult(InternalStreamConnection.java:821)
at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback$MessageCallback.onResult(InternalStreamConnection.java:785)
at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:645)
at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:642)
at com.mongodb.connection.netty.NettyStream.readAsync(NettyStream.java:319)
at com.mongodb.connection.netty.NettyStream.readAsync(NettyStream.java:266)
at com.mongodb.internal.connection.InternalStreamConnection.readAsync(InternalStreamConnection.java:642)
at com.mongodb.internal.connection.InternalStreamConnection.access$600(InternalStreamConnection.java:86)
at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback.onResult(InternalStreamConnection.java:775)
at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback.onResult(InternalStreamConnection.java:760)
at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:645)
at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:642)
at com.mongodb.connection.netty.NettyStream.readAsync(NettyStream.java:319)
at com.mongodb.connection.netty.NettyStream.handleReadResponse(NettyStream.java:347)
at com.mongodb.connection.netty.NettyStream.access$1100(NettyStream.java:105)
at com.mongodb.connection.netty.NettyStream$InboundBufferHandler.channelRead0(NettyStream.java:421)
at com.mongodb.connection.netty.NettyStream$InboundBufferHandler.channelRead0(NettyStream.java:418)
at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:834)
Suppressed: org.springframework.data.mongodb.MongoTransactionException: Command failed with error 263 (OperationNotSupportedInTransaction): 'Cannot run command against the 'local' database in a transaction.' on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "Cannot run command against the 'local' database in a transaction.", "code": 263, "codeName": "OperationNotSupportedInTransaction", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1672906268, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1672906268, "i": 1}}}; nested exception is com.mongodb.MongoCommandException: Command failed with error 263 (OperationNotSupportedInTransaction): 'Cannot run command against the 'local' database in a transaction.' on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "Cannot run command against the 'local' database in a transaction.", "code": 263, "codeName": "OperationNotSupportedInTransaction", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1672906268, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1672906268, "i": 1}}}
at app//org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:137)
at app//org.springframework.data.mongodb.core.ReactiveMongoTemplate.potentiallyConvertRuntimeException(ReactiveMongoTemplate.java:2962)
at app//org.springframework.data.mongodb.core.ReactiveMongoTemplate.lambda$translateException$89(ReactiveMongoTemplate.java:2945)
at app//reactor.core.publisher.Flux.lambda$onErrorMap$28(Flux.java:6954)
at app//reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94)
at app//reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onError(MonoFlatMapMany.java:255)
at app//reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:134)
at app//reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:106)
at app//reactor.core.publisher.Operators.error(Operators.java:198)
at app//reactor.core.publisher.MonoError.subscribe(MonoError.java:53)
at app//reactor.core.publisher.Mono.subscribe(Mono.java:4455)
at app//reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
at app//reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:93)
at app//reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:93)
at app//reactor.core.publisher.MonoFlatMap$FlatMapMain.secondError(MonoFlatMap.java:192)
at app//reactor.core.publisher.MonoFlatMap$FlatMapInner.onError(MonoFlatMap.java:259)
at app//reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onError(MonoPeekTerminal.java:258)
at app//reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:201)
at app//com.mongodb.reactivestreams.client.internal.MongoOperationPublisher.lambda$sinkToCallback$30(MongoOperationPublisher.java:545)
at app//com.mongodb.reactivestreams.client.internal.OperationExecutorImpl.lambda$execute$9(OperationExecutorImpl.java:124)
at app//com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at app//com.mongodb.internal.operation.CommandOperationHelper.lambda$exceptionTransformingCallback$23(CommandOperationHelper.java:632)
at app//com.mongodb.internal.async.function.AsyncCallbackSupplier.lambda$whenComplete$1(AsyncCallbackSupplier.java:95)
at app//com.mongodb.internal.async.function.RetryingAsyncCallbackSupplier$RetryingCallback.onResult(RetryingAsyncCallbackSupplier.java:109)
at app//com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at app//com.mongodb.internal.async.function.AsyncCallbackSupplier.lambda$whenComplete$1(AsyncCallbackSupplier.java:95)
at app//com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:48)
at app//com.mongodb.internal.async.function.AsyncCallbackSupplier.lambda$whenComplete$1(AsyncCallbackSupplier.java:95)
at app//com.mongodb.internal.operation.MixedBulkWriteOperation.lambda$executeBulkWriteBatchAsync$11(MixedBulkWriteOperation.java:454)
at app//com.mongodb.internal.async.function.AsyncCallbackLoop$LoopingCallback.onResult(AsyncCallbackLoop.java:72)
at app//com.mongodb.internal.async.function.AsyncCallbackLoop$LoopingCallback.onResult(AsyncCallbackLoop.java:61)
at app//com.mongodb.internal.operation.MixedBulkWriteOperation.lambda$executeBulkWriteBatchAsync$9(MixedBulkWriteOperation.java:448)
... 43 more
Caused by: com.mongodb.MongoCommandException: Command failed with error 263 (OperationNotSupportedInTransaction): 'Cannot run command against the 'local' database in a transaction.' on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "Cannot run command against the 'local' database in a transaction.", "code": 263, "codeName": "OperationNotSupportedInTransaction", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1672906268, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1672906268, "i": 1}}}
at app//com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:198)
at app//com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:512)
... 35 more
뭐야 갑자기;; 실컷 다 replica-set 구성했더니 왜 또 안되지;; 자세히 보니 오류 내용이 달랐다. 해당 오류내용을 찾아보니 몽고DB에서 기본적으로 제공하는 admin, config, local database에서는 트랜잭션 처리가 안된다고 한다. 그래서 test라는 database를 만들어 다시 시도해보았다.
saveTest는 정상적으로 수행되었고 rollbackTest는 내가 임의로 준 Exception이 발생하였다.