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
Related
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?
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
i have a question to Spring boot and the dependency jakarta-validation-api.
Actually i have a simple DTO which holds some properties. But this properties are not being validated when I call the REST-function within the #Valid annotation.
Can someone find my error?
A snippet of my pom.mxml dependencies:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0-M1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.0.0</version>
</dependency>
My DTO class:
import jakarta.validation.Valid;
#Data
public class TestDTO implements Serializable {
private static final long serialVersionUID = -1362258531757232654L;
#NotEmpty(message = "Id could not be empty or null.")
#Size(min = 36, max = 36, message = "Id must contains exactly out of 36 characters.")
private String id;
#Min(value = 1, message = "Page size cannot be null or <= 0.")
private Integer page;
}
And also a snippet of the REST-Resource class where the DTO is been used in the body:
#PostMapping(path = "/")
public Integer testValidation(#Valid #RequestBody TestDTO body) {
LOGGER.info(body);
return 1;
}
Actually i would think that when I call the Post-REST method that it will be validated before it will go into the method body, but actually it goes into the method body without has been validated before.
Is it due to "jakarta" dependency instead of "javax"?
Hope you can help me :)
From what I understand in the Spring Boot 3.0.0 M1 Release Notes, Spring Boot 2.X does not support Jakarta EE, but support will come with Spring Boot 3.X.
For Spring Boot 2.X you should stick with javax.
For Spring Boot 3.X you should stick with jakarta.
In my case(spring boot 2.4.*).
I remove jakarta.validation-api dependencies,then it works.
use javax.*
not jakarta.*
I tried to implement pagination for my api with Spring Boot 1.5.3 and Elasticsearch but it returned HttpMessageNotWritableException: could not write JSON document instead.
I thought that ElasticsearchRepository already provided paging and sorting method so I tried to used that then return pageable object to my controller. And I think the error occurs when the controller returns the result.
Here is the controller.
#GetMapping(value = "/client",
params = { "page", "size" })
public Page<Client> getAllClient( #RequestParam("page") int page, #RequestParam("size") int size){
return clientService.getAllClient(page, size);
}
The service.
public Page<Client> getAllClient(int page, int size) {
Page<Client> resultPage = clientRepository.findAll(new PageRequest(0, 3));
return resultPage;
}
The repository.
public interface ClientRepository extends
ElasticsearchRepository<Client, String> {
public Client findByName(String name);
}
I already used Lombok for my entity so it should not be the problem.
#Data
#Document(indexName = "customer", type = "client")
public class Client {
#Id
private String id;
private String name;
private String city;
private String phone;
}
dependencies in pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
When I make the HTTP GET request to the url that mapped with the controller, the error log below appeared.
WARN 12616 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON document: (was java.lang.NullPointerException) (through reference chain: org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl["facets"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl["facets"])
I wonder if I forgot any configuration in my project.
I just found that after I changed to Spring Boot version 2.0.4 it works!
But I still wanted to know how to do it in version 1.5.3 though.
I created a springboot (2) webflux project as follow :
JPA Entity
#Entity
#Table(name = "users")
public class User implements Serializable
{
...
}
Spring repository
public interface UserRepository extends CrudRepository<User, Long>
{
}
Service
#Service
public class UserService
{
#Autowired
private UserRepository userRepo;
...
}
Webflux Handler
#Component
public class UserHandler
{
#Autowired
private UserService userService;
public Mono<ServerResponse> getUser(ServerRequest request)
{
...
}
}
RouteConfiguration
#Configuration
public class RouteConfiguration
{
#Bean
public static RouterFunction<ServerResponse> userRoutes(UserHandler userHandler)
{
return RouterFunctions.route(RequestPredicates.GET("/user"), userHandler:: getUser);
}
WebApp
#SpringBootApplication
public class WebApplication
{
public static void main(String[] args)
{
SpringApplication.run(WebApplication.class);
}
}
POM
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Provided -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- Runtime -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Everything run fine, I can start my server and use it. I would like now to code some tests. Here is what I did :
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = WebApplication.class)
public class UserHandlerTest
{
#Autowired
private ApplicationContext context;
#MockBean
private UserService userService;
private WebTestClient testClient;
#Before
public void setUp()
{
testClient = WebTestClient.bindToApplicationContext(context).build();
}
#Test
public void testUser()
{
...
}
}
What ever I tried, I got an error with hibernate dependencies during "mvn clean install" process :
[ERROR] testUser(...UserHandlerTest) Time elapsed: 0 s <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl
I know JPA works in blocking way but I want to avoid to use NoSQL DB for this project. Did I miss something ? Thank you a lot for help !
Probably, you are missing details which we need to provide for datasource under src/main/resources. you can check https://github.com/hantsy/spring-reactive-sample/blob/master/boot-data-mongo/src/main/resources/application.yml. this might help you.
To test my Spring Webflux controllers, I finally use the WebFluxTest annotation. It works as expected :
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RouteConfiguration.class, UserHandler.class})
#WebFluxTest
public class UserHandlerTest
{
#Autowired
private ApplicationContext context;
#MockBean(name="userService")
private UserService userService;
private WebTestClient testClient;
#Before
public void setUp()
{
testClient = WebTestClient.bindToApplicationContext(context).build();
}
...
As I do not use RestController annotation but functional endpoints I had to use ContextConfiguration and manually instantiate the WebTestClient.