Spring-webflux에서 @Transactional를 사용하여 MongoDB Transaction 처리하기

dev-well-being·2023년 1월 6일
1
post-thumbnail

Spring webflux에서 MongoDB를 사용할 때 트랜잭션 처리가 필요하였다. webflux에서 @Transactional을 사용하기 위해서는 별도 설정을 해줘야 한다.

검색을 통해 아래 블로그를 참조하여 구현해보았다. 참고로 MongoDB 4버전 이상부터 트랜잭션 기능을 제공한다고 한다.

Spring Boot MongoDB multi-document transactions

MongoDB transaction 설정

블로그를 참조하여 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);
    }
}

서비스에 정상적인 save와 save후 강제로 Exception을 일으키는 함수를 서비스에 구현하고
	@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();})
                ;
    }

Junit을 활용하여 테스트를 해보았다.
	@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에서 트랜잭션 기능을 지원하려면 stand-alone이 아닌 Replica-set 으로 설치해야 한다는 것이다. 간단하게 말하면 단일노드가 아닌 다중노드로 설정해야 한다는 것이다. 자세한 내용은 아래 블로그를 참조하길..

MongoDB Replica Set (1) - Replication 구성

배보다 배꼽이 큰 상황이다. 백엔드 설정보다 DB 설정을 더해줘야 할 판이다.

어찌하겠는가? 해야지ㅠ

나는 MongoDB를 windows 로컬에서 설치하여 테스트했기 때문에 window버전 replica-set 레퍼런스를 찾아 구현했다.

Create a MongoDB replica set in Windows

MongoDB Replica-set 구성하기

일단 로컬에서 서비스되고 있는 몽고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:

이렇게 서비스를 시작하여 해당 mongoDB의 노드는 rs0이라는 replica-set에 primary로 잡히게 된다. 다음으로 secondary 노드를 생성해줘야 하는데 블로그에서는 노드를 생성 후 replica-set에 secondary 등록해주라고 했지만 나는 그렇게 하면 권한 오류가 발생해서;; secondary 노드를 replica-set를 등록한 후에 노드를 뛰었다.

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이 발생하였다.


그리고 mongoDB에는 정상적으로 수행된 save data만 저장되고 오류가 난 save data는 저장되지 않았다.

profile
안녕하세요!! 좋은 개발 문화를 위해 노력하는 dev-well-being 입니다.

0개의 댓글