javahibernatetransactionsdistributed-transactions

Transactions Across Microservices


I have two entities:

Entity A:

private Long idA;

Entity B:

private Long idB;
private Long fkA;

My Micro Service can save, modify or delete only the B entity :

Repository:

@Repository
public interface BRepository extends CrudRepository<B, Long>{

}

Feign External Service:

@FeignClient(value = "aExtClient", url = "${A_EXT_MS_URL}")
public interface AExtRemoteService {

    @DeleteMapping(path = "/a/{idA}", produces = MediaType.APPLICATION_JSON_VALUE)
    public Esito deleteA(@PathVariable(name = "idA") Long idA);

}

ServiceImpl:

@Component
@Transactional(rollbackFor = BusinessServiceException.class)
public class BServiceImpl implements BService { 
    
    @Autowired
    private BRepository bRepository;
    
    @Autowired
    private AExtRemoteService aExtRemoteService;
    
    @Override
    public void deleteB(B b) {        
        
        Long idA = b.getFkA();
        
        bRepository.delete(b); //marker
        
        if(idA  != null) {
            aExtRemoteService.deleteA(idA);
        }
    }
    
}

External service can save, modify or delete only the A entity :

Controller:

@RestController
@RequestMapping("/a")
@CrossOrigin(origins = "${mocks.allowedCrossOrigins:*}")
public class AServiceMockController {

    @Autowired
    private AMockService aMockService;
    
    @DeleteMapping(value = {
            "/{idA}" }, produces = MediaType.APPLICATION_JSON_VALUE)
    public @ResponseBody ResponseEntity<Result> deleteA(@PathVariable(name = "idA") Long idA) {
        Result result = new Result();
        aMockService.deleteA(idA);
        result.setResult(true);
        result.setCod(String.valueOf(HttpStatus.OK.value()));
        result.setMess(HttpStatus.OK.name());
        return new ResponseEntity<>(result, HttpStatus.OK);
    }

}

Service:

@Service
public class AMockService {

    private final ARepository aRepository;    

    @Autowired
    public AMockService(ARepository aRepository) {
        this.aRepository = aRepository;
    }
    
    public void deleteA(Long idA) {
        A byId = aRepository.findById(idA).orElseThrow(NoSuchElementException);
        aRepository.delete(byId);
    }

}

Repository:

@Repository
public interface ARepository extends CrudRepository<A, Long>{

}

On calling deleteB, the external service has returned this message:

Caused by: java.sql.SQLIntegrityConstraintViolationException: ORA-02292:  integrity constraint violated (X.YY) - child record found

How can I solve? Seems like it's not taking into consideration bRepository.delete(b);

P.S. I have access to the external service, so if needed I can modify it.


Solution

  • You cannot have transactions like this across microservices - one because you are using a remote http call to service A and it is by nature not capable in participating in transaction you are initiating in service B. The transaction you are using is resource-local transaction. Second you are sharing a database in both the services and the remote http service is trying to delete a record which has a foreign key relation as a parent. Since you are using Spring transaction manager you can use a TransactionEventListener ( see here to an example usage) to invoke deleting A after your transaction in B has finished successfully. There are other patterns to solve transactions across microservices like Saga