JPA-在多个JpaRepository方法调用上生成事务

时间:2018-12-16 12:18:56

标签: spring-boot spring-data-jpa

我正在使用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调用的(这里的主要方法是updateChairsdoUpdate):

@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为存储库接口中使用的每个(预定义)方法打开新事务?或者,如果不可能的话,如何“强制”事务管理器重用服务中打开的事务以使完全回滚成为可能?

1 个答案:

答案 0 :(得分:0)

嗨,我发现这份看起来对您有帮助的文档:

Publish Build Artifacts

接下来是上一个网站的示例:

@Configuration
**@ComponentScan
@EnableTransactionManagement**
public class AppConfig {
 ....
}

然后我们可以使用这样的事务:

@Service
public class MyExampleBean{

**@Transactional**

public void saveChanges() {
    **repo.save(..);
    repo.deleteById(..);**
    .....
}

}