Spring data updates entity without transaction

Viewed 4280

I have simple code example with Spring Data:

Entity

@Entity
@Table(name = "car")
public class Car {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
//...
}

Repository:

@Repository
public interface CarRepository extends JpaRepository<Car, Long> {
}

Service with example methods:

@Service
public class CarService {

    @Autowired
    private CarRepository carRepository;

    public void change() {
        Car one = carRepository.findOne(1L);
        one.setName("changed"); //important
    }
}

And controller:

@RestController("/")
public class CarController {

    @Autowired
    private CarRepository carRepository;

    @Autowired
    private CarService carService;

    @GetMapping
    public List<Car> home() {
        carService.change();
        List<Car> all = carRepository.findAll();

        return all;
    }

}

I also have data.sql with 3 records:

insert into car values (1, 'aaa');
insert into car values (2, 'bbb');
insert into car values (3, 'ccc');

When I send request to the localhost:8080 I get list with three cars, where the first one is updated:

insert into car values (1, 'changed');
insert into car values (2, 'bbb');
insert into car values (3, 'ccc');

My expectation is that after calling change() from controller actually nothing happens because this method is not transactional. So why my home() method in controller returns list with updated entity?

This question is completly different from this Spring data jpa @transactional I don't understand why you mark this as duplicate.

4 Answers

I assume, that you are using Spring Boot. If you use Spring Boot with Spring MVC and Spring Data, by default a Hibernate Session will be opened every time your controller methods receive a request. A Session provides a physical connectivity between your application and database. This functionality can be controlled with the spring.jpa.open-in-view application property.

Car one = carRepository.findOne(1L); //1
one.setName("changed"); //2
List<Car> all = carRepository.findAll(); //3
return all;
  1. A session will be open, when the program reaches the code above. On the first line, the Car object is retrieved from the DB, and the object is stored in the First Level Cache.
  2. As the "one" reference points to the same object, that is in the cache, at line 2 the cached objects name changes.
  3. On the third line we get the cached value for the object, that we retrieved earlier, and that value is returned at the end.

Since the modified value is never flushed to the DB, after your controller call you can check your DB, and you will see, that the old value remained for your object, despite you received a different value in the controller response.

If you set the spring.jpa.open-in-view property to false, your controllers will return the "correct" value.

According to the Spring Data documentation methods are transactional!

4.7. Transactionality

CRUD methods on repository instances are transactional by default. For reading operations the transaction configuration readOnly flag is set to true, all others are configured with a plain @Transactional so that default transaction configuration applies. For details see JavaDoc of SimpleJpaRepository.

Read the full documentation here: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions

A) The default behavior of a Spring Data JPARepository is to return a Attached Entity. If you perform an update on that attached object within the same persistent session then the changes to the persistent state are updated when the session is flushed.

B) The methods on a Spring Data Repository by default are transactional. The default settings are:

 propagation  REQUIRED
 isolation    DEFAULT
 timeout      -1
 read-only    false

C) Lastly your home() method marks the beginning and end of the transaction and JPA session. Once the session is used the TransactionManager creates the session. Once the method completes the transaction is committed or rolled back and the JPA session is flushed and closed.

I hope that helps explain your result.

Related