I am facing an issue that has been mentioned before with Spring Boot vs. Hibernate Validation, where autowiring of dependencies inside custom Constraint Validators is not working. From my own debugging, I have noticed that when entity-level validation occurs, Hibernate loads a different ConstraintValidatorManager compared to when Hibernate is performing bean validation for form submits. The latter works fine, the former leads to dependencies of the custom Constraint Validator being null. It seems as if Hibernate is loading one manager from the root context and one from the servlet context. This would explain Hibernate not having any knowledge of the existence of the dependencies autowired in the custom Constraint Validator. If this is true however, I do not understand what is going on, or how to make Hibernate/JPA aware of the Spring context and it's beans.
I am hoping someone could point me in the right direction? I have tried all of the below answers, and much more (e.g. different library versions, configuration methods, static bean loading through a utils class, etc.):
Inject Repository inside ConstraintValidator with Spring 4 and message interpolation configuration
Autowired gives Null value in Custom Constraint validator
Also I have been through the Reference guide for Spring Boot specifically several times, without much luck. There are several cases that mention their Hibernate validation working fine, both for regular bean submits, as well as during entity persisting. Unfortunately, I seem unable to retrieve their exact (Java) configuration they used, but it seems they are using default configuration. I am starting to wonder if this is a specific Spring Boot issue (although it is stated a combination of Spring Validation and Hibernate Validation should work out-of-the-box).
Adding anything like below bean does not solve the issue (default factory being SpringConstraintValidatorFactory ofcourse):
#Bean
public LocalValidatorFactoryBean validator()
{
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
Nor does including a bean definition for a Hibernate validator as such:
Autowired gives Null value in Custom Constraint validator
There are many different ways of loading and injecting the desired bean, but if Hibernate is not at all aware of the beans loaded in the context (because it is using a different context?), how to proceed?
Thanks in advance.
UPDATE: Gradle file
buildscript {
ext {
springBootVersion = '2.1.5.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = '<hidden>'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
jcenter()
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.springframework.boot:spring-boot-starter-tomcat:2.1.5.RELEASE')
implementation('org.springframework.boot:spring-boot-starter-thymeleaf')
implementation('org.springframework.boot:spring-boot-starter-security')
implementation('org.springframework.boot:spring-boot-starter-data-jpa')
implementation('org.springframework.boot:spring-boot-starter-mail')
implementation('org.springframework.session:spring-session-core')
annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')
implementation('org.postgresql:postgresql')
// https://mvnrepository.com/artifact/org.jboss.aerogear/aerogear-otp-java
implementation('org.jboss.aerogear:aerogear-otp-java:1.0.0')
implementation('com.github.mkopylec:recaptcha-spring-boot-starter:2.2.0')
implementation('nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:2.0.5')
implementation('org.thymeleaf.extras:thymeleaf-extras-springsecurity3:3.0.4.RELEASE')
implementation('javax.enterprise:cdi-api:2.0')
runtimeOnly('org.springframework.boot:spring-boot-devtools')
testImplementation('org.springframework.boot:spring-boot-starter-test')
testImplementation('org.springframework.security:spring-security-test')
testImplementation 'org.mockito:mockito-core:2.27.0'
}
There is a way to tell Hibernate to use the same validator by setting javax.persistence.validation.factory
#Configuration
#Lazy
class SpringValidatorConfiguration {
#Bean
#Lazy
public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(final Validator validator) {
return new HibernatePropertiesCustomizer() {
#Override
public void customize(Map<String, Object> hibernateProperties) {
hibernateProperties.put("javax.persistence.validation.factory", validator);
}
};
}
}
That way everything works fine.
Regarding the fix, just to summarize a more extensive/integrated answer for others that are/were dealing with these sorts of issues, my configuration now contains all of these beans:
#Bean
public LocalValidatorFactoryBean validator()
{
LocalValidatorFactoryBean validatorFactory = new LocalValidatorFactoryBean();
validatorFactory.setValidationMessageSource(messageSource());
return validatorFactory;
}
#Bean
public MessageSource messageSource()
{
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor()
{
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
#Bean
public HibernatePropertiesCustomizer hibernatePropertiesCustomizer()
{
return properties ->
{
properties.put("javax.persistence.validation.factory", validator());
// Add more properties here such as validation groups (see comment for SO example)
};
}
For an example on adding hibernate validation groups to tease apart validation of different life-cycle events (e.g. bean vs. entity), see Hibernate validations on save (insert) only
Related
I recently migrated my spring boot/batch Java application from spring-boot/spring-framework (respectively) 1.x.x/4.x.x to => 2.x.x/5.x.x (2.2.4/5.2.3 to be specific). The problem is something is definitely wrong (in my opinion) with the transaction/entity manager, as when the .saveAll() method is called from the JpaRepository class of my database persistance layer, it jumps into the SpringAOP framework/libarary code and into a infinite loop. I see it returning a "DefaulTransaction" object from a method (invoke()). My application on 1.x.x/4.x.x when it worked, would return the actual ArrayList here of my entities. I am using spring-boot-starter, spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-batch, and hibernate/hibernate-envers/hibernate-entitymanager (also of course many other dependencies, let me know if you would like me to list them).
After some research, I'm finding people are saying that Spring Batch #EnableBatchProcessing annotation sets up a default transaction manager, which if I'm using JPA could be causing issues. Reference:
https://github.com/spring-projects/spring-boot/issues/2363
wilkinsona suggested defining this Bean in my #Configuration class:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
return new BasicBatchConfigurer(dataSource, entityManagerFactory);
}
I'm getting an error when I do this because its saying the BasicBatchConfigurer() has protected access. What is the best way to instantiate this?
I also saw some people saying removing the #EnableBatchProcessing annotation fixes the persistance to database issue, but when I remove this, I lose the ability to Autowire my JobBuilderFactory and StepBuilderFactory. Is there a way to remove the annotation and get these objects in my code so I can at-least test if this works? Sorry, I'm not completely a master with Spring Batch/Spring.
In my #Configuration class, I am using the PlatformTransactionManager. I am setting up my JobRepository something like this.:
#Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager,
#Qualifier("dataSource") DataSource dataSource) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
I can provide any other information if needed. Another question is - if I was using the same code basically, transaction manager, entity manager etc.. how was old my code working on 1.x.x? Could I have a wrong dependency somewhere in my pom.xml such that my new migrated code is using a wrong method or something from the wrong dependency?
By default, #EnableBatchProcessing configures Spring Batch to use a DataSourceTransactionManager if you provide a DataSource. This transaction manager knows nothing about your JPA context. So if you want to use a JPA repository to save data, you need to configure Spring Batch to use a JpaTransactionManager.
Now in order to provide a custom transaction manager, you need to register a BatchConfigurer and override the getTransactionManager() method, something like:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager();
}
};
}
This is explained in the Configuring A Job section and in the Javadoc of #EnableBatchProcessing.
I am fairly new to working with Spring and I've come across a problem I cannot seem to resolve. I am trying to work with a package called Optaplanner. In this case I'm more or less following along with a simple course scheduler the video is here and the part I am working on happens at 51:00.
My problem is, one step of the process requires a dependency injection and when I build/run the Spring application I get the following error:
Description:
Field solverManager in com.java.optaplex.resource.MainResource required a bean of type 'org.optaplanner.core.api.solver.SolverManager' that could not be found.
The injection point has the following annotations:
- #javax.inject.Inject()
Action:
Consider defining a bean of type 'org.optaplanner.core.api.solver.SolverManager' in your configuration.
Process finished with exit code 1
I have used Gradle to manage my optaplanner package and I can see that the dependency is present (it is an interface called SolverManager). I cannot figure out why it cannot find/generate the bean however. I am assuming Spring would generate the bean on the fly much like a JPA repository's bean is created when using the #Autowired decorator. I have tried copying and pasting the SolverManager code to its own file under my project root and tried a few decorators on it (eg. #Component) in hopes that Spring would detect it, yet it still throws the same error and I am unsure what to do.
I have created a very simple demo app on GitHub here that has the error. The error is in the file src/main/java/com/java/optaplex/resource/MainResource.java
Also, in IntelliJ the SolverManager instance solverManager (see code below) is highlighted and says:
Could not autowire. No beans of 'SolverManager<Test, Long>' type found.
MainResource.java:
package com.java.optaplex.resource;
import com.java.optaplex.pojo.Test;
import org.optaplanner.core.api.solver.SolverManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.inject.Inject;
#RestController
#RequestMapping("/api")
public class MainResource {
// THE OFFENDING LINE IS HERE
#Inject
SolverManager<Test, Long> solverManager;
#GetMapping("/test")
public Test test () {
return new Test("Result message here");
}
}
For clarity, my Test class in the declaration of the Solver manager is:
Test.java
package com.java.optaplex.pojo;
public class Test {
private String message;
public Test(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
I realize my example code doesn't actually make use of the SolverManager, I just want the error on build to stop so I can proceed developing this. I just wanted to provide a very simple case that caused the error.
Lastly, my Gradle dependencies look like this:
build.gradle:
plugins {
id 'org.springframework.boot' version '2.2.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'war'
}
group = 'com.java'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
// https://mvnrepository.com/artifact/org.optaplanner/optaplanner-core
compile group: 'org.optaplanner', name: 'optaplanner-core', version: '7.36.1.Final'
compile group: 'javax.inject', name: 'javax.inject', version: '1'
}
test {
useJUnitPlatform()
}
To make it work you just need to replace the #Inject annotation with #Autowire.
From the latest versions of Spring, the best practice is to use constructor dependency injection instead of #Autowire (the annotation is not required anymore as Spring will autowire beans automatically.
Try this:
#RestController
#RequestMapping("/api")
public class MainResource {
// Just the definition here
private SolverManager<Test, Long> solverManager;
pubilc MainResource(SolverManager<Test, Long> solverManager) {
// When Spring will start, the bean will be injected here and become
// available in this class
this.solverManager = solverManager;
}
#GetMapping("/test")
public Test test () {
// .....
}
// Other controller methods ....
}
I'm using IntelliJ, and it reports the error to me too. But I'm using the same code that I've posted in this answer and it works.
As you stated, the SolverManager bean is configured inside the optaplanner artifact, I suspect that the problem here is only the wrong annotation being used.
The IDE error bothers me too! But for the moment it works and I will leave it like this. In case I'll find a solution then I will add it here.
I am developing a API REST of route optimization with Spring Boot using OptaPlanner. I am just getting into the work with OptaPlanner and at one point I had this same problem. I had 'optaplanner-spring-boot-starter' and I added spring-boot-configuration-processor', with the idea of having the bean creation configuration that allows injecting the SolverManager in my classes, without success. Also I tried to change to other versions of OptaPlanner and without success. Finally I solved the problem adding the configuration that OptaPlanner includes in its code with the class 'RouteOptimizerConfig'. This class creates the bean of the solver, in such a way that already later I could inject it. This problem seems to be related to the fact that at a certain point the OptaPlanner SolverManager is overwritten.
Try this and add this class to your project:
#Configuration
class SolverConfig {
private final SolverFactory<VehicleRoutingSolution> solverFactory;
SolverConfig (SolverFactory<VehicleRoutingSolution> solverFactory) {
this.solverFactory = solverFactory;
}
#Bean
Solver<VehicleRoutingSolution> solver() {
return solverFactory.buildSolver();
}
}
It turns out, after a lot of online searches and digging through the Optaplanner Spring Boot packages the SolverManager bean is not provided for Spring. It seems the development focuses on the Quarkus framework.
I figured it was a good chance to try out Quarkus and once I made the change my code worked. There are some good videos on Youtube by the lead developer Geoffrey De Smet on setting up Optaplanner via Quarkus.
I'm using Spring 4.2.7/Spring Boot 1.3.7 and I think the HibernateValidator is the default implementation that Spring uses in spring-web.
I would like to apply a configuration on the HibernateValidator like the following:
HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ).configure();
configuration.allowMultipleCascadedValidationOnReturnValues( true )
.allowOverridingMethodAlterParameterConstraint( true )
.allowParallelMethodsDefineParameterConstraints( true );
described in here:
https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-method-validation-prerequisite-relaxation
I've tried defining a #Bean with something like this:
#Bean
#Primary
public ValidatorFactory validatorFactory() {
// ... the above snippet
return configuration.getValidatorFactory()
}
But it looks the configuration is not applied.
I also tried defining the Validator bean like this:
#Bean
#Primary
public Validator validator(ValidatorFactory validatorFactory) {
return validatorFactory.getValidator();
}
But the defined configuration does not seem to apply to the validator Spring is finally using.
I am doing something wrong?
Which should be the appropriate way to apply a configuration (configure/customize) like the one in the above example to the Spring Hibernate validator?
We have a Spring-Boot application with some database-entity-classes.
We use ddl-auto: validate to make sure the connected database has correct schema.
Now we are at the point where we add a feature which is toggle-able to match different environments, the NewFeatureService is annotated with #ConditionalOnProperty("newfeature.enabled").
Everything works fine until here.
The problem is that the feature requires a database entity.
#Entity
#ConditionalOnProperty("newfeature.enabled") // <--- doesn't work
public class NewFeatureEnitity{...}
#ConditionalOnProperty will obviously not work but what is a good way to tell Hibernate only to validate this Entity against the database if a property is set.
What we don't want:
add this table in all databases even if feature isn't used
having different artifacts
Just to be sure that it's not overseen i want to provide my suggestion as an answer.
It makes a bit more use of spring-boot than the answer provided by O.Badr.
You could configure your spring-boot application to only scan your core entities like this:
#SpringBootApplication
#EntityScan("my.application.core")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
So you are able to provide your optional entities (and features) in a package like my.application.features (feel free to use any other structure but a package outside the previously specified base package).
#ConditionalOnProperty("newfeature.enabled")
#Configuration
#EntityScan("my.application.features.thefeature")
public class MyFeatureConfiguration {
/*
* No Configuration needed for scanning of entities.
* Do here whatever else should be configured for this feature.
*/
}
Hibernate will consider every #Entity class as an entity, as long as the class/package is included in Hibernate setting.
But, Spring came up with handy (and simple) solutions, so as you mention, you can use #ConditionalOnProperty (I assume that you're using java configuration for Spring):
In your Hibernate configuration class :
#Configuration
......
public class HibernateConfig {
#Bean
#ConditionalOnProperty(name = "newfeature.enabled")
public String newFeaturePackageToScan(){
return "com.example.so.entity.newfeature";
}
#Bean
public String commonPackageToScan(){
return "com.example.so.entity.common";
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(#Autowired DataSource dataSource, #Autowired String[] packagesToScan){
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
Properties jpaProperties = new Properties();
jpaProperties.put(AvailableSettings.HBM2DDL_AUTO, "validate");
jpaProperties.put(...); // other properties
emf.setDataSource(dataSource);
emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emf.setJpaProperties(jpaProperties);
emf.setPackagesToScan(packagesToScan);
return emf;
}
...
}
#Autowired String[] packagesToScan will combine all defined string beans in this array (I assume that you do not define any other string bean), so you can add as much as String bean for other features, You can check documentation for more details.
The point is to separate a newfeature package from common package in a way that the common is not a parent package.
I have multiple Spring Boot Starters, each of which define a DataSource like this:
#Bean
#ConfigurationProperties(prefix = "some.unique.namespace.datasource")
public DataSource someUniqueNamespaceDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public SomeOtherBean someOtherBean() {
return new SomeOtherBean(someUniqueNamespaceDataSource())
}
As you can see, the bean method someUniqueNamespaceDataSource() is being called directly in another bean method, within the same configuration class. However, Spring Boot is intercepting the method, and then performing its own internal injection. This time, it injects with a type of DataSource.
When an application uses one of these starters, it works without issue. However, when it uses multiple starters, I get errors like this:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: someUniqueNamespaceDataSource,someOtherUniqueNamespaceDataSource
I believe this is because Spring Boot is internally injected by type, even though my code injects a qualified bean.
Is there some way that the starter libraries can indicate that the DataSources should not be considered candidates for auto-configuration?
Is there some way that an application depending on more than one of these starter libraries can exclude them from auto-configuration?
Disabling auto-configuration entirely is not really viable. Additionally, manually excluding all current auto-configurations that trigger on existence of a DataSource bean is far too brittle because the addition of dependencies later, especially transitive dependencies, which trigger based on a DataSource bean, will reintroduce the error.
In your #SpringBootApplication or #EnableAutoConfiguration annotations set exclude property to:
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
That should do the trick.