使用ObjectifyService进行事务单元测试 - 没有发生回滚

时间:2017-05-22 17:37:02

标签: spring google-app-engine transactions google-cloud-datastore objectify

我们正在尝试在我们的项目中使用谷歌云数据存储,并尝试使用objectify作为ORM,因为谷歌推荐它。我已经仔细地使用并尝试了我能读到的所有内容并想到了但不知何故交易似乎不起作用。以下是我的代码和设置。

@RunWith(SpringRunner.class)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ContextConfiguration(classes = { CoreTestConfiguration.class })
public class TestObjectifyTransactionAspect {

    private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
            // Our tests assume strong consistency
            new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy(),
            new LocalMemcacheServiceTestConfig(), new LocalTaskQueueTestConfig());

    private Closeable closeableSession;

    @Autowired
    private DummyService dummyService;

    @BeforeClass
    public static void setUpBeforeClass() {
        // Reset the Factory so that all translators work properly.
        ObjectifyService.setFactory(new ObjectifyFactory());
    }

    /**
     * @throws java.lang.Exception
     */
    @Before
    public void setUp() throws Exception {
        System.setProperty("DATASTORE_EMULATOR_HOST", "localhost:8081");
        ObjectifyService.register(UserEntity.class);
        this.closeableSession = ObjectifyService.begin();
        this.helper.setUp();
    }

    /**
     * @throws java.lang.Exception
     */
    @After
    public void tearDown() throws Exception {
        AsyncCacheFilter.complete();
        this.closeableSession.close();
        this.helper.tearDown();
    }

    @Test
    public void testTransactionMutationRollback() {
        // save initial list of users
        List<UserEntity> users = new ArrayList<UserEntity>();
        for (int i = 1; i <= 10; i++) {
            UserEntity user = new UserEntity();
            user.setAge(i);
            user.setUsername("username_" + i);
            users.add(user);
        }
        ObjectifyService.ofy().save().entities(users).now();

        try {
             dummyService.mutateDataWithException("username_1", 6L);
        } catch (Exception e) {
            e.printStackTrace();
        }

        List<UserEntity> users2 = this.dummyService.findAllUsers();  

        Assert.assertEquals("Size mismatch on rollback", users2.size(), 10);

        boolean foundUserIdSix = false;
        for (UserEntity userEntity : users2) {
            if (userEntity.getUserId() == 1) {
                Assert.assertEquals("Username update failed in transactional context rollback.", "username_1",
                        userEntity.getUsername());
            }
            if (userEntity.getUserId() == 6) {
                foundUserIdSix = true;
            }
        }

        if (!foundUserIdSix) {
            Assert.fail("Deleted user with userId 6 but it is not rolledback.");
        }
    }
}

因为我使用的是spring,所以想要使用带有自定义注释的方面来编织objectify.transact围绕调用我的daos的spring服务bean方法。 但不知何故,由于ObjectifyService.ofy().save().entities(users).now();导致的更新不是gettign回滚,但异常抛出导致Objectify运行其回滚代码。我尝试打印ObjectifyImpl实例哈希码,它们都是相同的,但仍然没有回滚。

有人能帮我理解我做错了什么吗?还没有尝试过基于Web的实际设置......如果它无法通过跨国测试用例,那么Web请求场景中的实际事务使用就没有意义了。

更新:添加方面,服务,dao以及完整的图片。该代码使用spring boot。

DAO课程。注意我这里没有使用任何交易,因为根据com.googlecode.objectify.impl.TransactorNo.transactOnce(ObjectifyImpl<O>, Work<R>)的代码,在我不想要的方法中刷新并提交了跨国ObjectifyImpl。我希望承诺发生一次并全部休息以加入该交易。基本上这是com.googlecode.objectify.impl.TransactorNo中的错误代码.....我将在稍后的问题中尝试解释我的理解。

@Component
public class DummyDaoImpl implements DummyDao {

    @Override
    public List<UserEntity> loadAll() {
        Query<UserEntity> query = ObjectifyService.ofy().transactionless().load().type(UserEntity.class);
        return query.list();
    }

    @Override
    public List<UserEntity> findByUserId(Long userId) {
        Query<UserEntity> query = ObjectifyService.ofy().transactionless().load().type(UserEntity.class);
        //query = query.filterKey(Key.create(UserEntity.class, userId));
        return query.list();
    }

    @Override
    public List<UserEntity> findByUsername(String username) {
        return ObjectifyService.ofy().transactionless().load().type(UserEntity.class).filter("username", username).list();
    }

    @Override
    public void update(UserEntity userEntity) {
        ObjectifyService.ofy().save().entity(userEntity);
    }

    @Override
    public void update(Iterable<UserEntity> userEntities) {
        ObjectifyService.ofy().save().entities(userEntities);
    }

    @Override
    public void delete(Long userId) {
        ObjectifyService.ofy().delete().key(Key.create(UserEntity.class, userId));
    }
}

以下是服务类

@Service
public class DummyServiceImpl implements DummyService {

    private static final Logger LOGGER = LoggerFactory.getLogger(DummyServiceImpl.class);

    @Autowired
    private DummyDao dummyDao;

    public void saveDummydata() {
        List<UserEntity> users = new ArrayList<UserEntity>();
        for (int i = 1; i <= 10; i++) {
            UserEntity user = new UserEntity();
            user.setAge(i);
            user.setUsername("username_" + i);
            users.add(user);
        }
        this.dummyDao.update(users);
    }

    /* (non-Javadoc)
     * @see com.bbb.core.objectify.test.services.DummyService#mutateDataWithException(java.lang.String, java.lang.Long)
     */
    @Override
    @ObjectifyTransactional
    public void mutateDataWithException(String usernameToMutate, Long userIdToDelete) throws Exception {
        //update one
        LOGGER.info("Attempting to update UserEntity with username={}", "username_1");
        List<UserEntity> mutatedUsersList = new ArrayList<UserEntity>();
        List<UserEntity> users = dummyDao.findByUsername(usernameToMutate);
        for (UserEntity userEntity : users) {
            userEntity.setUsername(userEntity.getUsername() + "_updated");
            mutatedUsersList.add(userEntity);
        }
        dummyDao.update(mutatedUsersList);

        //delete another
        UserEntity user = dummyDao.findByUserId(userIdToDelete).get(0);
        LOGGER.info("Attempting to delete UserEntity with userId={}", user.getUserId());
        dummyDao.delete(user.getUserId());
        throw new RuntimeException("Dummy Exception");
    }

    /* (non-Javadoc)
     * @see com.bbb.core.objectify.test.services.DummyService#findAllUsers()
     */
    @Override
    public List<UserEntity> findAllUsers() {
        return dummyDao.loadAll();
    }

将使用ObjectifyTransactional注释的方法包装为交易工作的方面。

@Aspect
@Component
public class ObjectifyTransactionAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(ObjectifyTransactionAspect.class);

    @Around(value = "execution(* *(..)) && @annotation(objectifyTransactional)")
    public Object objectifyTransactAdvise(final ProceedingJoinPoint pjp, ObjectifyTransactional objectifyTransactional) throws Throwable {
        try {
            Object result = null;
            Work<Object> work = new Work<Object>() {
                @Override
                public Object run() {
                    try {
                        return pjp.proceed();
                    } catch (Throwable throwable) {
                        throw new ObjectifyTransactionExceptionWrapper(throwable);
                    }
                }
            };

            switch (objectifyTransactional.propagation()) {
            case REQUIRES_NEW:
                int limitTries = objectifyTransactional.limitTries();
                if(limitTries <= 0) {
                    Exception illegalStateException = new IllegalStateException("limitTries must be more than 0.");
                    throw new ObjectifyTransactionExceptionWrapper(illegalStateException);
                } else {
                    if(limitTries == Integer.MAX_VALUE) {
                        result = ObjectifyService.ofy().transactNew(work);
                    } else {
                        result = ObjectifyService.ofy().transactNew(limitTries, work);
                    }
                }
                break;
            case NOT_SUPPORTED :
            case NEVER :
            case MANDATORY :
                result = ObjectifyService.ofy().execute(objectifyTransactional.propagation(), work);
                break;
            case REQUIRED :
            case SUPPORTS :
                ObjectifyService.ofy().transact(work);
                break;
            default:
                break;
            }
            return result;
        } catch (ObjectifyTransactionExceptionWrapper e) {
            String packageName = pjp.getSignature().getDeclaringTypeName();
            String methodName = pjp.getSignature().getName();
            LOGGER.error("An exception occured while executing [{}.{}] in a transactional context."
                                , packageName, methodName, e);
            throw e.getCause();
        } catch (Throwable ex) {
            String packageName = pjp.getSignature().getDeclaringTypeName();
            String methodName = pjp.getSignature().getName();
            String fullyQualifiedmethodName = packageName + "." + methodName;
            throw new RuntimeException("Unexpected exception while executing [" 
                                            + fullyQualifiedmethodName + "] in a transactional context.", ex);
        }
    }
}

现在我看到的问题代码部分如下com.googlecode.objectify.impl.TransactorNo

@Override
public <R> R transact(ObjectifyImpl<O> parent, Work<R> work) {
    return this.transactNew(parent, Integer.MAX_VALUE, work);
}

@Override
public <R> R transactNew(ObjectifyImpl<O> parent, int limitTries, Work<R> work) {
    Preconditions.checkArgument(limitTries >= 1);

    while (true) {
        try {
            return transactOnce(parent, work);
        } catch (ConcurrentModificationException ex) {
            if (--limitTries > 0) {
                if (log.isLoggable(Level.WARNING))
                    log.warning("Optimistic concurrency failure for " + work + " (retrying): " + ex);

                if (log.isLoggable(Level.FINEST))
                    log.log(Level.FINEST, "Details of optimistic concurrency failure", ex);
            } else {
                throw ex;
            }
        }
    }
}

private <R> R transactOnce(ObjectifyImpl<O> parent, Work<R> work) {
    ObjectifyImpl<O> txnOfy = startTransaction(parent);
    ObjectifyService.push(txnOfy);

    boolean committedSuccessfully = false;
    try {
        R result = work.run();
        txnOfy.flush();
        txnOfy.getTransaction().commit();
        committedSuccessfully = true;
        return result;
    }
    finally
    {
        if (txnOfy.getTransaction().isActive()) {
            try {
                txnOfy.getTransaction().rollback();
            } catch (RuntimeException ex) {
                log.log(Level.SEVERE, "Rollback failed, suppressing error", ex);
            }
        }

        ObjectifyService.pop();

        if (committedSuccessfully) {
            txnOfy.getTransaction().runCommitListeners();
        }
    }
}

transactOnce是由代码/设计总是使用单个事务来做事情。它将提交或回滚事务。链接交易没有像普通企业应用程序那样需要....服务 - &gt;在单个事务中调用多个dao方法,并根据事物的外观进行提交或回滚。

记住这一点,我在我的dao方法中删除了所有注释和transact方法调用,这样它们就不会启动显式事务,服务中的方面将服务方法包装在事务中并最终在transactOnce中...所以基本上服务方法在事务中运行,并且没有再次触发新事务。这是一个非常基本的场景,在实际生产中,应用程序服务可以调用其他服务方法,并且它们可能在它们上有注释,我们仍然可以最终处于链式事务中。但无论如何......这是一个不同的问题需要解决。 ...

我知道NoSQL不支持表或表间级别的写一致性,所以我从谷歌云数据存储区要求太多了吗?

0 个答案:

没有答案