Environement is Null when I try to Autowire - spring

I am trying to use Environment abstraction & #PropertySource of Spring to load and use properties in my #Configuration annotated classes. However I get Environment as null in my PropertyConfig class as it is accessed from another #Configuration class PersistenceConfig which used it to access the properties. Here is my relevant code :
#Configuration
#PropertySource({ "classpath:/properties/email_${environment}.properties" })
#PropertySource({ "classpath:/properties/appconfig.properties" })
#PropertySource({ "classpath:/properties/ApplicationResources.properties" })
#PropertySource({ "classpath:/properties/Database_${environment}.properties" })
#PropertySource({ "classpath:/properties/log4j.properties" })
#PropertySource({ "classpath:/properties/system.properties" })
public class PropertiesConfig {
#Autowired
private Environment env;
private static final PropertiesAccessor propertyAccessor = new PropertiesConfig().new PropertiesAccessor();
public static String getPopertyValue(String property){
return propertyAccessor.getPropertyValue(property);
}
private class PropertiesAccessor{
public String getPropertyValue(String key){
return env.getProperty(key);
}
}
}
My Other #Configuration annotated class PersistenceConfig is as follows :
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {"com.template"})
public class PersistenceConfig {
#Bean
public LocalSessionFactoryBean sessionFactory(){
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String [] {"com.template.domain" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public BasicDataSource dataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(PropertiesConfig.getPopertyValue("jdbc.driverClassName"));
dataSource.setUrl(PropertiesConfig.getPopertyValue("jdbc.url"));
dataSource.setUsername(PropertiesConfig.getPopertyValue("jdbc.user"));
dataSource.setPassword(PropertiesConfig.getPopertyValue("jdbc.pass"));
return dataSource;
}
#Bean
public HibernateTransactionManager transactionManager(){
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
Properties hibernateProperties(){
return new Properties() {
{
setProperty("hibernate.hbm2ddl.auto", PropertiesConfig.getPopertyValue("hibernate.hbm2ddl.auto"));
setProperty("hibernate.dialect", PropertiesConfig.getPopertyValue("hibernate.dialect"));
setProperty("hibernate.globally_quoted_identifiers", "true");
}
};
}
}
However I get NullpointerException when dataSource() method of PersistenceConfig tries to retrieve properties using PropertiesConfig.getPopertyValue("jdbc.driverClassName") because env of type Environment is null in PropertyConfig.
I am loading both classes as follows in my WebApplicationInitializer :
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(PropertiesConfig.class,SecurityConfig.class,PersistenceConfig.class,ApplicationConfig.class);
//rootContext.register(ApplicationConfig.class, PersistenceConfig.class, SecurityConfig.class); I have not added security yet
// Manage the life-cycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
dispatcherServlet.register(MvcConfig.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
As far as I understand PersistenceConfig is being loaded first before PropertyConfig. Am I right? Or is there any other reason? How to make this work?

package com.template.config;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;import
org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
#Configuration
#PropertySource({ "classpath:/properties/email_${environment}.properties" })
#PropertySource({ "classpath:/properties/appconfig.properties" })
#PropertySource({ "classpath:/properties/ApplicationResources.properties" })
#PropertySource({ "classpath:/properties/Database_${environment}.properties"
})
#PropertySource({ "classpath:/properties/log4j.properties" })
#PropertySource({ "classpath:/properties/system.properties" })
public class PropertiesConfig {
#Autowired
private Environment env;
private static Environment environment;
#PostConstruct
public void init(){
environment = env;
System.out.println(environment == env);
}
public static String getPopertyValue(String property){
return environment.getProperty(property);
}
}

use #PostConstruct on a method to process what you want .Because you can't get inject bean before spring init container,the inject must be after refresh operation.
eg:
#Component
public class envConfig {
#Autowired
private Environment env;
//something want to get
private String[] profiles;
#PostConstruct
public void init(){
//get the env properties or throw injected bean to init other bean
this.profiles=env.getActiveProfiles();
}
}

I was facing the similar issue. There are many different questions around this problem. There are many answered. However i found the reason in below blog
https://allaboutspringframework.com/spring-fix-null-autowired-field/
In the end of this blog author has concluded the findings which is important.
Spring dependency injection only works with Spring-managed objects or Beans. If the object in which a Bean is getting injected is not a spring managed object, you will get null #Autowired fields. To fix this, make sure only the framework create and manage the related dependencies.

Related

Why are the data sources interfering in Spring Batch when using a RepositoryItemReader?

I am trying to migrate some data between a Postgres database and MongoDB using Spring Batch. I have a very simple ItemReader, ItemProcessor, and ItemWriter configured, and it everything works as intended. However, if I switch to a RepositoryItemReader, I'm getting the following error:
java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#684430c1] for key [HikariDataSource (HikariPool-1)] bound to thread
If I understand correctly, there is something wrong with the EntityManager or TransactionManager, but I cannot figure out what, and why it's working with a simple ItemReader that doesn't work with a repository, but it uses the same data source.
I would be very grateful for any help.
Here is my source db configuration:
package com.example.batch.primary;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager",
basePackages = {"com.example.batch.primary"}
)
public class PrimaryDBConfig {
#Bean(name = "primaryDataSource")
#Primary
public DataSource primaryDatasource(){
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create()
.driverClassName("org.postgresql.Driver")
.url("jdbc:postgresql://localhost:5432/postgres")
.username("test")
.password("test");
return dataSourceBuilder.build();
}
#Bean(name = "primaryEntityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("primaryDataSource")
DataSource primaryDataSource){
return builder.dataSource(primaryDataSource)
.packages("com.example.batch.primary")
.build();
}
#Bean(name = "primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(
#Qualifier("primaryEntityManagerFactory") EntityManagerFactory primaryEntityManagerFactory)
{
return new JpaTransactionManager(primaryEntityManagerFactory);
}
}
Here is the configuration of MongoDB:
package com.example.batch.secondary;
#EnableMongoRepositories(basePackages = "com.example.batch.secondary")
#Configuration
public class MongoDBConfig {
#Bean
public MongoClient mongo() {
ConnectionString connectionString = new ConnectionString("mongodb+srv://mongoadmin:blablabla.mongodb.net/?retryWrites=true&w=majority");
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), "test");
}
}
Here is the RepositoryItemReader:
package com.example.batch.stepcomponents;
#Component
public class RepositoryReader extends RepositoryItemReader<Partner> {
public RepositoryReader(#Autowired PartnerRepository partnerRepository){
setRepository(partnerRepository);
setPageSize(1);
setSort(Map.of("id", Sort.Direction.ASC));
setMethodName("findAll");
}
}
Batch Config:
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Autowired
RepositoryReader repositoryReader;
#Autowired
CustomWriter customWriter;
#Autowired
CustomProcessor customProcessor;
#Bean
public Job createJob() {
return jobBuilderFactory.get("MyJob")
.incrementer(new RunIdIncrementer())
.flow(createStep())
.end()
.build();
}
#Bean
public Step createStep() {
return stepBuilderFactory.get("MyStep")
.<Partner, Student> chunk(1)
.reader(repositoryReader)
.processor(customProcessor)
.writer(customWriter)
.build();
}
}
So I tried taking out the EntityManagerFactory and the TransactionManager, and now it works. I guess they are already initialized automatically when starting up the server..
Yes, by default, if you provide a DataSource bean, Spring Batch will use a DataSourceTransactionManager, not the JPA one as you expect. This is explained in the Javadoc of EnableBatchProcessing:
The transaction manager provided by this annotation will be of type:
* ResourcelessTransactionManager if no DataSource is provided within the context
* DataSourceTransactionManager if a DataSource is provided within the context
In order to use the JPA transaction manager, you need to configure a custom a BatchConfigurer and override getTransactionManager, something like:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(entityManagerFactory);
}
};
}
Note this will not be required anymore starting from v5, see:
Revisit the configuration of infrastructure beans with #EnableBatchProcessing
Spring Batch 5.0.0-M6 and 4.3.7 are out!
You can also set the JPA transaction manager on your step:
#Bean
public Step createStep(JpaTransactionManager jpaTransactionManager) {
return stepBuilderFactory.get("MyStep")
.<Partner, Student> chunk(1)
.reader(repositoryReader)
.processor(customProcessor)
.writer(customWriter)
.transactionManager(jpaTransactionManager)
.build();
}
Adding 'spring-data-jpa' as a dependency will automatically configure aJpaTransactionManager if no other TransactionManager is defined

problem with writing to 2 databases from Spring boot

I was trying this code from github:https://github.com/kodinor/spring-data-many-dbs
It's an example of how to use multiple db's from a Spring-boot application. it worked fine but I added the Spring-boot-starter-web dependency and now I'm getting an error:
>Method requestMappingHandlerMapping in >org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$En>ableWebMvcConfiguration required a single bean, but 2 were found:
> - productDSEmFactory: defined by method 'productDSEmFactory' in class >path resource [com/kodinor/configuration/ProductDBConfiguration.class]
> - userDSEmFactory: defined by method 'userDSEmFactory' in class path >resource [com/kodinor/configuration/UserDBConfiguration.class]
>
>
>Action:
>
>Consider marking one of the beans as #Primary, updating the consumer to >accept multiple beans, or using #Qualifier to identify the bean that should >be consumed<br>
I have two config files:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = UserRepository.class, entityManagerFactoryRef = "userDSEmFactory", transactionManagerRef = "userDSTransactionManager")
public class UserDBConfiguration {
#Primary
#Bean
#ConfigurationProperties("spring.datasource1")
public DataSourceProperties userDSProperties() {
return new DataSourceProperties();
}
#Primary
#Bean
public DataSource userDS(#Qualifier("userDSProperties") DataSourceProperties userDSProperties) {
return userDSProperties.initializeDataSourceBuilder().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
#Primary
#Bean
public PlatformTransactionManager userDSTransactionManager(EntityManagerFactory userDSEmFactory) {
return new JpaTransactionManager(userDSEmFactory);
}
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = ProductRepository.class, entityManagerFactoryRef = "productDSEmFactory", transactionManagerRef = "productDSTransactionManager")
public class ProductDBConfiguration {
#Bean
#ConfigurationProperties("spring.datasource2")
public DataSourceProperties productDSProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource productDS(#Qualifier("productDSProperties") DataSourceProperties productDSProperties) {
return productDSProperties.initializeDataSourceBuilder().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean productDSEmFactory(#Qualifier("productDS") DataSource productDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(productDS).packages(Product.class).build();
}
#Bean
public PlatformTransactionManager productDSTransactionManager(EntityManagerFactory productDSEmFactory) {
return new JpaTransactionManager(productDSEmFactory);
}
}
Application.properties:
spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=true
spring.datasource1.url=jdbc:mysql://localhost:3306/userdb?autoReconnect=true&useSSL=false
spring.datasource1.username=user
spring.datasource1.password=pass
spring.datasource2.url=jdbc:mysql://localhost:3306/productdb?autoReconnect=true&useSSL=false
spring.datasource2.username=user
spring.datasource2.password=pass
And a simple main app that adds some init data:
#SpringBootApplication
public class SpringDataManyDbsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataManyDbsApplication.class, args);
}
#Autowired
private UserRepository userRepository;
#Autowired
private ProductRepository productRepository;
#PostConstruct
void init () {
User user = new User("john", "doe");
userRepository.save(user);
Product product = new Product("chair", 5);
productRepository.save(product);
}
}
I tried to add the #Primary annotation to userDSEmFactory. The error goes away but the product data isn't saved anymore. Any ideas how to save this problem? I don't have a lot of experience with Spring-boot and I've read dozens of articles but many seem to do things in a different way. Thanks so much for helping me out!
update
I've added the #Primary annotation like this:
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
No there are no more errors, but only the user is being saved in the primary db and nothing is being saved to the product (second) database.
So if anybody has suggestions what could be the cause of that..
You are missing #Primary annotation on your userDSEmFactory
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
And also it is goot to use once #EnableTransactionManagement at your main class.

How to use user defined database proxy in #DataJpaTest

We need to track database metrics so we are using datasource-proxy to track this to integrate the same in spring boot project we have created custom datasource
as below
#Component
#Slf4j
#ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceBeanConfig
{
public DataSource actualDataSource()
{
EmbeddedDatabaseBuilder databaseBuilder = new EmbeddedDatabaseBuilder();
return databaseBuilder.setType(EmbeddedDatabaseType.H2).build();
}
#Bean
#Primary
public DataSource dataSource() {
// use pretty formatted query with multiline enabled
PrettyQueryEntryCreator creator = new PrettyQueryEntryCreator();
creator.setMultiline(true);
log.info("Inside Proxy Creation");
SystemOutQueryLoggingListener listener = new SystemOutQueryLoggingListener();
listener.setQueryLogEntryCreator(creator);
return ProxyDataSourceBuilder
.create(actualDataSource())
.countQuery()
.name("MyDS")
.listener(listener)
.build();
}
}
When we run main application datasource-proxy is picked up but when we use #DataJpaTest it is not picking up. How to enable datasource-proxy in JUNIT test cases?
Edit::
Using Spring BeanPostProcessor to configure Proxy DataSource
#Slf4j
#Configuration
public class DataSourceBeanConfig implements BeanPostProcessor
{
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException
{
if (bean instanceof DataSource)
{
System.out.println("AfterInitialization : " + beanName);
// use pretty formatted query with multiline enabled
PrettyQueryEntryCreator creator = new PrettyQueryEntryCreator();
creator.setMultiline(true);
log.info("Inside Proxy Creation");
SystemOutQueryLoggingListener listener = new SystemOutQueryLoggingListener();
listener.setQueryLogEntryCreator(creator);
return ProxyDataSourceBuilder.create((DataSource) bean).countQuery()
.name("MyDS").listener(listener).build();
}
return bean; // you can return any other object as well
}
}
Here is the solution we need to create TestConfiguration to use in #DataJpaTest
#RunWith(SpringRunner.class)
#DataJpaTest
public class DataTestJPA
{
#TestConfiguration
static class ProxyDataSourceConfig implements BeanPostProcessor
{
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException
{
if (bean instanceof DataSource)
{
return ProxyDataSourceBuilder
.create((DataSource) bean)
.countQuery()
.name("MyDS")
.build();
// #formatter:on
}
return bean; // you can return any other object as well
}
}
}

JPA with Spring MVC Configured via Annotations

I am trying to create a Spring MVC application leveraging JPA for its persistence layer. Unfortunately, I was getting a NullPointerException when accessing the EntityManager as Spring does not appear to be injecting it. My configuration is all annotation-based with #EnableWebMvc. After some searching, I added #Transactional on my DAO and #EnableTransactionManagement on my #Configuration class. Then I got an error about not having a DataSource. Supposedly, a class with #EnableTransactionManagement needs to implement TransactionManagementConfigurer. However, I am having problems figuring out how to create the DataSource as well as why it cannot get it from my persistence.xml.
I would greatly appreciate any help in trying to get the EntityManager injected into my DAO.
My #Configuration class
#Configuration
#EnableWebMvc
#EnableTransactionManagement
#ComponentScan("com.example.myapp")
public class MvcConfig extends WebMvcConfigurerAdapter
implements TransactionManagementConfigurer {
private static final boolean CACHE_ENABLED = true;
private static final String TEMPLATE_PATH = "/WEB-INF/freemarker";
private static final String TEMPLATE_SUFFIX = ".ftl";
private static final Logger LOG = Logger.getLogger( MvcConfig.class );
#Override
public void addResourceHandlers( ResourceHandlerRegistry registry ) {
registry.addResourceHandler( "/stylesheets/**" ).addResourceLocations( "/stylesheets/" );
}
#Bean
public FreeMarkerConfigurer configureFreeMarker() {
final FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath( TEMPLATE_PATH );
return configurer;
}
#Bean
public ViewResolver configureViewResolver() {
final FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache( CACHE_ENABLED );
resolver.setSuffix( TEMPLATE_SUFFIX );
return resolver;
}
#Bean
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager();
}
}
My DAO
#Component
#Transactional
public class MyDAO {
private static final Logger LOG = Logger.getLogger( MyDAO.class );
#PersistenceContext
private EntityManager entityManager;
public MyClass getMyClass() {
LOG.debug( "getMyClass()" );
final CriteriaQuery<MyClass> query = criteriaBuilder.createQuery( MyClass.class );
// more code here, but it breaks by this point
return myData;
}
}
My Updated Code
I have reached the point in which it almost all works. The EntityManager is being injected properly. However, transactions are not working. I get errors if I try to use a RESOURCE_LOCAL approach so I am looking at JTA managed transactions. When I add #Transactional on any of my DAO methods, I get a "Transaction marked for rollback" error with no further details in any log files to assist troubleshooting. If I remove the annotation from a basic read-only select, the select will work perfectly fine (not sure if I should even be putting the annotation on select-only methods). However, I obviously need this working on methods which perform db writes. If I debug through the code, it seems to retrieve the data perfectly fine. However, as it returns from the method, the javax.transaction.RollbackException gets thrown. From my understanding of everything, it seems as if the exception occurs in the AOP post-method processing.
My #Configuration class
#Configuration
#EnableWebMvc
#EnableTransactionManagement
#ComponentScan("com.example.myapp")
public class MvcConfig extends WebMvcConfigurerAdapter {
private static final boolean CACHE_ENABLED = true;
private static final String TEMPLATE_PATH = "/WEB-INF/freemarker";
private static final String TEMPLATE_SUFFIX = ".ftl";
private static final Logger LOG = Logger.getLogger( MvcConfig.class );
#Override
public void addResourceHandlers( ResourceHandlerRegistry registry ) {
registry.addResourceHandler( "/stylesheets/**" ).addResourceLocations( "/stylesheets/" );
}
#Bean
public FreeMarkerConfigurer configureFreeMarker() {
final FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath( TEMPLATE_PATH );
return configurer;
}
#Bean
public ViewResolver configureViewResolver() {
final FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache( CACHE_ENABLED );
resolver.setSuffix( TEMPLATE_SUFFIX );
return resolver;
}
#Bean
public PlatformTransactionManager transactionManager() {
return new JtaTransactionManager();
}
#Bean
public AbstractEntityManagerFactoryBean entityManagerFactoryBean() {
LocalEntityManagerFactoryBean factory = new LocalEntityManagerFactoryBean();
factory.setPersistenceUnitName( "my_db" );
return factory;
}
}
In my application I didn't implement TransactionManagerConfigurer interface. I use next code to configure JPA (with Hibernate implementation). You can do the same in your configuration class.
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean factoryBean =
new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setPackagesToScan(new String[] {"com.dimasco.springjpa.domain"});
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
//vendorAdapter.setGenerateDdl(generateDdl)
factoryBean.setJpaVendorAdapter(vendorAdapter);
Properties additionalProperties = new Properties();
additionalProperties.put("hibernate.hbm2ddl.auto", "update");
factoryBean.setJpaProperties(additionalProperties);
return factoryBean;
}
#Bean
public DataSource dataSource() {
final ComboPooledDataSource dataSource = new ComboPooledDataSource();
try {
dataSource.setDriverClass(driverClass);
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setMinPoolSize(3);
dataSource.setMaxPoolSize(15);
dataSource.setDebugUnreturnedConnectionStackTraces(true);
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
Hope this will help you)
EDIT:
You can get datasource using JNDI lookup:
#Bean
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
More details you can find in this article. There is example with JndiDatasourceConfig class.
EDIT 2:
I ahve persistence.xml in my project, but it is empty:
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="JPA_And_Spring_Test">
</persistence-unit>
</persistence>
And I didn't specify any persistent unit name in my java configuration.
The following might help, even though it uses XML-based configuration:
https://github.com/springinpractice/sip13/blob/master/helpdesk/src/main/resources/spring/beans-repo.xml
It uses Spring Data JPA, but you don't have to do that. Use
#PersistenceContext private EntityManager entityManager;
(But consider Spring Data JPA as it provides very capable DAOs out of the box.)
Side note: For DAOs, favor #Repository over #Component. Both work for component scanning, but #Repository better describes the intended use.

Multiple transaction managers annotation configuration

I have two transaction managers configured in annotation-based configuration class:
#Configuration
#EnableTransactionManagement
public class DBConfig implements TransactionManagementConfigurer {
//...
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return defTransactionManager();
}
#Bean
#Qualifier("defSessionFactory")
public LocalSessionFactoryBean defSessionFactory() {
LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
sfb.setDataSource(defDataSource());
Properties props = new Properties();
//...
sfb.setHibernateProperties(props);
sfb.setPackagesToScan("my.package");
return sfb;
}
#Bean
#Qualifier("defTransactionManager")
public PlatformTransactionManager defTransactionManager() {
return new HibernateTransactionManager(defSessionFactory().getObject());
}
#Bean
#Qualifier("secondSessionFactory")
public LocalSessionFactoryBean secondSessionFactory() {
LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
sfb.setDataSource(secondDataSource());
Properties props = new Properties();
//...
sfb.setHibernateProperties(props);
sfb.setPackagesToScan("my.package.subpackage");
return sfb;
}
#Bean
#Qualifier("secondTM")
public PlatformTransactionManager secondTransactionManager() {
return new HibernateTransactionManager(secondSessionFactory().getObject());
}
}
My intention is use annotation transactions with two transaction managers.
Methonds annotated like this
#Transactional
public void method() {}
should be handled by defTransactionManager
And methods annotated like this
#Transactional("secondTM")
public void anotherMethod() {}
by secondTransactionManager
defTransactionManager works fine but when it comes to anotherMethod() I get:
org.hibernate.HibernateException: No Session found for current thread
When I use programmatic transaction management for anotherMethod (autowire secondSessionFactory, use TransactionTemplate) everything works fine.
In case of #EnableTranscationManagement Spring will use by-type lookup, you can provide your own lookup method to a single transaction manager, but it will not work for two tx managers
If you want to check how Spring determines the transaction to execute, you can try to debug the TransactionAspectSupport class. The key methods are setTransactionManagerBeanName and determineTransactionManager.
Just in case anyone runs into this problem, I found a solution:
#Configuration
#EnableTransactionManagement
#DependsOn("myTxManager")
#ImportResource("classpath:applicationContext.xml")
public class AppConfig implements TransactionManagementConfigurer {
#Autowired
private PlatformTransactionManager myTxManager;
...
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return this.myTxManager;
}
In this way, you can use a specific txManager defined in an xml configuration.
In case you want to define the txManager used on service-level, you shall remove the #EnableTransactionManagement annotation from the #Configuration class and specify the txManager in the #Transactional annotations, e.g.
#Service
#Transactional(value="myTxManager", readOnly = true)
public class MyServiceImpl implements MyService { ... }

Resources