Spring - execute query before application shutdown (tests) - spring

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.

Related

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

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

Spring Boot Application - H2 Database (1.4.200) Array column, Hibernate mapping

I'm creating a Spring Boot application, where I have a Routes table. Within the Routes table I have stops column where I want to store the integer array of stop id.
Since H2 database (version 1.4.200) doesn't support typed array, I defined it as generic array.
CREATE TABLE Routes (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(250) NOT NULL,
stops ARRAY[100] NOT NULL
);
INSERT INTO Routes (id, name, stops) VALUES (1, '500CK', (1,2));
SELECT * FROM Routes;
ID NAME STOPS
1 500CK [1, 2]
I want to know what should be the type of stops property (any other configuration required) so it works with Hibernate
#Entity
#Table(name = "Routes")
public class Route {
#Id
#GeneratedValue
private int id;
private String name;
// I'm not sure what should be the data type of stops
private Object[] stops;
}
I'm getting below error while trying to get the route from /route endpoint
org.h2.jdbc.JdbcSQLDataException: Hexadecimal string contains non-hex character: "[1, 2]" [90004-200]
I created the Routes table by defining the DDL in the schema.sql file
DROP TABLE IF EXISTS Routes;
CREATE TABLE Routes (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(250) NOT NULL,
stops ARRAY[100] NOT NULL
);
And I inserted one record by defining the DML in data.sql file
INSERT INTO ROUTES (id, name, stops) VALUES
(1, '500CK', (1,2));
UPDATE :
I updated h2 to 2.1.212
CREATE TABLE Routes (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(250) NOT NULL,
stops INTEGER ARRAY[100] NOT NULL
);
INSERT INTO ROUTES (id, name, stops) VALUES(1, '500CK', ARRAY[1, 2]);
I can see the above record in the Routes table.
Changed the Route entity as below
#Entity
#Table(name = "Routes")
public class Route {
#Id
#GeneratedValue
private int id;
private String name;
private Integer[] stops;
// getter & setters
}
I'm getting below error
org.h2.jdbc.JdbcSQLDataException: Data conversion error converting "ARRAY to BINARY VARYING" [22018-212]
I'm using hibernate-core 5.6.8.Final
Looks like Hibernate is not able to do the conversion.
Can I achieve it with just Hibernate, Or do I have to do it, the way mentioned here
https://vladmihalcea.com/how-to-map-java-and-sql-arrays-with-jpa-and-hibernate/
Update : Trying to update hibernate-core to 6.1.0.Final
I tried to update the hibernate-core to 6.1.0.Final.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.0.Final</version>
</dependency>
But I'm getting below error
Field routeRepository in net.mahtabalam.service.RouteService required a bean named 'entityManagerFactory' 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 named 'entityManagerFactory' in your configuration.
Looks like this is caused due to dependency version conflicts.
Below is the project 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 https://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.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>net.mahtabalam</groupId>
<artifactId>routes</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>routes</name>
<description>Bus Routes</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<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>
<version>2.1.212</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Unfortunately to my knowledge there will be no way to use hibernate 6.1.0.Final with Spring Boot currently. The issue you've noted with entityManagerFactory will be because the hibernate-entitymanager project was moved into hibernate-core in version 6.1.0.Final.
Looking through the plans for Spring Boot, see here, it looks like there is no imminment plan to update the version of hibernate used either as it won't be until M3 of version 3.
Instead your best bet will be to make a custom converter, I am currently trying to tackle the exact same problem and if I find a solution I will share it.

Error when adding custom revision in Hibernate envers

When I add custom revision entity, I start getting error:
2020-12-13 00:22:29.418 ERROR 80983 --- [ost-startStop-1] o.s.b.web.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'webSecurityConfig': Unsatisfied dependency expressed through field 'userDetailsService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userDetailsServiceImpl': Unsatisfied dependency expressed through field 'userRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Cannot create inner bean '(inner bean)#4384acd' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#4384acd': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is 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: org/hibernate/resource/beans/spi/ManagedBeanRegistry
MyRevision:
package ...;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;
import javax.persistence.Entity;
#Entity
#RevisionEntity(MyRevisionListener.class)
public class MyRevision extends DefaultRevisionEntity {
private String username;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
}
MyRevisionListener:
package ...;
// import de.xxxxx.carorderprocess.models.User;
import org.hibernate.envers.RevisionListener;
// import org.springframework.security.core.Authentication;
// import org.springframework.security.core.context.SecurityContext;
// import org.springframework.security.core.context.SecurityContextHolder;
// import java.util.Optional;
public class MyRevisionListener implements RevisionListener {
#Override
public void newRevision(Object revisionEntity) {
/* String currentUser = Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername)
.orElse("Unknown-User"); */
MyRevision audit = (MyRevision) revisionEntity;
audit.setUsername("dd");
}
}
WebSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
UserDetailsServiceImpl:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
return UserDetailsImpl.build(user);
}
}
UserRepository:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
}
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 https://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.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>de.xxxxxxx</groupId>
<artifactId>carorderprocess</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>carorderprocess</name>
<description>Demo project for Spring Boot</description>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.4.25.Final</version>
</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>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
I think your problem could be related with the different dependencies in your pom.xml.
Please, first, remove the spring-data-envers dependency, unless you are querying your audit tables you do not need it. Even in that case, you can use Envers on its own to obtain that information if required.
Be aware that, as indicated in the comments of the answer from Sunit, you will need to remove the attribute repositoryFactoryBeanClass, it could not longer take the value EnversRevisionRepositoryFactoryBean. But you probably still need to include the #EnableJpaRepositories annotation.
Although I initially indicated that you can let Spring Boot manage your versions, due to the one of spring-boot-starter-parent, the framework is providing you versions of hibernate-xxx similar to 5.2.17.Final.
But, as you indicated, you need to use the method forRevisionsOfEntityWithChanges for querying your audit entities. As you can see in the java docs, that method was introduced in AuditQueryCreator in version 5.3.
As a consequence, you need to provide the following dependency:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.3.20.Final</version>
</dependency>
But in addition you also need to provide a compatible version of both hibernate-entitymanager and hibernate-core:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.3.20.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.20.Final</version>
</dependency>
From what I understood from all the comments above, your requirement is
to use Envers Auditing
and use method forRevisionsOfEntityWithChanges to get list of all revisions with what changed in them
Please start by doing these
Remove dependency of spring-data-envers library.
Just keep library hibernate-envers - version 5.4.23.Final also worked for me
Remove repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class from #EnableJpaRepositories annotation
All Repository classes should only extend from JpaRespository and NOT from RevisionRepository. You dont need RevisionRepository
You should be able to get your application up and running now.
Now coming back to the question, how to get all revisions with changes using forRevisionsOfEntityWithChanges method.
Create an AuditConfiguration class like this, to create the AuditReader bean
#Configuration
public class AuditConfiguration {
private final EntityManagerFactory entityManagerFactory;
AuditConfiguration(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
#Bean
AuditReader auditReader() {
return AuditReaderFactory.get(entityManagerFactory.createEntityManager());
}
}
In your AuditRevisionEntity class, add following annotation. Without this the serialization of this class wont work. e.g
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class AuditRevisionEntity extends DefaultRevisionEntity {
In your entity class add option withModifiedFlag = true to #Audited annotation. Without this you cannot get entity revisions with all changes. e.g
#Audited(withModifiedFlag = true)
public class Customer {
Modify your database table for this entity audit table and fields *_mod. e.g if you have a customer table with fields name, age, address columns, then add columns name_mod, age_mod, address_mod to the customer_audit table
Last, add following code in your service method to get audit revisions with changes
#Autowired
private AuditReader auditReader;
public List<?> getRevisions(Long id) {
AuditQuery auditQuery = auditReader.createQuery()
.forRevisionsOfEntityWithChanges(Customer.class, true)
.add(AuditEntity.id().eq(id));
return auditQuery.getResultList();
}
I will try to post the same code in Github sometime today, so that you can take a look at working code.
Your code looks fine. But it may not be sufficient to identify the root cause.
Looking at the exception it is clear that application is failing since it is not able to find bean dependency
Could you try following
Check your library imports first in your build.gradle or pom.xml. Generally you should not require any other Hibernate library other than Spring Boot Data JPA and Hibernate Envers
Try removing/disabling the Hibernate Envers audit code and library dependencies and see if can you get your application up and running. This will help you identify if error is due to Hibernate Envers or if your application code has other issues.
If above does not works, then please provide more information
Which version of Spring Boot are you on
What libraries have you imported (build.gradle or maven pom file)
What other Configurations you have in your project - do you have any other JPA configuration file or any other custom configuration related to Hibernate or JPA
What annotations are on the main application class
Directory structure of your Repository class, and the directory on which you do component scan (in case you have overridden it)

SpringBoot 2 + Junit5: null with #Value

I have an application with SpringBoot2 and Junit5, and now I'm trying to make a test. I have a this class called OrderService that looks like this:
#Component
public class OrderService {
#Value("#{'${food.requires.box}'.split(',')}")
private List<String> foodRequiresBox;
#Value("#{'${properties.prioritization}'.split(',')}")
private List<String> prioritizationProperties;
#Value("${further.distance}")
private Integer slotMeterRange;
#Value("${slot.meters.long}")
private Double slotMetersLong;
As you can see, the class has many #Value annotations that extracts values from application.properties file.
In the POM file I have these dependences:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.0.5.RELEASE</version>
</dependency>
Tn the test/resources folder I have the application.properties file with this information:
properties.prioritization:vip,food
food.requires.box:pizza,cake,flamingo
further.distance:2
slot.meters.long:0.5
The test file looks like this:
#ExtendWith(SpringExtension.class)
#TestPropertySource(locations="classpath:application.properties")
public class OrderServiceTest {
OrderService orderService;
#BeforeEach
void before(){
orderService = new OrderService();
}
#Test
void findAll() {
Order order = new Order().withDescription("2x Pizza with Salad\\n2x Kebab with Fries\\n1x Hot dog with Fries\\n2x Pizza with Fries");
assertTrue(orderService.orderHasFood.test(order));
}
}
But the test throws NullPointerException when it tries to use foodRequiresBox, so there is a problem to read the application.properties file.
Could you tell how can I read the application.properties file for the tests?
1st Solution
I would recommend to use Spring's internal annotation called #SpringJUnitConfig
This annotation is actually the same as #ExtendWith(SpringExtension.class) BUT you can configure your spring application contexts for your test in the same way you used to use #ContextConfiguration.
Or if you want a full Spring Boot Test you could just combine:
#SpringJUnitConfig
#SpringBootTest
public class OrderServiceTest {
...
}
2nd Solution
Another way is to not use Spring at all, but mock all the internal stuff with e.g. Mockito and write a plain simple Unit Test.
You could then set your normally via Spring injected annotated #Value fields via org.springframework.test.util.ReflectionTestUtils.
I'd recommend using org.springframework.test.util.ReflectionTestUtils (as indicated in #mrkernelpanic second solution) using the setField() method, in order to avoid initializing the full Spring context.
Here is a snippet:
// ReflexionTestUtils.setField(targetObject, "targetFieldName", valueToInject);
ReflexionTestUtils.setField(orderService, "foodRequiresBox", "pizza,cake,flamingo");

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