The entity field value is updated after exception throwing and transaction rollback - spring-boot

I have developed a test project to reproduce this issue.
This is a project structure:
pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>value-updated-after-fail-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
Persone.java file:
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#RequiredArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#Setter
#ToString
#FieldDefaults(level = AccessLevel.PRIVATE)
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column(nullable = false)
#NonNull
String name;
}
PersonRepository.java file:
#Repository
public interface PersonRepository extends JpaRepository<Person, Long> {}
PersonService.java file:
#Component
public class PersonService {
private final PersonRepository repository;
public PersonService(PersonRepository repository) {
this.repository = repository;
}
#Transactional
public Person create(String name) {
return repository.save(new Person(name));
}
#Transactional
public Person save(Person person) {
if(StringUtils.isBlank(person.getName())) {
throw new RuntimeException();
}
Person personFromDB = getById(person.getId());
personFromDB.setName(person.getName());
return repository.save(personFromDB);
}
#Transactional
public Person getById(Long id) {
return repository.findById(id)
.orElseThrow(NullPointerException::new);
}
#Transactional
public void deleteAll() {
repository.deleteAll();
}
}
application.properties file:
spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
TestApplicationConfiguration.java file
#SpringBootConfiguration
#EnableAutoConfiguration
#EnableJpaRepositories
#EntityScan("net.example.model")
#ComponentScan(basePackages = "net.example")
public class TestApplicationConfiguration {}
PersonServiceTest.java file:
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
class PersonServiceTest {
#Autowired
private PersonService service;
#AfterEach
void tearDownEach() {
service.deleteAll();
}
#Test
void rename() {
String expected = "name";
Person person = service.create(expected);
Person personFromDB = service.getById(person.getId());
personFromDB.setName("");
assertThrows(RuntimeException.class, () -> service.save(personFromDB));
assertEquals(expected, service.getById(personFromDB.getId()).getName());
}
}
The issue: Last assertion fails
org.opentest4j.AssertionFailedError:
Expected :name
Actual :
What I already tried to fix this?
I tried to remove the #Transactional annotation for the PersonService#getById method to avoid getting the entity from the cache. - This didn't fix the issue
I tried to add spring.cache.type=none to the application.properties file to disable the cache. - This didn't fix the issue
Why do I think it's the cache?
When I debugged this, I found that the PersonService#getById() method doesn't return actual data, but the method returns a cached object with a changed title.
The database isn't changed after calling the PersonService#save method because it throws an exception
Perhaps I'm not developing the tests correctly.
Maybe I should change the method of saving changed data.
Please share best practices and articles to better understand how to update data and how to properly configure and write tests for Spring Boot applications.

Thanks a lot for the comment from Andrey B. Panfilov.
I investigated the #Transactional and the first level cache of Hibernate.
Indeed, each test method call in a class annotated with the #DataJpaTest annotation creates, runs, and rollbacks a transaction.
Each transaction creates and closes the Hibernate session. As we know, the first level cache exists until the session closes. That's why it's also called session cache.
You can see the evidence in the following screenshots:
In the first screenshot, you can see that SpringExtension, which is defined in the #DataJpaTest annotation, opens a new session before each test is called.
In the second screenshot, you can see that SpringExtension closes the session after each test is called.
I decided to override the default transaction propagation:
#Transactional(propagation = Propagation.NEVER) it doesn't create a transaction when the method is called and throw an exception if the method is called in an existing transaction
Links that helped me:
Data Access
Transaction Propagation and Isolation in Spring #Transactional
Transaction Propagation with illustrations
Hibernate Caching - First Level Cach

Related

Spring-Batch using MySql and Spring-Data Key-Value together

I have PoC to test Spring-Batch with MySql and Key-Value together.
POM
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-keyvalue</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Application
#SpringBootApplication
#EnableMapRepositories
public class SpringBatchPocApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBatchPocApplication.class, args);
}
}
application.properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3307/pocdb
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.sql.init.platform=mysql
file.input=animal-list.csv
spring.batch.job.enabled=false
spring.batch.jdbc.initialize-schema=always
key-value entity
#Data
#Builder(setterPrefix = "with")
#NoArgsConstructor
#AllArgsConstructor
#KeySpace("imAnimal")
public class IMAnimal {
#Id
private UUID id;
private String name;
private String type;
private boolean processed;
}
key-value repository
#Repository
public interface IMAnimalRepository extends CrudRepository<IMAnimal, UUID> {
}
Than I have a service to controll key-value repository:
#Service
public class DatabaseService {
#Autowired
private IMAnimalRepository repository;
...
Yet, when I run my application, the IMAnimalRepository is not found by spring context.
Field repository in name.jikra.springbatchpoc.service.DatabaseService required a bean of type 'name.jikra.springbatchpoc.repository.IMAnimalRepository' that could not be found.
My guess is, there is a collision with JPA dataSource or something like this.
Is there a way to put it together?

Spring - execute query before application shutdown (tests)

I'm running SpringBoot 2.1 with Sprind Data JPA/Hibernate as persistence layer. I run into problem to succesfully run query, in my tests, before application shutdown.
Details:
During application context startup I'm executing a query via JPA (let's say this query translates to following SQL "insert into mytable('mycolumn') values ('abc')).
Now I need to execute another query before application is shutdown. For given example this would be "update mytable set mycolumn = 'xyz' where mycolumn = 'abc'
I managed to execute the query by using #PreDestroy on my configuration class
#Configuration
MyConfig {
#Autowired
private MyTransactionalService myService;
#PreDestroy
public void doQuery() {
mySerivce.runMyQuery();
}
}
mySerivce.runMyQuery() delagates to myRepository (which is Spring Data JPA Repository) to call update query:
MyRepository extends JpaRepository(String, Something) {
#Modifying
#Query("UPDATE myEntity e SET e.myColumn = 'xyz' WHERE e.myColumn = 'abc")'
void runMyQuery();
}
The method annotated with #PreDestroy executes but when the query is executed by H2 (inmemory db running inside my spring tests) it throws exception saying that table does not exist.
The thing is that table surely existed before as I'm able to execute INSERT on that table during application startup (see beginning of the post).
My guess would be that the shudtown process is in progress, so the in-memory database was cleared out... thus there is no table.
Is there anyway to ensure query is executed while connection to database is still healthy and removal of tables did not happen yet (upon application context shutdown) ?
#Predestroy works as expected, just put #PreDestroy annotation on some method in you Application class. I created an example here. To test quickly i used sql files to initialize my database as it is described here, by you can also use a service for it. When i shutdown the application the database is updated as wanted. Please try :
Dependencies in : pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zpavel</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>1.4.199</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
</project>
Model :
#Entity
#Data
public class Foo {
#Id
private Long id;
private String bar;
}
Repository :
public interface FooRepository extends JpaRepository<Foo, Long> {
}
src/main/resources/schema.sql :
DROP TABLE IF EXISTS foo;
CREATE TABLE foo (
id INT AUTO_INCREMENT PRIMARY KEY,
bar VARCHAR(250) NOT NULL
);
src/main/resources/data.sql :
INSERT INTO foo (bar) VALUES ('baz');
src/main/resources/application.properties :
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.datasource.url=jdbc:mariadb://localhost:3306/test?useSSL=false
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.initialization-mode=always
Application.java
#SpringBootApplication
public class Application {
#Autowired
private FooRepository fooRepository;
// keep main method here
#PreDestroy
private void shutdown() {
fooRepository.deleteAll();
}
}
I managed to overcome this by using DB_CLOSE_ON_EXIT=FALSE"; init parameter
String url = "jdbc:h2:~/test;DB_CLOSE_ON_EXIT=FALSE";
In this case H2 does not kill the database and keeps it available during the shutdown process.

spring boot fails to start-- define a bean of type 'TopicRepository' in configuration

I was following this JavaBrains tutorials of Spring Boot.
My project structure is as follows:
CourseApiApp.java:
#SpringBootApplication
#ComponentScan(basePackages = {
"com.bloodynacho.rishab.topics"
})
#EntityScan("com.bloodynacho.rishab.topics")
public class CourseApiApp {
public static void main(String[] args) {
SpringApplication.run(CourseApiApp.class, args);
}
}
TopicController.java:
#RestController
public class TopicController {
#Autowired
private TopicService topicService;
#RequestMapping(
value = "/topics"
)
public List<Topic> getAllTopcs() {
return topicService.getAllTopics();
}
}
TopicService.java:
#Service
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public List<Topic> getAllTopics() {
List<Topic> topics = new ArrayList<>();
this.topicRepository
.findAll()
.forEach(topics::add);
return topics;
}
}
Topic.java:
#Entity
public class Topic {
#Id
private String id;
private String name;
private String description;
}
TopicRepository.java:
#Repository
public interface TopicRepository extends CrudRepository<Topic, String>{
}
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
I was using the lombok #Getter, #Getter and #AllArgsConstructor in Topic.java but I removed it after reading one of the answers here.
I read this1, this2, this3
Still, I get
***************************
APPLICATION FAILED TO START
***************************
Description:
Field topicRepository in com.bloodynacho.rishab.topics.TopicService required a bean of type 'com.bloodynacho.rishab.topics.TopicRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.bloodynacho.rishab.topics.TopicRepository' in your configuration.
Process finished with exit code 1
EDIT: I read this explaining how even without actually implementing the interface the #Autowired works. I understand the solution, but I don't understand how to solve my issue. Clearly, there is some problem with the way Spring Data is set up and configured (as mentioned in the answer)
Because if your other packages hierarchies are below your main application with the #SpringBootApplication annotation, you’re covered by implicit components scan.
Therefore, one simple solution can be done by following 2 steps:
Rename the package of main class to be com.bloodynacho.rishab.
(That is what I suggest that the complete package name of main app. is supposed to be root of other packages.)
Remove #ComponentScan and #EntityScan annotation.
(Although #ComponentScan is different from #EntityScan, it can be also removed in my experience.)

SpringBootTest does not roll back

(Although, there are plenty of similar questions I was not able to find the solution among them).
Why #Transactional annotation with #SpringBootTest works and rolls back nicely when I use DAO directly, but does not work when I test with TestRestTemplate?
tl;dr
Entity
#Entity
public class ToDoItem {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull #Column(unique = true)
private String title;
public ToDoItem() {}
public ToDoItem(String title) { this.title = title;}
// getters, setters, etc.
Repository
public interface ToDoRepository extends CrudRepository<ToDoItem, Long> {}
Application
#EnableTransactionManagement
#SpringBootApplication
#RestController
#RequestMapping("/todos")
public class ToDoApp {
public static void main(String[] args) {
SpringApplication.run(ToDoApp.class, args);
}
#Autowired
private ToDoRepository toDoRepository;
#GetMapping
ResponseEntity<?> fetchAll() { return ok(toDoRepository.findAll()); }
#PostMapping()
ResponseEntity<?> createNew(#RequestBody ToDoItem toDoItem) {
try {
toDoRepository.save(toDoItem);
return noContent().build();
} catch (Exception e) {
return badRequest().body(e.getMessage());
}
}
}
Now I create two tests which do basically the same: store some to-do items in in-memory database and check if size has increased:
Repository test
#ExtendWith(SpringExtension.class)
#SpringBootTest
#Transactional
class ToDoRepoTests {
#Autowired
private ToDoRepository toDoRepository;
#Test
void when_adding_one_item_then_success() {
toDoRepository.save(new ToDoItem("Run tests"));
assertThat(toDoRepository.findAll()).hasSize(1);
}
#Test
void when_adding_two_items_then_success() {
toDoRepository.saveAll(List.of(
new ToDoItem("Run tests"), new ToDoItem("Deploy to prod")));
assertThat(toDoRepository.findAll()).hasSize(2);
}
}
Then I create similar test that does exactly the same thing but via REST API (This one does not work and fails with Unique index or primary key violation):
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = ToDoApp.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#Transactional
class ToDoControllerTests {
#LocalServerPort
private int localServerPort;
private TestRestTemplate testRestTemplate;
#BeforeEach
void setUp() {
testRestTemplate = new TestRestTemplate(new RestTemplateBuilder()
.rootUri("http://localhost:" + localServerPort));
}
#Test
void when_adding_one_item_then_success() {
// when
createToDo(new ToDoItem("Walk the dog"));
var allItems = fetchAllTodos();
// then
assertThat(allItems).hasSize(1);
}
#Test
void when_adding_two_items_then_success() {
// when
createToDo(new ToDoItem("Walk the dog"));
createToDo(new ToDoItem("Clean the kitchen"));
var allItems = fetchAllTodos();
// then
assertThat(allItems).hasSize(2);
}
private void createToDo(ToDoItem entity) {
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
var response = testRestTemplate.postForEntity("/todos", new HttpEntity<>(entity, headers), String.class);
assertThat(response.getStatusCode()).isEqualTo(NO_CONTENT);
}
private List<ToDoItem> fetchAllTodos() {
return Arrays.asList(testRestTemplate.getForObject("/todos", ToDoItem[].class));
}
}
And here is my pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>foo.bar</groupId>
<artifactId>todo-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>To-Do App</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Please, help me to understand, what I am doing wrong.
When you call the API using the RESTTemplate it crosses the boundary of the local transaction management and becomes as distributed transaction. There is not mechanism available for spring to roll back that call to the rest API. Although in your example the target system is the same application, it need not be so. It could be an external application that is not aware of the transactional boundaries that you have set in your test.

Spring Boot JPA Save not working

I am migrating Spring Boot 1.2.4 running in Websphere to Spring Boot 1.5.9. This application uses Spring Data JPA with Hibernate. After migration one of the save method is not working as expected. When I try to insert a new row in table getting an exception with SQLCODE=-204, SQLSTATE=42704, but same code works fine with Spring Boot 1.2.4.
My Pom.xml looks like below.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Spring Boot 1.2.4 configuration looks like below.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.10.Final</version>
</dependency>
#Repository
public interface MyDataRepository extends CurdRepository<MyEntity,Long>{}
#Service
public class MyDataService{
#Autowired
private MyDataRepository myDataRepository;
public void insertMyDate(MyData myData){
myDataRepository.save(myData)
}
}
#Entity
#Table(name="MYDATA" ,schema="MYSCHEMA")
public class MyData implements java.io.Serializable {
private int memberId;
private String firstName;
private String lastName;
private Date dateofBirth;
#GeneratedValue(strategy=GenerationType.AUTO)
#Id
#Column(name="MEMBER_ID",uniqueue=true,nullable=false)
public int getMemberId(){
return this.memberId;
}
public void setMemberId(int memberId){
return this.memberId = memberId;
}
//Geter and Setters
}
With same code when executing save method in 1.5.9 getting below exception.
could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
You don't need this hibernate-entitymanager dependency becz you are using repository to save data

Resources