zfoo icon indicating copy to clipboard operation
zfoo copied to clipboard

无法回滚事务

Open frank0234 opened this issue 1 year ago • 8 comments

使用OrmContext提供的MongoClient无法回滚事务,自行创建的client可以回滚

frank0234 avatar Oct 16 '23 11:10 frank0234

你指的是操作单条文档的事务,还是操作多条文档的事务。

jaysunxiao avatar Oct 16 '23 13:10 jaysunxiao

系统环境、相关软件版本; 描述你遇到的问题,简洁有效的说明 如何重现问题,描述遇到的问题的发生步骤,最好给出代码

jaysunxiao avatar Oct 16 '23 13:10 jaysunxiao

在MongoDB中,对单个文档的操作是原子的。由于您可以使用嵌入式文档和数组来捕获单个文档结构中数据之间的关系,而不是跨多个文档和集合进行规范化,因此这种单文档原子性消除了许多实际用例对多文档事务的需求。

jaysunxiao avatar Oct 16 '23 13:10 jaysunxiao

https://www.mongodb.com/docs/v7.0/core/transactions-production-consideration/

必须要用复制集或者分片集群才能使用事务,否则可能导致意料之外的结果

jaysunxiao avatar Oct 16 '23 13:10 jaysunxiao

系统环境、相关软件版本; 描述你遇到的问题,简洁有效的说明 如何重现问题,描述遇到的问题的发生步骤,最好给出代码

单机测试环境:我写了一个测试用例。

package com.zfoo.orm.transaction;

import com.mongodb.client.*;
import com.zfoo.orm.OrmContext;
import com.zfoo.orm.entity.UserEntity;
import org.bson.Document;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransactionTest {

    @Test
    public void test() {
        var user = new UserEntity();
        user.setId(1000L);
        user.setC(1);
        user.setE("User");

        MongoClient myClient = MongoClients.create("mongodb://127.0.0.1:27017/admin");
        MongoDatabase database = myClient.getDatabase("test");
        String dbInfo = database.runCommand(new Document("buildInfo", 1)).getString("version");
        System.out.println("数据库版本:" + dbInfo);
        MongoCollection<UserEntity> myCollection = database.getCollection("user", UserEntity.class);
        database.drop();
        assert 0 == myCollection.countDocuments();
        ClientSession mySession = myClient.startSession();
        try {
            myClient.startSession().withTransaction(() -> {
                myCollection.insertOne(user);
                //ID重复失败
                myCollection.insertOne(user);
                return true;
            });
        } catch (Exception e) {
            System.out.println("-----My rollback-----");
            mySession.close();
            assert 0 == myCollection.countDocuments();
            System.out.println("My rollback success");
        }

        new ClassPathXmlApplicationContext("application.xml");
        MongoClient yourClient = OrmContext.getOrmManager().mongoClient();
        ClientSession yourSession = yourClient.startSession();
        try {
            yourClient.startSession().withTransaction(() -> {
                OrmContext.getAccessor().insert(user);
                OrmContext.getAccessor().insert(user);
                return true;
            });
        } catch (Exception e) {
            System.out.println("-----your rollback-----");
            yourSession.close();
            UserEntity userEntity = OrmContext.getAccessor().load(1000, UserEntity.class);
            System.out.println("userEntity:" + userEntity);
            assert null == userEntity;
            System.out.println("your rollback success");
        }
    }

}

输出的log如下:

2023-10-17 15:51:42 [ INFO] [main] org.mongodb.driver.client.info(SLF4JLogger.java:71) - MongoClient with metadata {"driver": {"name": "mongo-java-driver|sync", "version": "4.10.2"}, "os": {"type": "Windows", "name": "Windows Server 2022", "architecture": "amd64", "version": "10.0"}, "platform": "Java/Oracle Corporation/21+35-2513"} created with settings MongoClientSettings{readPreference=primary, writeConcern=WriteConcern{w=null, wTimeout=null ms, journal=null}, retryWrites=true, retryReads=true, readConcern=ReadConcern{level=null}, credential=null, streamFactoryFactory=null, commandListeners=[], codecRegistry=ProvidersCodecRegistry{codecProviders=[ValueCodecProvider{}, BsonValueCodecProvider{}, DBRefCodecProvider{}, DBObjectCodecProvider{}, DocumentCodecProvider{}, CollectionCodecProvider{}, IterableCodecProvider{}, MapCodecProvider{}, GeoJsonCodecProvider{}, GridFSFileCodecProvider{}, Jsr310CodecProvider{}, JsonObjectCodecProvider{}, BsonCodecProvider{}, EnumCodecProvider{}, com.mongodb.client.model.mql.ExpressionCodecProvider@626abbd0, com.mongodb.Jep395RecordCodecProvider@169bb4dd, com.mongodb.KotlinCodecProvider@1f9e9475]}, loggerSettings=LoggerSettings{maxDocumentLength=1000}, clusterSettings={hosts=[127.0.0.1:27017], srvServiceName=mongodb, mode=SINGLE, requiredClusterType=UNKNOWN, requiredReplicaSetName='null', serverSelector='null', clusterListeners='[]', serverSelectionTimeout='30000 ms', localThreshold='30000 ms'}, socketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=0, receiveBufferSize=0, sendBufferSize=0}, heartbeatSocketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=10000, receiveBufferSize=0, sendBufferSize=0}, connectionPoolSettings=ConnectionPoolSettings{maxSize=100, minSize=0, maxWaitTimeMS=120000, maxConnectionLifeTimeMS=0, maxConnectionIdleTimeMS=0, maintenanceInitialDelayMS=0, maintenanceFrequencyMS=60000, connectionPoolListeners=[], maxConnecting=2}, serverSettings=ServerSettings{heartbeatFrequencyMS=10000, minHeartbeatFrequencyMS=500, serverListeners='[]', serverMonitorListeners='[]'}, sslSettings=SslSettings{enabled=false, invalidHostNameAllowed=false, context=null}, applicationName='null', compressorList=[], uuidRepresentation=UNSPECIFIED, serverApi=null, autoEncryptionSettings=null, dnsClient=null, inetAddressResolver=null, contextProvider=null} 2023-10-17 15:51:42 [ INFO] [cluster-ClusterId{value='652e3d0e04a66463a1467d34', description='null'}-127.0.0.1:27017] org.mongodb.driver.cluster.info(SLF4JLogger.java:71) - Monitor thread successfully connected to server with description ServerDescription{address=127.0.0.1:27017, type=STANDALONE, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=17, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=31770400}

数据库版本:6.0.11-rc0

-----My rollback-----

My rollback success

2023-10-17 15:51:43 [ INFO] [main] org.mongodb.driver.client.info(SLF4JLogger.java:71) - MongoClient with metadata {"driver": {"name": "mongo-java-driver|sync", "version": "4.10.2"}, "os": {"type": "Windows", "name": "Windows Server 2022", "architecture": "amd64", "version": "10.0"}, "platform": "Java/Oracle Corporation/21+35-2513"} created with settings MongoClientSettings{readPreference=primary, writeConcern=WriteConcern{w=null, wTimeout=null ms, journal=null}, retryWrites=true, retryReads=true, readConcern=ReadConcern{level=null}, credential=null, streamFactoryFactory=null, commandListeners=[], codecRegistry=ProvidersCodecRegistry{codecProviders=[ProvidersCodecRegistry{codecProviders=[ValueCodecProvider{}, BsonValueCodecProvider{}, DBRefCodecProvider{}, DBObjectCodecProvider{}, DocumentCodecProvider{}, CollectionCodecProvider{}, IterableCodecProvider{}, MapCodecProvider{}, GeoJsonCodecProvider{}, GridFSFileCodecProvider{}, Jsr310CodecProvider{}, JsonObjectCodecProvider{}, BsonCodecProvider{}, EnumCodecProvider{}, com.mongodb.client.model.mql.ExpressionCodecProvider@626abbd0, com.mongodb.Jep395RecordCodecProvider@169bb4dd, com.mongodb.KotlinCodecProvider@1f9e9475]}, ProvidersCodecRegistry{codecProviders=[org.bson.codecs.pojo.PojoCodecProvider@460b6d54]}]}, loggerSettings=LoggerSettings{maxDocumentLength=1000}, clusterSettings={hosts=[127.0.0.1:27017], srvServiceName=mongodb, mode=SINGLE, requiredClusterType=UNKNOWN, requiredReplicaSetName='null', serverSelector='null', clusterListeners='[]', serverSelectionTimeout='30000 ms', localThreshold='30000 ms'}, socketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=0, receiveBufferSize=0, sendBufferSize=0}, heartbeatSocketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=10000, receiveBufferSize=0, sendBufferSize=0}, connectionPoolSettings=ConnectionPoolSettings{maxSize=33, minSize=1, maxWaitTimeMS=120000, maxConnectionLifeTimeMS=0, maxConnectionIdleTimeMS=0, maintenanceInitialDelayMS=0, maintenanceFrequencyMS=60000, connectionPoolListeners=[], maxConnecting=2}, serverSettings=ServerSettings{heartbeatFrequencyMS=10000, minHeartbeatFrequencyMS=500, serverListeners='[]', serverMonitorListeners='[]'}, sslSettings=SslSettings{enabled=false, invalidHostNameAllowed=false, context=null}, applicationName='null', compressorList=[], uuidRepresentation=UNSPECIFIED, serverApi=null, autoEncryptionSettings=null, dnsClient=null, inetAddressResolver=null, contextProvider=null} 2023-10-17 15:51:43 [ INFO] [cluster-ClusterId{value='652e3d0f04a66463a1467d35', description='null'}-127.0.0.1:27017] org.mongodb.driver.cluster.info(SLF4JLogger.java:71) - Monitor thread successfully connected to server with description ServerDescription{address=127.0.0.1:27017, type=STANDALONE, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=17, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=4795900} 2023-10-17 15:51:43 [ INFO] [main] org.mongodb.driver.cluster.info(SLF4JLogger.java:71) - Cluster description not yet available. Waiting for 30000 ms before timing out 2023-10-17 15:51:43 [ INFO] [main] com.zfoo.orm.OrmContext.onApplicationEvent(OrmContext.java:93) - Orm started successfully and cost [0.19] seconds

-----your rollback-----

userEntity:UserEntity{id=1000, a=0, b=0, c=1, d=false, e='User', f='null', l=null}

java.lang.AssertionError at com.zfoo.orm.transaction.TransactionTest.test(TransactionTest.java:56) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

frank0234 avatar Oct 17 '23 07:10 frank0234

    MongoClient myClient = MongoClients.create("mongodb://127.0.0.1:27017/admin");
    MongoDatabase database = myClient.getDatabase("test");
    String dbInfo = database.runCommand(new Document("buildInfo", 1)).getString("version");
    System.out.println("数据库版本:" + dbInfo);
    MongoCollection<UserEntity> myCollection = database.getCollection("user", UserEntity.class);
    myCollection.insertOne(user);
    
    这种写法是有问题的,没有指定对象的bson的编解码无法使用对象
    

参考下面的标准写法,只能用Document https://github.com/zfoo-project/zfoo/blob/main/orm/src/test/java/com/zfoo/orm/client/SingleMongoTest.java

jaysunxiao avatar Oct 17 '23 10:10 jaysunxiao

    MongoClient myClient = MongoClients.create("mongodb://127.0.0.1:27017/admin");
    MongoDatabase database = myClient.getDatabase("test");
    String dbInfo = database.runCommand(new Document("buildInfo", 1)).getString("version");
    System.out.println("数据库版本:" + dbInfo);
    MongoCollection<UserEntity> myCollection = database.getCollection("user", UserEntity.class);
    myCollection.insertOne(user);
    
    这种写法是有问题的,没有指定对象的bson的编解码无法使用对象

参考下面的标准写法,只能用Document https://github.com/zfoo-project/zfoo/blob/main/orm/src/test/java/com/zfoo/orm/client/SingleMongoTest.java

那不就没用ORM了,这种写法确实取不到数据,至少能回滚。是不是初始化client的参数有问题才导致无法回滚?

frank0234 avatar Oct 17 '23 13:10 frank0234

弄个副本集再看看事务,单机看不出来

jaysunxiao avatar Oct 17 '23 14:10 jaysunxiao