DEV Community

loading...
Cover image for Transaction in Spring Boot as simple as possible

Transaction in Spring Boot as simple as possible

fabiothiroki profile image Fabio Hiroki ・2 min read

Introduction

A few days ago I had to investigate a bug in production that involved a database transaction, specifically defined by
@Transaction annotation. That sounded like a great opportunity to review the basic concepts and hopefully fix the bug.

No bug yet

Before the bug was introduced, this is how the application looked like: an endpoint that received some payload and then performed write on different tables in the same transactional annotation, using its respective dependencies:

@Autowired
private TableOneService tableOneService;

@Autowired
private TableTwoService tableTwoService;


@PostMapping("/")
@Transactional
public void save(@RequestBody Payload payload) {
  tableOneService.save(payload);
  tableTwoService.save(payload);
}
Enter fullscreen mode Exit fullscreen mode

So far, so good, the application was behaving as expected.

Requirements change

After observing the data persisted in table two, we've decided to change the business rule, validate the payload persisted based on its values. If you don't know the entire application and just focus on changing the tableTwoService code based on these specific requirements, this is a possible solution that you may consider:

  1. Add some king of validation on save method
  2. Throw an exception that will be translated to a Bad Request response to the client.

Don't feel guilty if you've considered this solution, we've also done this and deployed it to production.

This is what the code looked like now. As developers, we're so wrongly proud.

public class TableTwoService {
  public void save(Payload paylod) {
    if (isValid(payload)) {
     // persists on database
    } else {
     // throw some exception
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Bug detected

After some days, someone raised the hand:

"Hey something feels wrong, we're missing some data on table one since the deployment of the validation code".

Damn, we didn't even change the tableOneService neither the API controller code, for sure this is someone else's problem.

Database Transaction

In short words, this is what defines the behavior of a method annotated by @Transaction:

  1. Begin the transaction.
  2. Execute a set of data manipulations and/or queries.
  3. If no error occurs, then commit the transaction.
  4. If an error occurs, then roll back the transaction.
@Transactional
public void save(@RequestBody Payload payload) {
  // Both operations should work for transaction commit
  // Otherwise no operation will persist
  tableOneService.save(payload);
  tableTwoService.save(payload);
}
Enter fullscreen mode Exit fullscreen mode

Besides the code of tableOneService was executing without errors on runtime, the exception thrown on tableTwoService was rollbacking its persistence.

Let's fix the bug

The solution I've decided to use was to remove the exception thrown and instead just log the payload received so I could have better observability of this flow. An exception now won't trigger the transaction rollback.

In this case, my solution works because it's ok for the client that sent this request to not receive a Bad Request response whenever it sends an invalid payload, and just have its payload ignored.

public class TableTwoService {
  public void save(Payload paylod) {
    if (isValid(payload)) {
     // persists on database
    } else {
     // log a message and do nothing
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The end

The bug is now fixed and I've learned a little more about the @Transaction annotation. I hope this story helps someone else in the future.

Discussion (4)

pic
Editor guide
Collapse
khmarbaise profile image
Karl Heinz Marbaise

Are you aware of using the spring.jpa.open-in-view property which is by default true? This creates an transaction over each rest call...
more details here:
stackoverflow.com/questions/305494...

Collapse
fabiothiroki profile image
Fabio Hiroki Author

I didn't know about this property! Thanks for the information!

Collapse
hallsamuel90 profile image
Sam Hall

Just curious, are table 1 and 2 services always used together like this?

Collapse
fabiothiroki profile image
Fabio Hiroki Author

No, not always, some other endpoint uses persists only in table 2.