Hello I want to inject EntityManager within ConstraintValidator
This is my codes
CoreConfiguration
#Configuration
public class CoreConfiguration {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
#Bean(name="validator")
public static LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
/* * ReloadableResourceBundleMessageSource messageSource = new
* ReloadableResourceBundleMessageSource();
* messageSource.setBasename("/WEB-INF/messages/validation");
* localValidatorFactoryBean.setValidationMessageSource(messageSource);*/
return localValidatorFactoryBean;
}
}
UniqueKeyValidator.java
In this class I try to inject the EntityManager, but it always gives null
#Component
public class UniqueKeyValidator implements
ConstraintValidator<Unique, Serializable> {
#PersistenceContext
private EntityManager entityManager;
private Class<?> entityClass;
private String uniqueField;
public void initialize(Unique unique) {
entityClass = unique.entity();
uniqueField = unique.property();
}
#Transactional
public boolean isValid(Serializable property,
ConstraintValidatorContext cvContext) {
String query = String.format("from %s where %s = :val ",entityClass.getName(), uniqueField);
List<?> list = entityManager.createQuery(query).setParameter("val", property).getResultList();
return list != null && list.size() > 0;
}
}
When I test the code, the EntityManager always gives null.
You created the UniqueKeyValidator using its constructor (new UniqueKeyValidator()) instead of injecting it (using Spring DI) into your test class.
The CoreConfiguration doesn't contain this validator anyway, so probably Spring doesn't scan it at all. All #Component classes must have a component:scan too.
Related
I have created a spring-data project (version = 2.5.5) with two datasources GitHub. It works fine, as long, as I am using the interface CrudRepository.java. But when I am trying to implement my own DAO based on SimpleJpaRepository.java I am getting the error
No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: db1TransactionManager,db2TransactionManager
DB2Config.java (DB1Config.java is the same but instead of '2' the bean names has '1')
#Configuration
#PropertySource("classpath:db2-config.properties")
#EnableJpaRepositories(
basePackages = "com.vscoding.jpa.db2.entity",
entityManagerFactoryRef = "db2EntityManagerFactory",
transactionManagerRef = "db2TransactionManager"
)
public class DB2Config {
#Bean
#ConfigurationProperties(prefix = "db2.datasource")
public DataSource db2DataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public FactoryBean<EntityManagerFactory> db2EntityManagerFactory(#Qualifier("db2DataSource") DataSource db2DataSource, Environment env){
var em = new LocalContainerEntityManagerFactoryBean();
var va = new HibernateJpaVendorAdapter();
var properties = new HashMap<String,Object>();
properties.put("hibernate.hbm2ddl.auto","create");
em.setDataSource(db2DataSource);
em.setPackagesToScan("com.vscoding.jpa.db2.entity");
em.setJpaVendorAdapter(va);
em.setJpaPropertyMap(properties);
return em;
}
#Bean
public PlatformTransactionManager db2TransactionManager(#Qualifier("db2EntityManagerFactory") FactoryBean<EntityManagerFactory> db2EntityManagerFactory) throws Exception {
var tm = new JpaTransactionManager();
tm.setEntityManagerFactory(db2EntityManagerFactory.getObject());
return tm;
}
#Bean
#Profile("with-init")
public DataSourceInitializer dataSourceInitializer2(#Qualifier("db2DataSource") DataSource datasource) {
var populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("db2.sql"));
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(datasource);
dataSourceInitializer.setDatabasePopulator(populator);
return dataSourceInitializer;
}
}
ProductCustomRepository.java
#Repository
#Transactional(transactionManager = "db2TransactionManager")
public class ProductCustomRepository extends SimpleJpaRepository<ProductEntity2, Integer> {
private final EntityManager entityManager;
public ProductCustomRepository(#Qualifier("db2EntityManagerFactory") EntityManager entityManager) {
super(ProductEntity2.class, entityManager);
this.entityManager = entityManager;
}
public List<ProductEntity2> customQuery() {
var query = entityManager.createQuery("SELECT p FROM ProductEntity2 p WHERE p.name='special'",ProductEntity2.class);
return query.getResultList();
}
}
I would expect, that #Transactional(transactionManager = "db2TransactionManager") would select the right transactionManager, but maybe I am missing something.
ProductCustomRepositoryTest.java (test to reproduce the error)
#SpringBootTest
class ProductCustomRepositoryTest {
#Autowired
private ProductCustomRepository sut;
#Test
void customQuery() {
//Given
var special = new ProductEntity2("special");
sut.save(special);
//When
var result = sut.customQuery();
//Then
assertTrue(result.contains(special));
}
}
Thanks in advance.
As mentioned by Simon, I could solve the error, by following https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
Changed my ProductCustomRepository.javato CustomRepositoryImpl.java and added Interface CustomRepository.java
#Repository
#Transactional(transactionManager = "db2TransactionManager")
public class CustomRepositoryImpl implements CustomRepository {
// no changes here
}
Then extend my main Repository with the interface.
public interface ProductRepository2 extends CrudRepository<ProductEntity2,Integer>,CustomRepository {
}
i´m going to write a library that does some stuff and uses spring data.
The idea is that projects which uses this library can import this jar and use this library: MyLib.doSomeStuff().
It is possible to use Spring in this way and how can i initialize the ApplicationContext within the doSomeStuff() method, so that DI and the #Configuration Classes with the DataSources will be loaded?
public class MyLib {
#Autowired
private static SomeJpaRepository someJpaRepository;
public static void doSomeStuff(){
...init ApplicationContext....
...setup DataSources...
List<SomeEntity> someEntityList = someJpaRepository.someMethod();
}
// or
public static List<SomeEntity> getSomeEntityList() {
return someJpaRepository.finAll();
}
}
//other package
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "gxEntityManager", transactionManagerRef = "gxTransactionManager",
basePackages = "com.gx")
public class GxConfig {
#Primary
#Bean(name = "gxDataSource")
public DataSource gxDataSource() {
DataSourceBuilder dataSourceBuilderGx = null;
//..
return dataSourceBuilderGx.build();
}
#Primary
#Bean(name = "gxEntityManager")
public LocalContainerEntityManagerFactoryBean gxEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(gxDataSource()).packages("com.gx").build();
}
#Primary
#Bean(name = "gxTransactionManager")
public PlatformTransactionManager gxTransactionManager(
#Qualifier("gxEntityManager") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
//other package
public interface SomeEntity extends JpaRepository<SomeEntity, Long>
{
SomeEntity findById(Long id);
}
If you have a root configuration class it can be as simple as
ApplicationContext context =
new AnnotationConfigApplicationContext(GxConfig.class);
Just don't do it every time you call doStuff() as creating an application context is expensive. If you library is meant to be used as a black box, I guess it's ok to have this isolated application context.
You can do something like this:
public class MyLib {
private ApplicationContext context;
public MyLib() {
context = new AnnotationConfigApplicationContext(GxConfig.class);
}
public void doStuff() {
SomeBean bean = context.getBean(SomeBean.class);
// do something with the bean
}
}
Why would I be getting a cast error from a proxy based on a bean that is properly implementing an interface? Package and object names have been changed to protect proprietary data.
I know the error
java.lang.ClassCastException: com.sun.proxy.$Proxy20 cannot be cast to package.Order
normally comes up when one is not implementing an interface as is expected. In this case I am implementing interfaces for facilitating Proxy generation.
My specific problem is using a collection of domain objects in an enhanced for loop within an ItemWriter implementation. This exception is being thrown from the enhanced for loop I reference below.
public void write(List<? extends Order> items) throws Exception {
for ( Order order : items){
Order is an interface implemented by OrderBean.
public class OrderBean implements Order
And is declared as a Prototype for use by a BeanWrapperFieldSetMapper in a Spring Java Configuration class. As below.
#Bean
#Scope("prototype")
public Order order()
As an experiment I commented out the Java Configuration declaration, and replaced it with the XML declaration below.
<bean name="order"
scope="prototype"
class="package.OrderBean"/>
The entirety of my configuration appears below as requested in comments. I am not sure why the Order objects are being proxied, unless possibly it comes from the BeanWrapperFieldSetMapper.
Upon further testing, I found I get the same error from any bean set with the Step scope, as in #Scope("step").
#Configuration
public class OrderProcessingBatchConfiguration {
#Value("${batch.jdbc.driver}")
private String driverClassName;
#Value("${batch.jdbc.url}")
private String driverUrl;
#Value("${batch.jdbc.user}")
private String driverUsername;
#Value("${batch.jdbc.password}")
private String driverPassword;
#Value("${order.delimiter}")
private String delimiter;
#Value("${order.item.field.names}")
private String[] orderItemFieldNames;
#Value("${order.item.file.path}")
private String orderItemFilePath;
#Value("${order.field.names}")
private String[] orderFieldNames;
#Value("${order.file.path}")
private String orderFilePath;
#Value("${query.order.clear}")
private String deleteOrderQuery;
#Value("${query.order.item.clear}")
private String deleteOrderItemQuery;
#Value("${ftp.host.name}")
private String ftpHostName;
#Value("${ftp.host.port}")
private Integer ftpHostPort;
#Value("${ftp.client.mode}")
private Integer ftpClientMode;
#Value("${ftp.file.type}")
private Integer ftpFileType;
#Value("${ftp.host.username}")
private String ftpUsername;
#Value("${ftp.host.password}")
private String ftpPassword;
#Value("${ftp.tasklet.retryIfNotFound}")
private Boolean retryIfNotFound;
#Value("${ftp.tasklet.download.attempts}")
private Integer downloadAttempts;
#Value("${ftp.tasklet.retry.interval}")
private Integer retryInterval;
#Value("${ftp.tasklet.file.name.pattern}")
private String fileNamePattern;
#Value("${ftp.host.remote.directory}")
private String remoteDirectory;
#Value("${ftp.client.local.directory}")
private File localDirectory;
#Value("${ftp.tasklet.sftp}")
private Boolean sftp;
#Value("${query.order.insert}")
private String orderInsertQuery;
#Value("${query.order.items.insert}")
private String orderItemInsertQuery;
#Autowired
#Qualifier("jobRepository")
private JobRepository jobRepository;
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(driverUrl);
dataSource.setUsername(driverUsername);
dataSource.setPassword(driverPassword);
return dataSource;
}
#Bean
public SimpleJobLauncher jobLauncher() {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository);
return jobLauncher;
}
#Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
#Bean
#Scope("prototype")
public OrderItem orderItem(){
return new OrderItemBean();
}
#Bean
#Scope("prototype")
public Order order(){
return new OrderBean();
}
#Bean
//#Scope("step")
public DelimitedLineTokenizer orderItemLineTokenizer(){
DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer(delimiter);
lineTokenizer.setNames(orderItemFieldNames);
return lineTokenizer;
}
#Bean
//#Scope("step")
public BeanWrapperFieldSetMapper<OrderItem> orderItemFieldSetMapper(){
BeanWrapperFieldSetMapper<OrderItem> orderItemFieldSetMapper = new BeanWrapperFieldSetMapper<OrderItem>();
orderItemFieldSetMapper.setPrototypeBeanName("orderItem");
return orderItemFieldSetMapper;
}
#Bean
//#Scope("step")
public DefaultLineMapper<OrderItem> orderItemLineMapper(){
DefaultLineMapper<OrderItem> orderItemLineMapper = new DefaultLineMapper<OrderItem>();
orderItemLineMapper.setLineTokenizer(orderItemLineTokenizer());
orderItemLineMapper.setFieldSetMapper(orderItemFieldSetMapper());
return orderItemLineMapper;
}
#Bean
//#Scope("step")
public Resource orderItemResource(){
Resource orderItemResource = new FileSystemResource(orderItemFilePath);
return orderItemResource;
}
#Bean
//#Scope("step")
public FlatFileItemReader<OrderItem> orderItemItemReader(){
FlatFileItemReader<OrderItem> orderItemItemReader = new FlatFileItemReader<OrderItem>();
orderItemItemReader.setLineMapper(orderItemLineMapper());
orderItemItemReader.setResource(orderItemResource());
return orderItemItemReader;
}
#Bean
//#Scope("step")
public DelimitedLineTokenizer orderLineTokenizer(){
DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer(delimiter);
lineTokenizer.setNames(orderFieldNames);
return lineTokenizer;
}
#Bean
//#Scope("step")
public BeanWrapperFieldSetMapper<Order> orderFieldSetMapper(){
BeanWrapperFieldSetMapper<Order> orderItemFieldSetMapper = new BeanWrapperFieldSetMapper<Order>();
orderItemFieldSetMapper.setPrototypeBeanName("order");
return orderItemFieldSetMapper;
}
#Bean
//#Scope("step")
public DefaultLineMapper<Order> orderLineMapper(){
DefaultLineMapper<Order> orderItemLineMapper = new DefaultLineMapper<Order>();
orderItemLineMapper.setLineTokenizer(orderLineTokenizer());
orderItemLineMapper.setFieldSetMapper(orderFieldSetMapper());
return orderItemLineMapper;
}
#Bean
//#Scope("step")
public Resource orderResource(){
Resource orderItemResource = new FileSystemResource(orderFilePath);
return orderItemResource;
}
#Bean
//#Scope("step")
public FlatFileItemReader<Order> orderItemReader(){
FlatFileItemReader<Order> orderItemItemReader = new FlatFileItemReader<Order>();
orderItemItemReader.setLineMapper(orderLineMapper());
orderItemItemReader.setResource(orderResource());
return orderItemItemReader;
}
#Bean
#Scope("step")
public Map<String, Order> orderCache(){
Map<String, Order> orderCache = new HashMap<String, Order>();
return orderCache;
}
#Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}
#Bean
//#Scope("step")
public AggregatingFlatFileOrderItemReader aggregatingFlatFileOrderItemReader(){
AggregatingFlatFileOrderItemReader aggregatingFlatFileOrderItemReader = new AggregatingFlatFileOrderItemReader();
aggregatingFlatFileOrderItemReader.setJdbcTemplate(jdbcTemplate());
aggregatingFlatFileOrderItemReader.setOrderCache(orderCache());
aggregatingFlatFileOrderItemReader.setOrderItemFlatFileItemReader(orderItemItemReader());
aggregatingFlatFileOrderItemReader.setOrderFlatFileItemReader(orderItemReader());
aggregatingFlatFileOrderItemReader.setDeleteOrderQuery(deleteOrderQuery);
aggregatingFlatFileOrderItemReader.setDeleteOrderItemQuery(deleteOrderItemQuery);
return aggregatingFlatFileOrderItemReader;
}
#Bean
#Scope("step")
public SessionFactory ftpSessionFactory(){
DefaultFtpSessionFactory ftpSessionFactory = new DefaultFtpSessionFactory();
ftpSessionFactory.setHost(ftpHostName);
ftpSessionFactory.setClientMode(ftpClientMode);
ftpSessionFactory.setFileType(ftpFileType);
ftpSessionFactory.setPort(ftpHostPort);
ftpSessionFactory.setUsername(ftpUsername);
ftpSessionFactory.setPassword(ftpPassword);
return ftpSessionFactory;
}
#Bean
#Scope(value="step")
public FtpGetRemoteFilesTasklet myFtpGetRemoteFilesTasklet(){
FtpGetRemoteFilesTasklet ftpTasklet = new FtpGetRemoteFilesTasklet();
ftpTasklet.setRetryIfNotFound(retryIfNotFound);
ftpTasklet.setDownloadFileAttempts(downloadAttempts);
ftpTasklet.setRetryIntervalMilliseconds(retryInterval);
ftpTasklet.setFileNamePattern(fileNamePattern);
ftpTasklet.setRemoteDirectory(remoteDirectory);
ftpTasklet.setLocalDirectory(localDirectory);
ftpTasklet.setSessionFactory(ftpSessionFactory());
ftpTasklet.setSftp(sftp);
return ftpTasklet;
}
#Bean
#Scope(value="step")
public OrderItemWriter orderItemWriter(){
OrderItemWriter orderItemWriter = new OrderItemWriter();
orderItemWriter.setJdbcTemplate(jdbcTemplate());
orderItemWriter.setOrderInsertQuery(orderInsertQuery);
orderItemWriter.setOrderItemInsertQuery(orderItemInsertQuery);
return orderItemWriter;
}
Given the following service:
public interface MyService {
void method();
}
And it's implementation:
#Service
public class MyServiceImpl implements MyService {
#Transactional
#CustomAnnotation
#Override
public void method() {
...
}
}
I would like to use a StaticMethodMatcherPointcutAdvisor in the following manner:
public class MyPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
...
#Override
public boolean matches(Method method, Class targetClass) {
Method m = method;
if(annotationPresent(method)) {
return true;
}
Class<?> userClass = ClassUtils.getUserClass(targetClass);
Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if(annotationPresent(specificMethod )) {
return true;
}
return false;
}
...
}
The problem is that Spring uses an InfrastructureAdvisorAutoProxyCreator to create a Proxy of that class, whereas the DefaultAdvisorAutoProxyCreator would create the proxy for the MyPointcutAdvisor, but the MyPointcutAdvisor is only given the proxy as targetClass parameter. Thus, the PointcutAdvisor cannot find the annotation and therefore does not match.
For completion this is my Configuration-class:
#Configuration
#EnableTransactionManagement
public class MyConfiguration {
#Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
#Bean
public MyPointcutAdvisor myPointcutAdvisor() {
return new MyPointcutAdvisor();
}
...
}
My question is: Is there a way to use #EnableTransactionManagement in combination with a StaticMethodMatcherPointcutAdvisor ?
Workarounds:
Put #CustomAnnotation into the service interface: I want to have clean interfaces.
Add #Role(BeanDefinition.ROLE_INFRASTRUCTURE) to MyPointCutAdvisor bean configuration, thus, the InfrastructureAdvisorAutoProxyCreator will create the proxy. This seems like the wrong way, since this bean is not infrastructure
Copy the beans from ProxyTransactionManagementConfiguration, remove #EnableTransactionManagement and remove #Role(BeanDefinition.ROLE_INFRASTRUCTURE), thus the DefaultAdvisorAutoProxyCreator will create the proxy, which is my current workaround and results in the following configuration:
#Configuration
public class MyWorkaroundConfiguration {
#Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
#Bean
public MyPointcutAdvisor myPointcutAdvisor() {
return new MyPointcutAdvisor();
}
#Bean
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
#Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor =
new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor);
return advisor;
}
#Bean
public TransactionInterceptor transactionInterceptor(
PlatformTransactionManager transactionManager) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
interceptor.setTransactionManager(transactionManager);
return interceptor;
}
...
}
Using #EnableAspectJAutoProxy instead of the DefaultAutoProxyCreator works for me.
#Configuration
#EnableAspectJAutoProxy
#EnableTransactionManagement
public class MyConfiguration {
}
This also allows using #Aspect like M. Deinum suggested.
I have AnnotationConfigApplicationContext created with #Configuration annotated class as a param:
#Configuration
class Config {
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
#Bean
public A aBean() {
return new A();
}
#Bean
public B aBean() {
return new B();
}
}
Where A and B are:
class A {
#Min(1)
public int myInt;
}
class B {
#Autowire(required = true)
#Valid
public A aBean;
}
Q: Is it possible to make Spring to process #Valid annotation in this case?
PS: Currently I have following working implementation of B:
class B {
public A aBean;
public void setABean(A aBean, Validator validator) {
if (validator.validate(aBean).size() > 0) {
throw new ValidationException();
}
this.aBean = aBean;
}
}
This impl seems a bit clumsy to me and I want to replace it. Please help :)
It looks like you want to validate your bean in the process of injection.
You can read
here.
Here is an example:
public class BeanValidator implements org.springframework.validation.Validator, InitializingBean {
private Validator validator;
public void afterPropertiesSet() throws Exception {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.usingContext().getValidator();
}
public boolean supports(Class clazz) {
return true;
}
public void validate(Object target, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(target);
for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
String propertyPath = constraintViolation.getPropertyPath().toString();
String message = constraintViolation.getMessage();
errors.rejectValue(propertyPath, "", message);
}
}
}
You will need to implement InitializingBean, to be able to validate the bean after it was set.