Spring Cloud Config Server + Wildfly JNDI (MySql) + Spring Data Jpa with Hibernate with DataSource refresh

From the given title, I got a working sample without the Spring Data Part by using a plain DataSource and JDBC template with the refresh of datasource on hitting curl to refresh the Datasource bean as well. But could not get it working with Spring Data JPA after spending countless hours of searching and browsing different blog posts like spring-persistence-jpa-jndi-datasource, spring-boot-jndi-datasource

I'll explain the complete steps and will also link the git project of working JDBC template example and JPA project below as well. All the components are set up locally for demonstration.


Adding JNDI MySql Datasource to Wildfly (10.1.10 final)

Thanks to following video - 3 ways how to add a datasource to Wildfly 9, I added two MySql Datasources pointing to two different databases on my local mysql server by deploying the mysql connector jar and adding datasource using Wildfly console and testing both from console was successful.

Config Server and Client

Both of these are configured properly and tested as well. Config clients jdbcConfigClient and jpaConfigClient read the JNDI lookup property from remote config server. MysqlConnector jar, ConfigClient and ConfigServer are deployed on local wildfly using Wildfly Console (Only one client deployed for testing).

JdbcClient

Here is a snippet of the Datasource configuration along with the main applicaton and sample controller to test datasource refresh -

    @SpringBootApplication
    @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
    @RefreshScope
    public class ConfigclientApplication extends SpringBootServletInitializer      {

    public static void main(String[] args) {
        SpringApplication.run(ConfigclientApplication.class, args);
    }
}

@RefreshScope
@RestController
class MessageRestController {

@Autowired
SampleDao dao;

@Value("${message:Hello default}")
private String message;

@RequestMapping("/message")
public String getMessage() {
    return this.message;
}

@RequestMapping("/person")
public List<SampleEntity> getAll() {
    return dao.findAll();
}

}

@RefreshScope
@Configuration
class DataSourceConfig {


@Value("${jndi.datasource.name}")
private String jndiName;

//@RequestMapping("/jndi")
public String getJndiName() {
    return this.jndiName;
}

@Bean
@RefreshScope
public DataSource getDataSource() throws NamingException {
    JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup();
    System.out.println("Datasource jndi is "+getJndiName());
    //return (DataSource) new JndiTemplate().lookup(getJndiName());
    return jndiDataSourceLookup.getDataSource(getJndiName());
}

@Bean
@RefreshScope
public JdbcTemplate getJbdcTemplate() throws NamingException {
    return new JdbcTemplate(getDataSource());
}

For testing, I hit a Get request on /person to get list of persons and was able to successfully get the response with the refresh of datasource as well using the curl request to config client as suggested in Getting Started Centralized Config. For the full project check jdbcConfigClient

JPA Config Client

Here is when things got problematic. I tried a lot of different things but even with hardcoded JNDI parameter, the application failed to startup due to different causes such as no URL parameter found, no EntityManager bean available or Hikari Config is being loaded and fails even though I'm not using Hikari Datasource in Java Datasource config which I assume Spring boot tries to load by default. I tried excluding hikari cp from spring data starter, tried excluding Datasource autoconfiguration using exlcude property of @EnableAutoConfiguration in the main application still no use. I am using the Jpa Datasource config in normal projects without JNDI and config server. Below is a snippet of JPA Datasource configuration with the main application:

    SpringBootApplication
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
@EnableAutoConfiguration
@RefreshScope
public class ConfigclientApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(ConfigclientApplication.class, args);
    }
}

@RefreshScope
@RestController
class MessageRestController {

    @Autowired
    SampleDao dao;

    @Value("${message:Hello default}")
    private String message;

    @RequestMapping("/message")
    public String getMessage() {
        return this.message;
    }

    @RequestMapping("/person")
    public List<SampleEntity> getAll() {
        return dao.findAll();
    }

}

@RefreshScope
@Configuration
@EnableJpaRepositories("com.example.demo.dao")
@EnableTransactionManagement
/*@EntityScan("com.example.demo.entity")
@ComponentScan("com.example.demo")*/
class DataSourceConfig {

    @Value("${jndi.datasource.name}")
    private String jndiName;

    public String getJndiName() {
        return this.jndiName;
    }

    @Bean
    public DataSource getDataSource() throws NamingException {
        //JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup();
        //System.out.println("Datasource jndi is "+getJndiName());
        //return (DataSource) new JndiTemplate().lookup(getJndiName());
        //return jndiDataSourceLookup.getDataSource("java:/MySqlDS");
        JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
        bean.setJndiName(getJndiName());
        bean.setProxyInterface(DataSource.class);
        bean.setLookupOnStartup(false);
        bean.afterPropertiesSet();
        return (DataSource)bean.getObject();
    }

    @Bean
    public EntityManagerFactory entityManagerFactory() throws NamingException {

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setDataSource(getDataSource());
        factory.setPackagesToScan("com.example.demo.entity");

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.MYSQL);
        vendorAdapter.setShowSql(true);
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.afterPropertiesSet();

        //HashMap<String, Object> properties = new HashMap<>();
        //properties.put("hibernate.generate-ddl", Boolean.TRUE.toString());
        //properties.put("hibernate.hbm2ddl.auto", "update");
        //properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        //entityManagerContainer.setJpaPropertyMap(properties);

        return factory.getObject();

    }

    @Bean
    public PlatformTransactionManager transactionManager() throws NamingException {

        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory());
        return transactionManager;
    }
}

The JPA project can be found here - JpaConfigClient If I remove the whole datasource configuration altogether and just give the following property in the remote properties file -

spring.datasource.jndi-name=java:/MySqlDs

Using the autoconfiguration, the JPA configuration completes successfully and I can get the data using postman request but that is not my requirement and I cannot refresh datasource since it's autoconfigured by boot. Also the remote properties file named a-bootiful-client.properties file contains just the following properties:

#spring.datasource.jndi-name=java:/MySqlDs
jndi.datasource.name=java.name=java:comp/env/MySqlDs
message=Hi test

While application starts up, it correctly lookups the wildfly datasource (I have tried naming the datasource as java:comp/env/DatasourceName as well using the default java:/DatasourceName as well but it didn't make a difference and I got the same issues as reported above. Feel free to fork the above project.