springjpaspring-data-jpatransactionmanager

Spring TransactionManager behavior with Spring Data and JpaRepository


I have a controller which does the following

  1. A submit end point which save an entry in db and then call some external service asynchronously
  2. Track the update of asynchronous call (this call updates an associated table) by watching the db and update the status of the entry created in step one

I was using the @Query Annotation to verify if step one entry exist in db and it was always returning empty. I tried changing it to the default spring method and it starts returning the inserted value.

I read about proxies, @Transactional and how non CRUD methods in a JPARepository are non transactional and tried few things like transaction propagation and self injection and even explicitly marking the repo method @Transactional. But none of them fixed the issue. Using spring data method solved it but I still don't understand what happened. Can someone help with an explanation of this behavior.

Basic code snippet is below

MyController

@RestController
public class MyController {

    private final MyService myService;
    private final MyRepository myRepository;


    @Autowired
    public MyController(MyService myService,
                        MyRepository myRepository) {
        this.myService = myService;
        this.myRepository = myRepository;
    }

    @PostMapping(value = "/submit")
    public ResponseEntity<MyResponse> submit(@Valid @RequestBody MyRequest myRequest) {
        return ResponseEntity
                .accepted()
                .body(MyResponse.success(myService.submit(myRequest), "SUBMITTED"));
    }

    /**
     * This method is to update the status of the entry created by /submit endpoint
     * if the asynchoronous process triggered by submit endpoint update an associated table
     */
    @PostConstruct
    private void trackUpdates() {
        ..
        someObserver.subscribe(trackedAssociatedEntity -> {
            myService.trackAndUpdateBasedOnAssociatedEntity(trackedAssociatedEntity);
        });
    }

}

MyService

@Service
@Transactional
public class MyService {


    private final MyRepository myRepository;


    @Autowired
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }


     submit(MyRequest myRequest) {
         myRepository.save(myEntity);
         //makes that asynchronous call
    }
    public void trackAndUpdateBasedOnAssociatedEntity(@NotNull MyAssociatedEntity myassociatedEntity) {
        //  This commented call always return empty but the uncommented code works as expected
        //        List<MyEntity> existingEntity =
        //                myRepository.findEntityByField1AndField2(myassociatedEntity.getField1(),myassociatedEntity.getField2());

        List<MyEntity> existingEntities =
                        myRepository.findByField1AndField2(myassociatedEntity.getField1(),myassociatedEntity.getField2());

        if(existingEntities.isEmpty()){
                    //create new
                }else{
                    //update
                }
            }
        }
    }
}

MyRepository

@Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {

    @Query("SELECT e FROM MyEntity e WHERE e.field1 = ':field1' and e.field2 = ':field2' ")
    List<MyEntity> findEntityByField1AndField2(String field1, String field2);

    List<MyEntity> findByField1AndField2(String field1, String field2);
}

Solution

  • I believe that '' are not needed. Please try the following:

    @Repository
    public interface MyRepository extends JpaRepository<MyEntity, Long> {
    
        @Query("SELECT e FROM MyEntity e WHERE e.field1 = :field1 and e.field2 = :field2")
        List<MyEntity> findEntityByField1AndField2(String field1, String field2);
    
        List<MyEntity> findByField1AndField2(String field1, String field2);
    }