Issue
I have the following code in a Spring boot integration test class:
@Autowired
private AddressRepository addressRepository;
// other Repositories that all extend CrudRepository
@BeforeEach
void init(){
Address address = new Address();
// Set up address
address = addressRepository.save(address); //<-- address properly persisted?
Building building = new Building();
building.setAddress(address);
buildingRepository.save(building); //<-- throws error
}
where
@Entity
class Building {
@ManyToOne(fetch = FetchType.LAZY, optional = false)
Address address;
//...
}
and the pom.xml:
//...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>//<-- upping the version breaks things
<relativePath/>
</parent>
//...
which runs smooth on Spring Boot 2.6.7. After upgrading to 2.7.2 however, saving the building
now throws a org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation
. If I understood correctly, Spring thinks that address
is not yet persisted and thus cannot store its reference in the building
. But it is already persisted in line two of init
?
What am I missing?
Solution
TL;DR: @Transactional in tests should not be done, except you know what you are doing, H2 is weird.
Why is @Transactional
not a good idea?
Every test is already wrapped in its own transaction by default (docs). That's why it worked beforehand. Nesting another Transaction
seems to actually confuse Spring as it does not seem to autocommit on every operation anymore as it is supposed to do.
H2 is weird
The problem was that Address
(please note that Address
is not my real code but an example of a simalar relation of objects) used an UUID
as its id
.
We use H2 as a DB for testing which did not create a proper table for Address
but messed up the id column with a wrong data type with the new update. Thus, it could not find any Address
by id
. That's why the JPAContext errored saying that the address was not existent.
Adding
//...
@Entity
public class Address{
@Column(columnDefinition = 'uuid') <-- add this line
@Id
UUID addrId;
//...
}
helps M2 to determine the correct column type and solves the problem.
Answered By - KuSpa
Answer Checked By - Gilberto Lyons (JavaFixing Admin)