I have a method inside a #Service class which calls two different methods in two different #Service classes. These two different methods save two entities inside the database (through hibernate) and they both may throw some exceptions.
I would like that if an exception is thrown, independently from which #Service method, all the changes are rolled back. So all the entities created inside the database are deleted.
//entities
#Entity
public class ObjectB{
#Id
private long id;
...
}
#Entity
public class ObjectC{
#Id
private long id;
...
}
//servicies
#Service
#Transactional
public class ClassA{
#Autowired
private ClassB classB;
#Autowired
private ClassC classC;
public void methodA(){
classB.insertB(new ObjectB());
classC.insertC(new ObjectC());
}
}
#Service
#Transactional
public class ClassB{
#Autowired
private RepositoryB repositoryB;
public void insertB(ObjectB b){
repositoryB.save(b);
}
}
#Service
#Transactional
public class ClassC{
#Autowired
private RepositoryC repositoryC;
public void insertC(ObjectC c){
repositoryC.save(c);
}
}
//repositories
#Repository
public interface RepositoryB extends CrudRepository<ObjectB, String>{
}
#Repository
public interface RepositoryC extends CrudRepository<ObjectC, String>{
}
I would like that methodA of ClassA, once an exception has been thrown from either methodB or methodC, it rollbacks all the changes inside the database.
But it doesn't do that. All the changes remains after the exception...
What am I missing?
What should I add in order to make it work as I want?
I'm using Spring Boot 2.0.6!
I haven't configured anything in particular to make the transactions work!
EDIT 1
This is my main class if it can help:
#SpringBootApplication
public class JobWebappApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(JobWebappApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(JobWebappApplication.class, args);
}
}
When an exception is thrown this is what I see in the console:
Completing transaction for [com.example.ClassB.insertB]
Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#31d4fbf4] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#df9d400] bound to thread [http-nio-8080-exec-7]
Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder#1d1ad46b] for key [HikariDataSource (HikariPool-1)] bound to thread [http-nio-8080-exec-7]
Getting transaction for [com.example.ClassC.insertC]
Completing transaction for [com.example.ClassC.insertC] after exception: java.lang.RuntimeException: runtime exception!
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: runtime exception!
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Completing transaction for [com.example.ClassA.methodA] after exception: java.lang.RuntimeException: runtime exception!
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: runtime exception!
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Clearing transaction synchronization
Removed value [org.springframework.jdbc.datasource.ConnectionHolder#1d1ad46b] for key [HikariDataSource (HikariPool-1)] from thread [http-nio-8080-exec-7]
Removed value [org.springframework.orm.jpa.EntityManagerHolder#31d4fbf4] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#df9d400] from thread [http-nio-8080-exec-7]
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: runtime exception!] with root cause
It seems that each time it calls a method it creates a new transaction! Is without rolling back anything after RuntimeException occurs!
EDIT 2
This is the pom.xml dependencies file:
<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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.10.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
This is the application.properties file:
spring.datasource.url=jdbc:mysql://localhost:3306/exampleDB?useSSL=false
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.show-sql=true
logging.level.org.springframework.transaction=TRACE
spring.jpa.database=MYSQL
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver.class=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.locationId.new_generator_mappings=false
SOLUTION
Thanks to #M.Deinum I found the solution!
I was using a wrong database engine (MyISAM), which does not support transaction! So I changed the table engine type with "InnoDB" which supports transactions. What I did is this:
I added this property inside application.properties file, inorder to tell to JPA which was the engine type it should use to "manipulate" the tables:
spring.jpa.properties.hibernate.dialect =
org.hibernate.dialect.MySQL5InnoDBDialect
I dropped all the existing tables (with the wrong engine type) inside my DB and I let JPA to recreate all of them with the right engine (InnoDB).
Now all the RuntimeExceptions thrown make the transaction to rollback all the changes done within it.
ALERT: I noticed that if an exception which is not a subclass of RuntimeException is thrown, no rollback is applied and all the changes already done remain inside the database.
What you are trying to achieve should work out of the box. Check your spring configuration.
Make sure you created TransactionManager bean and make sure you placed #EnableTransactionManagement annotation on some of your spring #Configurations. This annotation are responsible for registering the necessary Spring components that power annotation-driven transaction management, such as the TransactionInterceptor and the proxy- or AspectJ-based advice that weave the interceptor into the call stack when #Transactional methods are invoked.
See the linked documentation.
If you are using spring-boot it should automatically add this annotation for you if you have PlatformTransactionManager class on classpath.
Also, please note that checked exceptions does not trigger a rollback of the transaction. Only runtime exceptions and errors trigger a rollback. You can, of course, configure this behavior with the rollbackFor and noRollbackFor annotation parameters.
Edit
As you clarified that you are using spring-boot, the answer is: all should work without any configuration.
Here is minimal 100% working example for spring-boot version 2.1.3.RELEASE (but should work with any version ofc):
Dependencies:
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtimeOnly('com.h2database:h2') // or any other SQL DB supported by Hibernate
compileOnly('org.projectlombok:lombok') // for getters, setters, toString
User entity:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
#Getter
#Setter
#ToString
public class User {
#Id
#GeneratedValue
private Integer id;
private String name;
}
Book entity:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
#Entity
#Getter
#Setter
#ToString
public class Book {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
private User author;
private String title;
}
User repository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Integer> {
}
Book repository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Integer> {
}
User service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
#Transactional
#Component
public class UserService {
#Autowired
private UserRepository userRepository;
public User saveUser(User user) {
// return userRepository.save(user);
userRepository.save(user);
throw new RuntimeException("User not saved");
}
public List<User> findAll() {
return userRepository.findAll();
}
}
Book service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
#Transactional
#Component
public class BookService {
#Autowired
private BookRepository bookRepository;
public Book saveBook(Book book) {
return bookRepository.save(book);
}
public List<Book> findAll() {
return bookRepository.findAll();
}
}
Composite service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
#Transactional
#Component
public class CompositeService {
#Autowired
private UserService userService;
#Autowired
private BookService bookService;
public void saveUserAndBook() {
User user = new User();
user.setName("John Smith");
user = userService.saveUser(user);
Book book = new Book();
book.setAuthor(user);
book.setTitle("Mr Robot");
bookService.saveBook(book);
}
}
Main:
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class JpaMain {
public static void main(String[] args) {
new SpringApplicationBuilder(JpaMain.class)
.web(WebApplicationType.NONE)
.properties("logging.level.org.springframework.transaction=TRACE")
.run(args);
}
#Bean
public CommandLineRunner run(CompositeService compositeService, UserService userService, BookService bookService) {
return args -> {
try {
compositeService.saveUserAndBook();
} catch (RuntimeException e) {
System.err.println("Exception: " + e);
}
System.out.println("All users: " + userService.findAll());
System.out.println("All books: " + bookService.findAll());
};
}
}
If you run the main method you should see that no books or users found in DB. The transaction is rolled back. If you remove the throw new RuntimeException("User not saved") line from UserService, both entities will be saved fine.
Also you should see the logs of org.springframework.transaction package on TRACE level, where for instance you will see:
Getting transaction for [demo.jpa.CompositeService.saveUserAndBook]
And then after exception is thrown:
Completing transaction for [demo.jpa.CompositeService.saveUserAndBook] after exception: java.lang.RuntimeException: User not saved
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: User not saved
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Clearing transaction synchronization
Here No relevant rollback rule found: applying default rules means that rules defined by DefaultTransactionAttribute will be applied to determine if transaction should be rolled back. And these rules are:
Rolls back on runtime, but not checked, exceptions by default.
RuntimeException is runtime exception, so the transaction will be rolled back.
The line Clearing transaction synchronization is where rollback is actually applied. You will see some other Applying rules to determine whether transaction should rollback messages because #Transactional methods are nested here (UserService.saveUser called from CompositeService.saveUserAndBook and both methods are #Transactional), but all they do is determine rules for future actions (at the point of transaction synchronization). The actual rollback will be done only once, at the outermost #Transactional method exit.
Since spring 3.1 if you're using spring-data-* or spring-tx dependencies on the classpath, then transaction management will be enabled by default.
https://www.baeldung.com/transaction-configuration-with-jpa-and-spring
But checking Springs Transactional annotation we can see that you'll need to inform the parameter rollbackFor if exception thown isn't a extension of RuntimeException.
/**
* Defines zero (0) or more exception {#link Class classes}, which must be
* subclasses of {#link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>By default, a transaction will be rolling back on {#link RuntimeException}
* and {#link Error} but not on checked exceptions (business exceptions). See
* {#link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
* for a detailed explanation.
* <p>This is the preferred way to construct a rollback rule (in contrast to
* {#link #rollbackForClassName}), matching the exception class and its subclasses.
* <p>Similar to {#link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
* #see #rollbackForClassName
* #see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] rollbackFor() default {};
A simple #Transactional(rollbackFor = Exception.class) should work
The thing that you are trying to achieve here is not possible, as once you come out of the method after executing it; changes can not be reverted as you have #Transactional annotation.
Alternatively you could set auto commit false, and write a try catch block in methodA of class A. And if there is no exception commit the DB transaction, or else don't.
Related
I'm having a database with existing tables. One of these is called AKTIVE_AUFTRAEGE with the id "AKTIVE_AUFTRAGE_ID" and the second field "ROBOTER_AUFTRAG_ID". I want to read the data using Spring data. I followed some tutorials an my code looks like this:
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:sqlserver://****;databaseName=****;schema=dbo
spring.datasource.username=****
spring.datasource.password=****
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.jpa.show-sql=true
spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
and
package hello;
import javax.persistence.*;
#Entity
#Table(name = "AKTIVE_AUFTRAEGE")
public class AktiveAuftraege {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="AKTIVE_AUFTRAEGE_ID")
private Integer auftragID;
private Integer ROBOTER_AUFTRAG_ID;
... getter and setter
}
and
package hello;
import org.springframework.data.repository.CrudRepository;
public interface AuftraegeRepsository extends CrudRepository<AktiveAuftraege, Integer> {
AktiveAuftraege findByauftragID(Integer aktive_auftraege_id);
}
and
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
#Controller
public class Test {
#Autowired
private AuftraegeRepsository auftraegeRepsository;
public void testAll(){
if (auftraegeRepsository != null) {
Iterable<AktiveAuftraege> results = auftraegeRepsository.findAll();
for (AktiveAuftraege e : results) {
System.out.println(e);
}
} else {
System.out.println("ISNULL!");
}
}
}
and the main
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
Test t = new Test();
t.testAll();
}
}
I'm very new to Spring data and have a couple of questions:
How can I check if the application has a connection to the database?
Why is the autowired repository always null?
I assume the problem mentioned problem is caused in the application class with the new operator. How can I avoid this? (Or is this fine?)
Do I need to implement all variables of a table in a class or can I just implement the ones I want to get back and igore the other columns?
How can I check if the application has a connection to the database?
If the application starts up correctly, it means you have a connection to the database
Why is the autowired repository always null?
Because you instantiate the Test class yourself, and Spring is thus out of the equation, and thus doesn't inject anything in the object you created behind its back, since it doesn't even know about it.
How can I avoid this?
By getting it from the ApplicationContext returned by SpringApplication.run(), or by creating a bean of type CommandLineRunner and injecting the Test inside it. Check the Spring Boot documentation.
Do I need to implement all variables of a table in a class or can I just implement the ones I want to get back and igore the other columns?
If you just want to read from that table, having only a subset of the columns is fine. But if you want to insert data in it, the other columns will be ignored, and will thus always have their default value (or null if there is no default value).
I have below Service:
#Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
public void test(String idEntity) throws BaseException
{
getCustomerInformationDAO().updatetm(idEntity);
}
This service has been marked as #Service annotation.
I am calling this service from a controller.
#RequestMapping(value="/test", method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_VALUE,consumes = MediaType.APPLICATION_JSON_VALUE)
#Override
public void test(#RequestParam("idEntity") String idEntity) throws BaseException
{
monolithicService.test(idEntity);
}
Below Dao(this has been marked as #Repository) method:
#Override
public void updatetm(String idEntity) throws BaseException
{
updateRecord( "customerinformation-update.updatelfcentitylDt", idEntity );
}
Transaction manager has been marked as
<tx:annotation-driven transaction-manager="transactionManager" />.
With above changes, it doesnt commit the transaction, even if it is successful.
Can anyone help me with this...
I dealt with a similar issue for a whole day.
Just when I was borderline with insanity, I found out that when you use #Transactional in a test the rules are different: by default, your changes are rolled back.
Quick solution: add the annotation #Commit to your method, i.e.:
#Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
#Commit
public void test(String idEntity) throws BaseException
{
getCustomerInformationDAO().updatetm(idEntity);
}
You can read some details in the following text:
One common issue in tests that access a real database is their effect on the state of the persistence store. Even when you use a development database, changes to the state may affect future tests. Also, many operations — such as inserting or modifying persistent data — cannot be performed (or verified) outside of a transaction.
The TestContext framework addresses this issue. By default, the framework creates and rolls back a transaction for each test. You can write code that can assume the existence of a transaction. If you call transactionally proxied objects in your tests, they behave correctly, according to their configured transactional semantics. In addition, if a test method deletes the contents of selected tables while running within the transaction managed for the test, the transaction rolls back by default, and the database returns to its state prior to execution of the test. Transactional support is provided to a test by using a PlatformTransactionManager bean defined in the test’s application context.
If you want a transaction to commit (unusual, but occasionally useful when you want a particular test to populate or modify the database), you can tell the TestContext framework to cause the transaction to commit instead of roll back by using the #Commit annotation.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testing
You probably have two data sources using MapperScan that might be messing up a mybatis config. You need to add SqlSessionFactory and SqlSessionTemplate as mentioned here http://mybatis.org/spring/getting-started.html.
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
#Configuration
#Slf4j
#MapperScan(value = "com.abc.xyx.aaa", sqlSessionTemplateRef = "PrimarySessionTemplate")
public class MyBatisPrimaryConfig {
#Bean(name = "PrimarySessionFactory")
#Primary
public SqlSessionFactory sessionFactory(#Qualifier("PrimaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean.getObject();
}
#Bean(name = "PrimarySessionTemplate")
#Primary
public SqlSessionTemplate primarySessionTemplate(#Qualifier("PrimarySessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
I'm using Google Apps Engine (GAE) with JPA's JpaRepository interfaces and Data Nucleus JPA implementation for GAE DataStore like that:
package com.appspot.repo;
import com.appspot.model.BusStop;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Created by eljah32 on 10/8/2017.
*/
public interface BusStopRepository extends JpaRepository<BusStop, String> {
}
and another repo
package com.appspot.repo;
import com.appspot.model.BusNode;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* Created by eljah32 on 10/8/2017.
*/
public interface BusNodeRepository extends JpaRepository<BusNode, String> {
List<BusNode> findTop1ByLatitude(double latitude);
List<BusNode> findTop1ByLongitude(double longitude);
}
Then the model entities:
package com.appspot.model;
import org.datanucleus.api.jpa.annotations.Extension;
import javax.persistence.*;
/**
* Created by eljah32 on 10/8/2017.
*/
#Entity
public class BusStop {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
public String key;
#OneToOne
public BusNode busNode;
public String name;
public String name_ru;
public String name_tt;
public String name_en;
}
and the other model
package com.appspot.model;
import org.datanucleus.api.jpa.annotations.Extension;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* Created by eljah32 on 10/8/2017.
*/
#Entity
public class BusNode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
private String id;
#org.datanucleus.api.jpa.annotations.Index(unique = "false", name="LAT")
public double latitude;
#org.datanucleus.api.jpa.annotations.Index(unique = "false", name="LON")
public double longitude;
}
and then I'm calling the jpa repo methods in the same method in the controller like that (see getBusStops()):
package com.appspot.controller;
import com.appspot.model.BusNode;
import com.appspot.model.BusRoute;
import com.appspot.model.BusStop;
import com.appspot.repo.BusNodeRepository;
import com.appspot.repo.BusRouteRepository;
import com.appspot.repo.BusStopRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.persistence.EntityManager;
import java.util.List;
/**
* Created by eljah32 on 10/8/2017.
*/
#Controller
#RequestMapping("/bus")
#Transactional
public class BatchStorageController {
private static Logger logger = LoggerFactory.getLogger(BatchStorageController.class);
#Autowired
BusNodeRepository busNodeRepository;
#Autowired
BusStopRepository busStopRepository;
#Autowired
BusRouteRepository busRouteRepository;
#RequestMapping(value = "/nodes", method = RequestMethod.GET)
public #ResponseBody
List<BusNode> getBusRoutes() {
BusNode busNode=new BusNode();
busNode.latitude=50.4;
busNode.longitude=45.5;
BusNode busNode2=new BusNode();
busNode2.latitude=50.5;
busNode2.longitude=45.3;
busNodeRepository.save(busNode);
busNodeRepository.save(busNode2);
return busNodeRepository.findAll();
}
#RequestMapping(value = "/nodes/{latitude}", method = RequestMethod.GET)
public #ResponseBody
List<BusNode> getByLatitude(#PathVariable("latitude") double latitude) {
List<BusNode> busNode=busNodeRepository.findTop1ByLatitude(latitude);
return busNode;
}
#Transactional()
#RequestMapping(value = "/stops", method = RequestMethod.GET)
public #ResponseBody
List<BusStop> getBusStops() {
BusNode busNode=busNodeRepository.findTop1ByLatitude(50.5).get(0);
//BusNode busNode3=new BusNode();
//busNode3.latitude=50.2;
//busNode3.longitude=45.2;
//busNodeRepository.save(busNode3);
BusStop busStop=new BusStop();
busStop.busNode=busNode;
busStop.name="Idel";
busStop.name_en="Idel";
busStop.name_ru="Idel";
busStop.name_tt="Idel";
busStopRepository.save(busStop); // here the exception occurs
return busStopRepository.findAll();
}
}
So, after the method is called, I'm getting the message that Object with id "aglidXNyb3V0ZXNyFAsSB0J1c05vZGUYgICAgICAoAgM" is managed by a different Object Manager; nested exception is javax.persistence.PersistenceException: Object with id "aglidXNyb3V0ZXNyFAsSB0J1c05vZGUYgICAgICAoAgM" is managed by a different Object Manager on line with busStopRepository.save(busStop);
So how to combine the extracting of one entity from the first repository with the storage of another entity in the second repository in the same method? I have never faced the same problem in other JPA implementations.
UPD:
libraries used in pom.xml:
<appengine.version>1.9.30</appengine.version>
...
<!-- Spring data jpa -->
<dependency>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-api-1.0-sdk</artifactId>
<version>${appengine.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.4.Final</version>
</dependency>
<dependency>
<groupId>org.datanucleus</groupId>
<artifactId>datanucleus-enhancer</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.google.appengine.orm</groupId>
<artifactId>datanucleus-appengine</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.datanucleus</groupId>
<artifactId>datanucleus-core</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.datanucleus</groupId>
<artifactId>datanucleus-api-jpa</artifactId>
<version>3.1.3</version>
</dependency>
UPD 2.
the project with the current problem can be fount on https://github.com/Eljah/busroutes-gae/tree/0.1 (tagged)
Despite I didn't got the internals of Data Nucleus and Google's JPA implementation, why I got the exceptions, I said above, I have resolved globally the problem I faced with. If you are facing the same exception it is likely that you are trying to solve the problem of reading many entities and updating few in the same transaction (since not transactional update is failing due to exceptions said above).
The problem is solved architecturally: 1) you should create separate methods in service layer for reading the entities or obtaining counts and NOT making them transactional 2) create separate methods in the service layers obtaining the results of non-transactional methods as parameters and updating the entities; make those methods transactional 3) in controller layer or in your custom layer below the controller but above the service call those methods separately!
My fault was that I supposed that Spring's #Transactional annotation makes sense even when the method with #Transactional is called from another method withot #Transactionl. That is wrong: due to Aspect nature of the annotation, they make sense only if the methods are called from outside class object. So in my examples I got the whole call being within the only transaction (with too many entities exception) or within none transaction (thus getting the Object with id ... is managed by a different Object Manager). So separating non-transactional behaviour and transactional behaviour to the different methods and calling them from outside helped me.
In my Spring Boot 1.5.1 application I'm trying to configure support of JSR-303 / JSR-349 validation.
I have added a following annotations #NotNull #Size(min = 1) to my method:
#Service
#Transactional
public class DecisionDaoImpl extends BaseDao implements DecisionDao {
#Override
public Decision create(#NotNull #Size(min = 1) String name, String description, String url, String imageUrl, Decision parentDecision, Tenant tenant, User user) {
...
}
}
I'm trying to invoke this method from my test, but it does not fail on the validation constraints.
This is my test and configs:
#SpringBootTest(classes = { TestConfig.class, Neo4jTestConfig.class })
#RunWith(SpringRunner.class)
#Transactional
public class TenantTest {
#Test
public void testCreateDecision() {
User user1 = userService.createUser("test1", "test1", "test1#test.com", null, null);
Tenant tenant1 = tenantDao.create("Tenant 1", "Tenant 1 description", false, user1);
// the following line should fail on the validation constraint because name parameter is null but it doesn't
final Decision rootDecision = decisionDao.create(null, "Root decision 1 description", null, tenant1, user1);
...
#Configuration
#ComponentScan("com.example")
#SpringBootApplication(exclude={Neo4jDataAutoConfiguration.class})
public class TestConfig {
}
What am I doing wrong and how to configure JSR-303 there ?
UPDATED
I have added
public Decision create(#Valid #NotNull #Size(min = 1) String name, String description, Decision parentDecision, Tenant tenant, User author) {
but it still doesn't work
I have added #Validated to my DecisionDaoImpl but it fails now with a following exception:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'decisionDaoImpl': Bean with name 'decisionDaoImpl' has been injected into other beans [criterionGroupDaoImpl,characteristicGroupDaoImpl,tenantDaoImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:585)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 43 common frames omitted
I have also added #Lazy annotation in a places where I'm autowiring my DecisionDao but right now my test fails with a following exception:
javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not alter the parameter constraint configuration, but method public com.example.domain.model.entity.decision.Decision com.example.domain.dao.decision.DecisionDaoImpl.create(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Long,java.lang.Long,com.example.domain.model.entity.user.User) changes the configuration of public abstract com.example.domain.model.entity.decision.Decision com.example.domain.dao.decision.DecisionDao.create(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Long,java.lang.Long,com.example.domain.model.entity.user.User).
at org.hibernate.validator.internal.metadata.aggregated.rule.OverridingMethodMustNotAlterParameterConstraints.apply(OverridingMethodMustNotAlterParameterConstraints.java:24)
at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.assertCorrectnessOfConfiguration(ExecutableMetaData.java:456)
Move your validation to interface, as follows:
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public interface DecisionDao {
Decision create(#Valid #NotNull #Size(min = 1) String name,
String description, String url, String imageUrl);
}
Annotate your DecisionDaoImpl with #Validated, as follows:
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
#Service
#Validated
public class DecisionDaoImpl extends BaseDao implements DecisionDao {
#Override
public Decision create(String name,
String description, String url, String imageUrl) {
System.out.println(name);
return new Decision();
}
}
Modify your test case to verify for javax.validation.ConstraintViolationException using assertj or ExpectedException, as follows:
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import javax.validation.ConstraintViolationException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#ContextConfiguration(classes = { TenantTest.Config.class })
#RunWith(SpringRunner.class)
public class TenantTest {
#Autowired
private DecisionDao decisionDao;
#Rule
public ExpectedException expectedException = ExpectedException.none();
#Test
public void testCreateDecisionUsingAssertj() {
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(
() -> decisionDao.create(null,
"Root decision 1 description", null, null));
}
#Test
public void testCreateDecision() {
expectedException.expect(ConstraintViolationException.class);
decisionDao.create(null, "Root decision 1 description", null, null);
}
#Configuration
public static class Config {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
#Bean
public DecisionDao decisionDao() {
return new DecisionDaoImpl();
}
}
}
Make sure you have hibernate-validator in your classpath along with #StanislavL answer:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
And an optional dependency for org.assertj.core.api.Assertions.assertThatExceptionOfType, as:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
For sample example, you can refer arpitaggarwal/jsr-303
You need #Valid annotation
Marks a property, method parameter or method return type for validation cascading.
Constraints defined on the object and its properties are be validated when the
property, method parameter or method return type is validated.
The constraint annotations are meant to applied to JavaBeans. See http://beanvalidation.org/1.0/spec/#constraintsdefinitionimplementation-constraintdefinition
You have the constraint annotation #NotNull, #Size, etc. applied within the DAO. You must create a Java Bean, e.g. "Person", that wraps those attributes (name, description, etc.), then pass "Person" as a parameter to the Controller method. If you need to use a DAO instead of a controller, it will need to be instrumented to perform the validation. You may be on your own in that regard with regard to AOP, etc., unless something has changed since this post: http://forum.spring.io/forum/spring-projects/container/82643-annotation-driven-jsr-303-validation-on-service-and-dao-tier
Update: Well, looks like it (method level validation JSR-349) is supported now see http://blog.codeleak.pl/2012/03/how-to-method-level-validation-in.html for an example, similar to Arpit's answer.
Updated title of question to reflect this latest JSR.
I need to test the validation annotations but it looks like they do not work. I am not sure if the JUnit is also correct. Currently, the test will be passed but as you can see the specified email address is wrong.
JUnit
public static void testContactSuccess() {
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
System.err.println(contact);
}
Class to be tested
public class Contact {
#NotNull
#Size(min = 1, max = 10)
String name;
#NotNull
#Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\."
+"[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#"
+"(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
message="{invalid.email}")
String email;
#Digits(fraction = 0, integer = 10)
#Size(min = 10, max = 10)
String phone;
getters and setters
}
The other answer saying that "the annotations do not do anything by themselves, you need to use a Validator to process the object" is correct, however, the answer lacks working instructions on how to do it using a Validator instance, which for me was what I really wanted.
Hibernate-validator is the reference implementation of such a validator. You can use it quite cleanly like this:
import static org.junit.Assert.assertFalse;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class ContactValidationTest {
private Validator validator;
#Before
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test
public void testContactSuccess() {
// I'd name the test to something like
// invalidEmailShouldFailValidation()
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
assertFalse(violations.isEmpty());
}
}
This assumes you have validator implementation and junit as dependencies.
Example of dependencies using Maven pom:
<dependency>
<groupId>org.hibernate</groupId>
<version>5.2.4.Final</version>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
A simple way to test validation annotations using javax:
Declare the Validator at Class level:
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Then in your test simply call it on the object you require validation on, with what exception you are validating:
Set<TheViolation<TheClassYouAreValidating> violations = validator.validate(theInstanceOfTheClassYouAreValidating);
Then simply assert the number of expected violations:
assertThat(violations.size()).isEqualTo(1);
You will need to add this to your dependencies (gradle):
compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
The annotations do not do anything by themselves, you need to use a Validator to process the object.
Your test needs to run some code like this
Configuration<?> configuration = Validation
.byDefaultProvider()
.providerResolver( new MyResolverStrategy() ) // <== this is where is gets tricky
.configure();
ValidatorFactory factory = configuration.buildValidatorFactory();
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
factory.getValidator().validate(contact); <== this normally gets run in the background by whatever framework you are using
However, the difficulty you face here are these are all interfaces, you will need implementations to be able to test. You could implement it yourself or find one to use.
However the question you want to ask yourself is what are you trying to test? That the hibernate validator works the way it should? or that your regex is correct?
If this was me I would assume that the Validator works(ie someone else tested that) and focus on the regex. Which would involve a bit of reflection
public void emailRegex(String email,boolean validates){
Field field = Contact.class.getDeclaredField("email");
javax.validation.constraints.Pattern[] annotations = field.getAnnotationsByType(javax.validation.constraints.Pattern.class);
assertEquals(email.matches(annotations[0].regexp()),validates);
}
then you can define your testMethods which are actual unit tests
#Test
public void testInvalidEmail() throws NoSuchFieldException {
emailRegex("Jackyahoo.com", false);
}
#Test
public void testValidEmail() throws NoSuchFieldException {
emailRegex("jack#yahoo.com", true);
}
#Test
public void testNoUpperCase() throws NoSuchFieldException {
emailRegex("Jack#yahoo.com", false);
}
First thanks #Eis for the answer, it helped me. It's a good way to fail the test, but I wanted a bit more "life-like" behaviour. At runtime an exception would be thrown so I came up with this:
/**
* Simulates the behaviour of bean-validation e.g. #NotNull
*/
private void validateBean(Object bean) throws AssertionError {
Optional<ConstraintViolation<Object>> violation = validator.validate(bean).stream().findFirst();
if (violation.isPresent()) {
throw new ValidationException(violation.get().getMessage());
}
}
Have an entity with validation:
#Data
public class MyEntity {
#NotBlank(message = "Name cannot be empty!")
private String name;
}
In a test you can pass an instance with invalid attributes and expect an exception:
private Validator validator;
#Before
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test(expected = ValidationException.class)
public void testValidationWhenNoNameThenThrowException() {
validateBean(new Entity.setName(""));
}
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
public class ValidationTest {
private Validator validator;
#Before
public void init() {
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
this.validator = vf.getValidator();
}
#Test
public void prereqsMet() {
Workshop validWorkshop = new Workshop(2, 2, true, 3);
Set<ConstraintViolation<Workshop>> violations = this.validator.validate(validWorkshop);
assertTrue(violations.isEmpty());
}
}
Strictly speaking it is not a unit test, rather an Integration Test. In Unit Test you would like to test the validator logic only, without any dependencies to the SPI.
https://www.adam-bien.com/roller/abien/entry/unit_integration_testing_the_bean
Here my way to unit test my objects with fields annotated with some javax.validation.constraints constraints.
I will give an example with Java 8, JPA entity, Spring Boot and JUnit 5 but the overall idea is the same whatever the context and the frameworks :
We have a nominal scenario where all fields are correctly valued and generally multiple error scenarios where one or more fields are not correctly valued.
Testing field validation is not a particularly hard thing.
But as we have many fields to validate, the tests may become more complex, we can forget some cases, introducing side effects in tests between two cases to validate or simply introduce duplication.
I will give my mind about how to avoid that.
In the OP code, we will suppose that the 3 fields have a NotNull constraint. I think that under 3 distinct constraints, the pattern and its value are less visible.
I wrote first a unit test for the nominal scenario :
import org.junit.jupiter.api.Test;
#Test
public void persist() throws Exception {
Contact contact = createValidContact();
// action
contactRepository.save(contact);
entityManager.flush();
entityManager.clear();
// assertion on the id for example
...
}
I extract the code to create a valid contact into a method as it will be helpful for no nominal cases :
private Contact createValidContact(){
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
contact.setPhone("33999999");
return contact;
}
Now I write a #parameterizedTest with as fixture source a #MethodSource method :
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.validation.ConstraintViolationException;
#ParameterizedTest
#MethodSource("persist_fails_with_constraintViolation_fixture")
void persist_fails_with_constraintViolation(Contact contact ) {
assertThrows(ConstraintViolationException.class, () -> {
contactRepository.save(contact);
entityManager.flush();
});
}
To compile/run #parameterizedTest, think of adding the required dependency that is not included in the junit-jupiter-api dependency :
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
In the fixture method to create invalid contacts, the idea is simple. For each case, I create a new valid contact object and I set incorrectly only the field to validate concerned to.
In this way, I ensure that no side effect between cases are present and that each case provokes itself the expected validation exception as without the field set the valid contact was successful persisted.
private static Stream<Contact> persist_fails_with_constraintViolation_fixture() {
Contact contactWithNullName = createValidContact();
contactWithNullName.setName(null);
Contact contactWithNullEmail = createValidContact();
contactWithNullEmail.setEmail(null);
Contact contactWithNullPhone = createValidContact();
contactWithNullPhone.setPhone(null);
return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone);
}
Here is the full test code :
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.validation.ConstraintViolationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#DataJpaTest
#ExtendWith(SpringExtension.class)
public class ContactRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private ContactRepository contactRepository;
#BeforeEach
public void setup() {
entityManager.clear();
}
#Test
public void persist() throws Exception {
Contact contact = createValidContact();
// action
contactRepository.save(contact);
entityManager.flush();
entityManager.clear();
// assertion on the id for example
...
}
#ParameterizedTest
#MethodSource("persist_fails_with_constraintViolation_fixture")
void persist_fails_with_constraintViolation(Contact contact ) {
assertThrows(ConstraintViolationException.class, () -> {
contactRepository.save(contact);
entityManager.flush();
});
}
private static Stream<Contact> persist_fails_with_constraintViolation_fixture() {
Contact contactWithNullName = createValidContact();
contactWithNullName.setName(null);
Contact contactWithNullEmail = createValidContact();
contactWithNullEmail.setEmail(null);
Contact contactWithNullPhone = createValidContact();
contactWithNullPhone.setPhone(null);
return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone);
}
}
There are 2 things that you need to check:
The validation rules are configured correctly
The validation rules can be checked the way others advise - by creating a validator object and invoking it manually:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator()
Set violations = validator.validate(contact);
assertFalse(violations.isEmpty());
With this you should check all the possible cases - there could be dozens of them (and in this case there should be dozens of them).
The validation is triggered by the frameworks
In your case you check it with Hibernate, therefore there should be a test that initializes it and triggers some Hibernate operations. Note that for this you need to check only one failing rule for one single field - this will be enough. You don't need to check all the rules from again. Example could be:
#Test(expected = ConstraintViolationException.class)
public void validationIsInvokedBeforeSavingContact() {
Contact contact = Contact.random();
contact.setEmail(invalidEmail());
contactsDao.save(contact)
session.flush(); // or entityManager.flush();
}
NB: don't forget to trigger flush(). If you work with UUIDs or sequences as an ID generation strategy, then INSERT is not going to be flushed when you save() - it's going to be postponed until later.
This all is a part of how to build a Test Pyramid - you can find more details here.
such as:
public class Test {
#Autowired
private Validator validator;
public void testContactSuccess() {
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
System.err.println(contact);
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
assertTrue(violations.isEmpty());
}
}
and you also need add bean autowired in your context.xml, such as:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
</bean>
If you try using new versions of the validator but land on that thread (like me), you will start getting tons of wired exceptions. So should have in mind that to do test with Hibernate 7+
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.2.Final</version>
<scope>test</scope>
</dependency>
should be sure that you are NOT using
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
but switched to
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.1</version>
</dependency>
and have
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>
For those with Spring Boot with Spring-Data-JPA you just need to autowire the validator
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.validation.Validator;
import static org.assertj.core.api.Assertions.assertThat;
#SpringBootTest
class ValidatorTest {
#Autowired
private Validator validator;
#Test
void ensureValidatorIsLoaded() {
assertThat(validator).isNotNull();
}
#Test
void failValidate() {
final var violations = validator.validate(new StartRequest());
assertThat(violations).isNotEmpty();
}
#Test
void passValidate() {
final var startRequest = StartRequest.builder()
.contentType("foo/bar")
.contentMd5Hash("abcdef1234567890abcdef1234567890")
.category("Pc")
.contentLength(55)
.siteId("ca1")
.desiredExpiration(55)
.build();
final var violations = validator.validate(startRequest);
assertThat(violations).isEmpty();
}
}
I think validations would work after calling predefined methods which is usually done by the containers mostly not immediately after calling setters of the object. From the documentation link you shared:
>
By default, the Persistence provider will automatically perform validation on entities with persistent fields or properties annotated with Bean Validation constraints immediately after the PrePersist, PreUpdate, and PreRemove lifecycle events.