Programmatically adding hibernate interceptor in JpaProperties - Spring - java

I'm writing a library with spring boot, and I need to programmatically insert a hibernate interceptor through it (because I can't use a .properties in a lib).
I want to avoid providing my own sessionFactory bean, I think it would be good to leave that possibility to an implementing project, also saves me from manually scanning for entities.
My dumb idea was that I could "inject" my interceptor into the JpaProperties. That didn't work at all, it ran the #PostConstruct but nothing changed. I had a feeling this wouldn't work, but I would like to understand why, and how I may get this to work.
#Autowired private JpaProperties properties;
#Autowired private MyInterceptor myInterceptor; //yep a bean
#PostConstruct public void add() {
((Map) properties.getProperties())
.put(
"hibernate.session_factory.interceptor",
myInterceptor
);
}

As this is using an #PostConstruct annotation, the addition to the JpaProperties will only occur after the EntityManagerFactoryBuilder has been created in JpaBaseConfiguration. This means that changes to the property map will not be present in the builder after this point.
To customize the JpaProperties, you should instantiate a bean which adds your configuration in, like:
#Primary
#Bean
public JpaProperties jpaProperties() {
JpaProperties properties = new JpaProperties();
properties.getProperties().put("hibernate.session_factory.interceptor", myInterceptor);
return properties;
}
This will then be injected into HibernateJpaConfiguration and used when constructing the EntityManagerFactoryBuilder.

Related

How to use the JdbcTemplate that is configured and available through Spring-Boot in the JobRepository?

Hiho,
we use Spring-Batch 4.3.5 inside a Spring-Boot 2.6.7 service. All things work fine so far. While unit testing the use-cases, we realized that the BatchAutoConfiguration/BatchConfigurerConfiguration creates a JobRepository. This JobRepository needs and wants some JdbcOperations. Because no instance of JdbcOperations is taken from the Spring application context while initializing all the beans, the JobRepositoryFactoryBean decides to create a fresh instance of type JdbcTemplate and attach it to the JobRepository.
Therefore I would like to ask if there is an 'easy' possibility to attach the instance of the JdbcTemplate that is provided by Spring-Boot? Is there another possibility as overwriting the whole initialization mechanism? Do we need to provide our own BatchConfigurer?
Any help is really appreciated! :)
That is not possible. You need to provide a custom BatchConfigurer and use any bean auto-configured by Boot to configure your job repository. Here is a quick example:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, JdbcTemplate jdbcTemplate) {
return new DefaultBatchConfigurer(dataSource) {
#Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factoryBean = new JobRepositoryFactoryBean();
factoryBean.setJdbcOperations(jdbcTemplate);
// set other properties on the factory bean
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
};
}
In this snippet, the dataSource and jdbcTemplate passed as parameters to the batchConfigurer method will be those auto-configured by Boot (and autowired by Spring).

#Autowired bean property is null in #Configuration bean

I have a problem with #Autowired property in #Configuration bean.
I have a bean similar to the one below:
#Configuration
public class MyConfig {
#Autowired MongoTemplate mongoTemplate;
#Bean
MongoDbMetadataStore indexMetadataStore() {
return new MongoDbMetadataStore(mongoTemplate, "index");
}
}
and ... mongoTemplate is null while creating indexMetadataStore bean (checked with debugger). Unfortunately I cannot provide entire project structure, it is large (it has ~5 XML config files and around 20-30 #Configuration beans) and my bet is that there could a circular reference or something of sort.
However, this mongoTemplate bean is created earlier and injected to other beans (also checked with debugger), so at this point mongoTemplate is fully created and I cannot understand why it is not injected and left null.
Any ideas where I should look?
Ok, I found out a problem. I will describe it here, so that perhaps someone else may find this answer useful and save precious time resolving it :).
It turned out that there was a circular reference and Spring was doing its best to initialize and use not-fully-initialized config objects. There were config1 and config2 beans (both #Configuration) that used objects from each other.
It is interesting to know that in such a situation Spring tries to initialize #Resource, #Autowired and #Value in the following order:
#Resource is initialized first, in the order that objects were declared in the #Configuration bean
#Value is treated as #Autowired. Hence, #Value and #Autowired are initialized in the order of appearance AFTER all #Resource beans are initialized.
It is important to understand the above order, because your beans and circular reference may rely on #Value settings and such setting may still be null while creating a resource referenced from other config bean.
However, the best strategy is to avoid circular references and finally that is what I did - put offending resources to new, third, config bean.
The order statement seems to be true. I have a number of #Autowired properties in my #Configuration bean. Those at the end of the list appear as null when trying to use them in my #Bean.
#Configuration
public class MyConfig {
#Autowired
private MyClass myClass;
#Autowired
private MyClass2 myClass2;
...
#Autowired
private MyArgument1 myArgument1;
#Autowired
private MyArgument2 myArgument2;
#Bean(name = "myList")
public List<MyInterface> getMyList() { //given MyArgument1 & MyArgument2 implement MyInterface
List<MyInterface> myList= new ArraList<MyInterface>();
myList.add(myArgument1); //resolved as null
myList.add(myArgument2); //resolved as null
return myList;
}
}
If I put them in the top of the list they are resolved properly. So.. how many should I count on being properly resolved? Need a better approach.
This is working
#Configuration
public class MyConfig {
#Autowired
private MyClass myClass;
#Autowired
private MyClass2 myClass2;
...
#Bean(name = "myList")
public List<myInterface> getMyList((#Qualifier("myArgument1") MyInterface myArgument1, (#Qualifier("myArgument2") MyInterface myArgument2) { //given myArgument1 & myArgument 2 are properly labeled as #Component("myArgument1") & #Component("myArgument2")
List<myInterface> myList= new ArraList<myInterface>();
myList.add(myArgument1); //resolved!
myList.add(myArgument2); //resolved!
return myList;
}
}
This seems to implement the right dependency

Spring Boot not reading from property file

I have tried every option on web but not able to set the values in following method:
#Configuration
#PropertySource("classpath:application.properties")
public class MyDataSource {
#Value("${db.driver}")
private String DB_DRIVER;
#Value("${db.url}")
private String DB_URL;
#Value("${db.username}")
private String DB_USERNAME;
#Value("${db.password}")
private String DB_PASSWORD;
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(DB_DRIVER);
dataSource.setUrl(DB_URL);
dataSource.setUsername(DB_USERNAME);
dataSource.setPassword(DB_PASSWORD);
return dataSource;
}
}
My application.properties is in main/resources folder and values can be seen in variables in debug mode. But on running app, it shows Property ' ' must not be empty.
EDIT: I am not sure what can be the issue in first case?
So changed the application.property file as suggested and code as below :
#Autowired
protected JdbcTemplate jdbcTemp;
public List<> getData(String id) {
return jdbcTemp.query("SELECT ........,new RowMapper());
}
But getting java.lang.NullPointerException:
If you're using Spring Boot, you can leverage application.properties file by declaring some entries:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
In this way there is no need to implement a #Configuration class to setup database connection in Spring Boot.
You can deepen more here:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html
By the way, take a look at spring.io
For the java configuration, using Environment instance to obtain the properties seems to be the preferred way, as by default ${..} placeholders are not resolved.
You may use something like this:
#Autowired
private Environment env;
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver");
.....
return dataSource;
}
Reasons from the Spring Jira:
it's inconsistent. #PropertySource is the declarative counterpart to ConfigurableEnvironment#addPropertySource. We do not add a
PropertySourcesPlaceholderConfigurer in the latter case, and it would
be inconsistent to do so in the former. it will not be what the user
intended in every (or even most) cases.
It is entirely possible, and even recommended that #Configuration class users forego $ {...} property replacement entirely, in favor of
Environment#getProperty lookups within #Bean methods. For users
following this recommendation, the automatic registration of a
PropertySorucesPlaceholderConfigurer would be confusing when noticed,
and generally undesirable as it's one more moving part. Yes, it's
presence is benign, but not cost-free. a PSPC must visit every bean
definition in the container to interrogate PropertyValues, only to do
nothing in cases where users are going with the
Environment#getProperty approach.
it is solvable (and already solved) by documentation. Proper use of #PropertySource, PropertySourcesPlaceholderConfigurer and other
components is pretty comprehensively documented in the Javadoc for
#Configuration already, and reference documentation is soon to follow.
Me too was getting the error when tried to switch from MySQL to MSSQL. The actual issue was I forgot to put the MSSQL dependency in the service. I used mssql-jdbc

How to inject different services at runtime based on a property with Spring without XML

I am using Spring Boot for Java standalone application. I have a bean which makes use of a service. I want to inject different implementations of that service at runtime, based on a property in a properties file with Spring (4 for that matter).
This sounds like the Factory pattern, but Spring also allows using annotations to solve the problem, like this.
#Autowired #Qualifier("selectorProperty") private MyService myService;
Then in the beans.xml file I have an alias, so that I can use the property in the #Qualifier.
<alias name="${selector.property}" alias="selectorProperty" />
And in my different implementations I would have different qualifiers.
#Component("Selector1")
public class MyServiceImpl1
#Component("Selector2")
public class MyServiceImpl2
application.properties
selector.property = Selector1
selector.property = Selector2
Whereas regarding the factory pattern, in Spring you can use ServiceLocatorFactoryBean to create a factory that would give you the same functionality.
<bean
class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean"
id="myServiceFactory">
<property
name="serviceLocatorInterface"
value="my.company.MyServiceFactory">
</property>
</bean>
public interface MyServiceFactory
{
MyService getMyService(String selector);
}
And then in your bean you can use something like this to get the right implementation at runtime depending on the value of the property.
#Value("${selector.property}") private String selectorProperty;
#Autowired private MyServiceFactory myServiceFactory;
private MyService myService;
#PostConstruct
public void postConstruct()
{
this.myService = myServiceFactory.getMyService(selectorProperty);
}
But the problem with this solution is that I could not find a way to avoid using XML to define the factory, and I would like to use only annotations.
So the question would be, is there a way to use the ServiceLocatorFactoryBean (or something equivalent) using only annotations, or am I forced to use the #Autowired #Qualifier way if I do not want to define beans in XML? Or is there any other way to inject different services at runtime based on a property with Spring 4 avoiding XML? If your answer is just use the #Autowired #Qualifier with the alias, please give a reason why that is better than using a well known factory pattern.
Using the extra XML is forcing me to use #ImportResource("classpath:beans.xml") in my Launcher class, which I'd rather not use either.
Thanks.
Actually, you can use ServiceLocatorFactory without XML by declaring it as a bean in your configuration file.
#Bean
public ServiceLocatorFactoryBean myFactoryServiceLocatorFactoryBean()
{
ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
bean.setServiceLocatorInterface(MyServiceFactory.class);
return bean;
}
#Bean
public MyServiceFactory myServiceFactory()
{
return (MyServiceFactory) myFactoryServiceLocatorFactoryBean().getObject();
}
Then you can still use the factory as usual, but no XML is involved.
#Value("${selector.property}") private String selectorProperty;
#Autowired #Qualifier("myServiceFactory") private MyServiceFactory myServiceFactory;
private MyService myService;
#PostConstruct
public void postConstruct()
{
this.myService = myServiceFactory.getMyService(selectorProperty);
}
I'm using Spring profiles
For example with dataSources
Using it you can define as many dataSources, as you like
#Configuration
#Profile("dev")
public class StandaloneDataConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
#Configuration
#Profile("cloud")
public class CloudDataConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
And in runtime, by specifying
-Dspring.profiles.active="myProfile"
you active one or another configuration (All of them must be imported in your main Configuration, they are just ignored based on active profile).
Here is a good article:
http://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/

Spring batch 2.2 JavaConfig

I'm trying to get Spring Batch 2.2 working with JavaConfig.
Nowadays they have a #EnableBatchProcessing annotation that sets up a lot of things.
Default that annotation uses a datasource for its job data, but we don't want to save this data and don't want to create the table for it. The documentation says something about customizing but I have not been able to get it working:
The user has to provide a DataSource as a bean in the context, or else implement BatchConfigurer in the configuration class itself, e.g.:
public class AppConfig extends DefaultBatchConfigurer {
In our older version we've been able to use MapJobRepositoryFactoryBean class so it keeps all its data in memory. Is there anyway to use the full JavaConfig way and not define a DataSource? I've not been able to get it working.
Even if I define two data sources (one HSQL in-memory that never gets used) and our real Oracle datasource it does not work because it finds two data sources instead of one.
Anyone have an idea how to get this working? Or is the only solution going back to configuring this in the XML way?
Assuming that no other artifacts require a DataSource, you can use java config to create a context without a DataSource. To do that, your configuration will need to extend DefaultBatchConfigurer as you point out. In there, you'll override two methods, createJobRepository() and setDataSource(). Below is an example context (it doesn't define a job or steps, but it bootstraps all the related beans correctly).
#Configuration
#EnableBatchProcessing
public static class BatchConfiguration extends DefaultBatchConfigurer {
#Override
protected JobRepository createJobRepository() throws Exception {
MapJobRepositoryFactoryBean factory =
new MapJobRepositoryFactoryBean();
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
#Override
#Autowired
public void setDataSource(DataSource dataSource) {
if(dataSource != null) {
super.setDataSource(dataSource);
}
}
#Bean
public DataSource dataSource() {
return null;
}
}
I do think that simplifying this would be a useful feature and have added it to Jira. You can track it's progress here: https://jira.springsource.org/browse/BATCH-2048
Just define a dataSource() method in your BatchConfig Class
Here is how
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(driverUrl);
dataSource.setUsername(driverUsername);
dataSource.setPassword(driverPassword);
return dataSource;
}
This will automatically be invoked while setting up the TransactionManager

Categories

Resources