Search code examples
hibernatespring-bootspring-data-jpaspring-transactions

Spring - @Transactional across multiple services and repositories


I am building an application with Spring Boot v2.1.3.RELEASE and Hibernate v5.3.6.

What I wish to accomplish is using the @Transactional annotation on my services to ensure that when deleting a record, if an error occurs, either database or business logic exception, that any deleted records from the transaction are rolled back to its original state before the delete began.

The service where I want the transaction to begin is MasterSpecService and call the other services like so:

deleteMasterSpec(MasterSpec masterSpec) {
     this.approvalTargetService.deleteApprovalTargetsForSpec(masterSpec.getSysI());
     this.approvalToleranceService.deleteApprovalTolerancesForSpec(masterSpec.getSysI());
     this.receivingToleranceService.deleteReceivingTolerancesForSpec(masterSpec.getSysI());
     this.finishedGoodTargetService.deleteFinishedGoodTargetsForSpec(masterSpec.getSysI());
     this.finishedGoodToleranceService.deleteFinishedGoodTolerancesForSpec(masterSpec.getSysI());
     this.maxValueService.deleteMaxValuesForSpec(masterSpec.getSysI());
     this.ingredientService.deleteSpecIngredients(masterSpec.getSysI());
     this.masterSpecRepo.deleteById(masterSpec.getSysI()); 
     // throw some exception here as a test, and have all the deletes rollback
}

In the database, tables for targets, tolerances, max values and ingredients all have foreign key references to the MasterSpec table, so I delete the MasterSpec last.

I want the MasterSpecService to be the main Transaction, and have all other services attach themselves to that transaction, so if anything fails it will revert. All services have their own JPA Repository handling the deletes.

Is this possible to accomplish with the @Transactional annotation?

Or, will using @OneToMany and @ManyToOne mappings inside my various Entity classes accomplish the same task and revert any deleted records should any part of the transaction fail when I call delete on the MasterSpec record?,


Solution

  • After some more digging, a more proper way is as I mentioned at the end of my question, using bi-directional mappings with @OneToOne, @OneToMany and @ManyToOne annotations on my classes.

    In my Entity class PMasterSpec.java, I added:

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "masterSpec")
    private PApprovalTarget approvalTarget;
    
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "masterSpec")
    private PApprovalTolerance approvalTolerance;
    
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "masterSpec")
    private PReceivingTolerance receivingTolerance;
    
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "masterSpec")
    private PFinishedGoodTarget finishedGoodTarget;
    
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "masterSpec")
    private PFinishedGoodTolerance finishedGoodTolerance;
    
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "masterSpec")
    private PMaxValue maxValue;
    
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "masterSpec")
    private List<PIngredient> ingredients;
    

    And in the PApprovalTarget, PApprovalTolerance, PReceivingTolerance, PFinishedGoodTarget, PFinishedGoodTolerance, PMaxValue entity classes, I mapped back to the PMasterSpec class by the foreign key:

    @OneToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "BOM_SPEC_SYS_I")
    private PMasterSpec masterSpec;
    

    And in the PIngredient class:

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "BOM_SPEC_SYS_I", insertable = false, updatable = false)
    private PMasterSpec masterSpec;
    

    My MasterSpecService is still marked as transactional:

    @Transactional(propagation = Propagation.REQUIRED)

    But all the other services are not.

    Now, as long as I build the PMasterSpec object and provide all the other nested entity classes, when i call masterspecRepo.save(masterSpec) it will create a single transaction to save all entity objects, and rollback everything on save or delete should an issue occur.

    I followed these examples:

    Spring Boot JPA One to Many Mapping

    Spring Boot JPA One to One Mapping