1

I have looked at several examples and don't understand what I am doing differently; however, this is not working for me. Here is a simplified description of my code, with code snippets, to illustrate what is not working.

I have a main transaction table that hold one record for each application transaction. I have a linked table that holds images from the transactions with a foreign key pointing back to the master transaction record. Nothing too strange or fancy here. (Full disclosure: There are two other linked tables, but I am certain that the fix for one table will work for all of them.)

My code creates a RESTful endpoint like this:

@Controller
@RequestMapping("/feed/v1")
public class ReportingDataFeedControllerV1 {
    
    @Resource(name = "dataFeedService")
    private DataFeedService dataFeedService;
    
    @PostMapping(value = "/myend", consumes = "application/json", produces = "application/json")
    public @ResponseBody DataFeedResponse DataFeed(@RequestBody DataFeedRequest request) {
        
        DataFeedResponse response = new DataFeedResponse();

        try {
        dataFeedService.saveData(request);

DataFeedService is an interface that defines a saveData method and gets implemented by DataFeedServiceImpl:

public class DataFeedServiceImpl implements DataFeedService {
    
    @Resource(name = "dataFeedRepository")
    private DataFeedRepository dataFeedRepository;

    @Override
    //@Transactional        // Outer @Transactional commented
    /**
     * Call the save method on the repository implementation class.
     * 
     * @param request - DataFeedRequest object that contains the data to be saved
     * @throws Exception
     * @author SmithDE
     */
    public void saveData(DataFeedRequest request) throws Exception {
        dataFeedRepository.saveData(request);
    }

DataFeedRepository is another interface that gets implemented by DataFeedRepositoryImpl:

public class DataFeedRepositoryImpl implements DataFeedRepository {
    private static final int REF_SS_MFA = 4;

    @Resource(name="rdpJdbcTemplate")
    private JdbcTemplate jdbcTemplate;
    

So, there are at least two levels of classes between the method that starts this action and the real call to saveData().

My method, in class DataFeedRepositoryImpl, that saves transaction data to the database tables is annotated with @Transactional, like this: (It also includes @Override because it is the implementation of a interface from a base class.)

@Override
@Transactional           // Nested @Transactional
/**
 * Method that performs the save of the data to the database tables.
 * 
 * @param request - Request object containing the data to be saved
 * @exception Exception
 * @author SmithDE
 */
public void saveData(DataFeedRequest request) throws Exception {

The method builds an INSERT statement for the main transaction table. It needs to retrieve the new primary key, so it calls the update method of JdbcTemplate like this:

        KeyHolder keyHolder = new GeneratedKeyHolder();
        int newRowCount = jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection
                    .prepareStatement(thisquery, Statement.RETURN_GENERATED_KEYS);
                    return ps;
                  }, keyHolder);
        id = (long) keyHolder.getKey();

Next it builds a series of INSERT statements for the images associated with this transaction and sends them to the database in separate calls to JdbcTemplate.update(), like this:

    for (DataFeedImage image : images) {
        ...
            newRowCount = jdbcTemplate.update(thisquery);
    }

My expectation is that an error in any of the calls to insert images into the linked image table will cause the entire database transaction to rollback. However, my observation is different:

  • The main transaction data record is still there in the main transaction table.
  • Any images that were inserted before the error are still there in the image table.

I want all of these calls to update() to be part of the same database transaction, but they clearly are not.

Please help me understand what I am doing wrong.

Just a thought as I was writing this edit. Could it be a problem that the method in the previous implementation class is annotated as @Transactional and the method that actually makes the JdbcTemplate.update() calls is also annotated as @Transactional? Could this nested declaration of @Transactional cause a problem?

dacDave
  • 232
  • 1
  • 12
  • You're not using MyISAM tables by accident are you (as they don't support transactions and you're on MySQL and that would be the easiest explanation)? Otherwise the behaviour should be the one you're expecting, and full code and more debug info will be required. – Kayaman Feb 10 '21 at 20:21
  • All tables were created to use the InnoDB storage engine. – dacDave Feb 10 '21 at 21:21
  • 1
    Where do you call `saveData` from? A different component and not from a method inside the same class? If calling from the same class, it would prevent the `@Transactional` attribute from taking effect resulting in this sort of behaviour. – Kayaman Feb 10 '21 at 21:29
  • @Kayaman Yup, smells exactly like a self-call. – chrylis -cautiouslyoptimistic- Feb 10 '21 at 22:00
  • I really hope that you are right, but I don't see it. – dacDave Feb 11 '21 at 00:32
  • I have added more detail to show the hierarchy of classes leading up to the call to my saveData class method. This method does get called within the same @Controller module (same Component?) but not from within the same Java class. – dacDave Feb 11 '21 at 01:18
  • I was excited because this looked like the answer. My saveData method in DataServiceImpl is marked with Transactional. It calls my saveData method in DataRepositoryImpl, which is also marked with Transactional. So it did look like a self-call. However, I removed the Transactional from the upper method and re-ran the test. Same result. Neither the main transaction record nor the image records before the faulty one get rolled back. I will update the sample code to reflect my change. – dacDave Feb 11 '21 at 13:29
  • Nested `@Transactional` is normal to denote transactional boundaries, and while they can be done wrong, in this case there seems to be nothing out of the ordinary. The next step is to enable logging for the transactions, so [this](https://stackoverflow.com/q/1965454/2541560) is a good place to start. A self-call is described [here](https://stackoverflow.com/q/34197964/2541560), you don't have that problem. – Kayaman Feb 11 '21 at 13:46

1 Answers1

0

This is a resolution but NOT an answer!

The decision was may above my pay grade that this application will be re-architected and rewritten such that it uses the Java Persistence API (JPA) and not JdbcTemplates.

Will that have the same database transaction problems? We'll see.

This issue is closed.

Thank you to those who offered suggestions to this question. I would really like to fine the solution here, but that is not to be.

dacDave
  • 232
  • 1
  • 12