0

I've got the following case:

  1. Start transaction
  2. Fetch object A
  3. Update A
  4. Commit transaction
  5. Perform a long task with A
  6. Update A (not necessary within transaction, but it would be nice)

How to accomplish that? I do not want to have locked table during step 5. Steps 1-4 are expected to behave like "SELECT FOR UPDATE". Below is my current code. The method I'm executing is execute(). I'm testing it by executing it from different application instances and I'm checking, if instance A is able to do operations on table while instance B is executing executeJob(job).

@Service
@Slf4j
@Transactional
public class JobExecutionService {

    private final Environment environment;
    private final TestJobRepository testJobRepository;
    private final TestJobResultRepository testJobResultRepository;

    @Autowired
    public JobExecutionService(Environment environment, TestJobRepository testJobRepository, TestJobResultRepository testJobResultRepository) {
        this.environment = environment;
        this.testJobRepository = testJobRepository;
        this.testJobResultRepository = testJobResultRepository;
    }

    public void execute() {
        TestJob job = getJob();
        executeJob(job);
        finishJob(job);
    }

    @Transactional
    public TestJob getJob() {
        TestJob testJob = testJobRepository.findFirstByStatusOrderByIdAsc(
                0
        );
        testJob.setStatus(1);
        testJobRepository.save(testJob);
        return testJob;
    }

    public void executeJob(TestJob testJob) {
        log.debug("Execution-0: {}", testJob.toString());
        Random random = new Random();
        try {
            Thread.sleep(random.nextInt(3000) + 1000);
        } catch (InterruptedException e) {
            log.error("Error", e);
        }
        log.debug("Execution-1: {}", testJob.toString());
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void finishJob(TestJob testJob) {
        testJobResultRepository.save(
                new TestJobResult(
                        null,
                        testJob.getId(),
                        environment.getProperty("local.server.port")
                )
        );
    }

}

public interface TestJobRepository extends PagingAndSortingRepository<TestJob, Long>, QueryDslPredicateExecutor<TestJob> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    TestJob findFirstByStatusOrderByIdAsc(Integer status);

}
Mateusz Stefaniak
  • 856
  • 1
  • 9
  • 20
  • After your edit, now `executeJob(job);` and `finishJob(job);` are different transactions, so what's your questions now? – hagrawal7777 May 03 '17 at 18:04
  • Why do you annotate the class and the individual methods with "@Transactional". Adding "@Transactional" to the class means that it applies to each method. – Ueli Hofstetter May 03 '17 at 18:07
  • At class level you need to have `@EnableTransactionManagement` annotation. – hagrawal7777 May 03 '17 at 18:11
  • I'v added some info to this post. @UeliHofstetter Without `@Transaction` on the class I have "javax.persistence.TransactionRequiredException: no transaction is in progress". @hagrawal I have `@EnableTransactionManagement` in my main class, so I think I do not need it in separate classes. @hagrawal I think it should behave this way I want but while I'm testing it, it seems not. – Mateusz Stefaniak May 03 '17 at 18:14
  • It doesn't make sense to annotate private methods with @Transactional. http://stackoverflow.com/questions/4396284/does-spring-transactional-attribute-work-on-a-private-method (at least, in proxy mode). The only one public method you have here is `execute()`, so you could either annotate it or the class as a whole. – Roman Puchkovskiy May 03 '17 at 18:33
  • @RomanPuchkovskiy Ok, so I changed these methods to be public. – Mateusz Stefaniak May 03 '17 at 18:42

1 Answers1

5

@Transactional on private methods does not work in proxy mode: Does Spring @Transactional attribute work on a private method? The only one public method you have here is execute(), so you could either annotate it or the class as a whole.

(By the way, if you make getJob() and finishJob() public, @Transactional will still not work for them as they are called from execute() method via this and not via proxy generated by transaction management infrastructure).

You could drop @Transactional from your JobExecutionService, move getJob() and finishJob() (or whatever methods should run in independent transactions) as public methods to a new service like TransactionalJobExecutionService and annotate that new service as @Transactional.

Community
  • 1
  • 1
Roman Puchkovskiy
  • 11,415
  • 5
  • 36
  • 72