Transaction in Spring Boot as simple as possible
Tags: java, database, programming, webdev
Published: 2021-01-24 14:09:14

# Introduction

A few days ago I had to investigate a bug in production that involved a database transaction, specifically defined by 
[`@Transaction`](https://spring.io/guides/gs/managing-transactions/) 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:

```java
@Autowired
private TableOneService tableOneService;

@Autowired
private TableTwoService tableTwoService;


@PostMapping("/")
@Transactional
public void save(@RequestBody Payload payload) {
  tableOneService.save(payload);
  tableTwoService.save(payload);
}
```

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.

```java
public class TableTwoService {
  public void save(Payload paylod) {
    if (isValid(payload)) {
     // persists on database
    } else {
     // throw some exception
    }
  }
}
```

# Bug detected
![](https://media.giphy.com/media/xTiTnGuHmcaQeWSryE/giphy.gif)

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. 

```java
@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);
}
```
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
![](https://media.giphy.com/media/Wsju5zAb5kcOfxJV9i/giphy.gif)

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.

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

# 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.