我正在使用SpringBoot 2.x和SpringData-JPA通过CrudRepository访问数据库。
基本上,我想调用CrudRepository的方法来更新或保留数据。在一个用例中,我想在插入新元素之前从数据库中删除较旧的条目(出于本示例的简短性,假设:从表中删除所有条目)。 如果由于某种原因持久化新元素失败,则应回滚删除操作。
但是,主要的问题似乎是为CrudRepository中调用的每个方法打开了新事务。即使通过调用服务中的方法打开了一个事务。我无法使用存储库方法来使用现有事务。
Getting transaction for [org.example.jpatrans.ChairUpdaterService.updateChairs]
Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
我尝试使用其他传播方式。 (必需,支持,强制性)在不同方法(服务/存储库)上无效。
将方法@Transactional
注释更改为@Transactional(propagation = Propagation.NESTED)
听起来只能这样做,但无济于事。
JpaDialect does not support savepoints - check your JPA provider's capabilities
我可以直接使用EntityManager实现预期的行为吗,不能?
我还希望避免也必须使用本机查询。
有什么我忽略的东西吗?
出于演示目的,我创建了一个非常简洁的示例。 完整的示例可以在https://gitlab.com/cyc1ingsir/stackoverlow_jpa_transactions
中找到以下是主要(甚至更简化)的细节:
首先,我定义了一个非常简单的实体:
@Entity
@Table(name = "chair")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Chair {
// Not auto generating the id is on purpose
// for later testing with non unique keys
@Id
private int id;
@Column(name = "legs", nullable = false)
private Integer legs;
}
通过CrudRepository建立与数据库的连接:
@Repository
public interface ChairRepository extends CrudRepository<Chair, Integer> {
}
这是从另一个bean调用的(这里的主要方法是updateChairs
和doUpdate
):
@Slf4j
@Service
@AllArgsConstructor
@Transactional
public class ChairUpdater {
ChairRepository repository;
/*
* Initialize the data store with some
* sample data
*/
public void initializeChairs() {
repository.deleteAll();
Chair chair4 = new Chair(1, 4);
Chair chair3 = new Chair(2, 3);
repository.save(chair4);
repository.save(chair3);
}
public void addChair(int id, Integer legCount) {
repository.save(new Chair(id, legCount));
}
/*
* Expected behaviour:
* when saving a given chair fails ->
* deleting all other is rolled back
*/
@Transactional
public void updateChairs(int id, Integer legCount) {
Chair chair = new Chair(id, legCount);
repository.deleteAll();
repository.save(chair);
}
}
我要实现的目标由以下两个测试用例证明:
@Slf4j
@RunWith(SpringRunner.class)
@DataJpaTest
@Import(ChairUpdater.class)
public class ChairUpdaterTest {
private static final int COUNT_AFTER_ROLLBACK = 3;
@Autowired
private ChairUpdater updater;
@Autowired
private ChairRepository repository;
@Before
public void setup() {
updater.initializeChairs();
}
@Test
public void positiveTest() throws UpdatingException {
updater.updateChairs(3, 10);
}
@Test
public void testRollingBack() {
// Trying to update with an invalid element
// to force rollback
try {
updater.updateChairs(3, null);
} catch (Exception e) {
LOGGER.info("Rolled back?", e);
}
// Adding a valid element after the rollback
// should succeed
updater.addChair(4, 10);
assertEquals(COUNT_AFTER_ROLLBACK, repository.findAll().spliterator().getExactSizeIfKnown());
}
}
更新:
如果不是从CrudRepository或JpaRepository扩展存储库,而是从普通存储库扩展存储库,则显式定义所有需要的方法似乎是可行的。对我来说,这似乎是一种解决方法,而不是适当的解决方案。
它的问题归结为:是否可以防止SimpleJpaRepository为存储库接口中使用的每个(预定义)方法打开新事务?或者,如果不可能的话,如何“强制”事务管理器重用服务中打开的事务以使完全回滚成为可能?
答案 0 :(得分:0)
嗨,我发现这份看起来对您有帮助的文档:
接下来是上一个网站的示例:
@Configuration
**@ComponentScan
@EnableTransactionManagement**
public class AppConfig {
....
}
然后我们可以使用这样的事务:
@Service
public class MyExampleBean{
**@Transactional**
public void saveChanges() {
**repo.save(..);
repo.deleteById(..);**
.....
}
}