fix(personal): require outer transaction for reservation

This commit is contained in:
2026-07-12 02:26:01 +08:00
parent cdc47ee97e
commit 655d784beb
2 changed files with 65 additions and 1 deletions
@@ -6,6 +6,7 @@ import org.dromara.common.core.exception.ServiceException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@@ -46,7 +47,7 @@ public class PersonalSpaceService {
*
* @return the locked personal space id
*/
@Transactional
@Transactional(propagation = Propagation.MANDATORY)
public long reserve(PersonalOwner owner, long bytes) {
if (bytes < 0 || properties.getMaxItems() <= 0 || properties.getMaxSpaceMb() <= 0) {
throw new ServiceException(QUOTA_EXCEEDED);
@@ -6,8 +6,17 @@ import org.dromara.aihr.personal.support.PersonalOwner;
import org.dromara.common.core.exception.ServiceException;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.mockito.InOrder;
import java.util.Map;
@@ -188,6 +197,35 @@ class PersonalSpaceServiceTest {
verifyNoInteractions(jdbc);
}
@Test
void reserveRequiresAnExistingOuterTransactionThroughTheSpringProxy() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
PersonalSpaceService target = new PersonalSpaceService(jdbc, properties());
TestTransactionManager transactionManager = new TestTransactionManager();
ProxyFactory factory = new ProxyFactory(target);
factory.setProxyTargetClass(true);
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionManager(transactionManager);
interceptor.setTransactionAttributeSource(new AnnotationTransactionAttributeSource());
interceptor.afterPropertiesSet();
factory.addAdvice(interceptor);
PersonalSpaceService proxy = (PersonalSpaceService) factory.getProxy();
PersonalOwner owner = new PersonalOwner("000000", 101L, null);
assertThrows(IllegalTransactionStateException.class, () -> proxy.reserve(owner, 1L));
verifyNoInteractions(jdbc);
when(jdbc.queryForMap(contains("from aihr_personal_space"), eq("000000"), eq(101L)))
.thenReturn(space(7L, 1024L, 0L, 0));
Long spaceId = new TransactionTemplate(transactionManager)
.execute(status -> proxy.reserve(owner, 1L));
assertEquals(7L, spaceId);
verify(jdbc).update(contains("on duplicate key update"),
eq("000000"), eq(101L), eq(null), eq(500L * 1024 * 1024));
verify(jdbc).queryForMap(contains("for update"), eq("000000"), eq(101L));
}
private static Map<String, Object> space(long id, long quota, long used, int count) {
return Map.of(
"id", id,
@@ -200,4 +238,29 @@ class PersonalSpaceServiceTest {
private static PersonalKnowledgeProperties properties() {
return new PersonalKnowledgeProperties();
}
private static final class TestTransactionManager extends AbstractPlatformTransactionManager {
@Override
protected Object doGetTransaction() {
return new Object();
}
@Override
protected boolean isExistingTransaction(Object transaction) {
return TransactionSynchronizationManager.isActualTransactionActive();
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
}
@Override
protected void doRollback(DefaultTransactionStatus status) {
}
}
}