Search code examples
javaspringdatasourcejndijdbctemplate

Spring Factory method 'jdbcTemplate' threw exception; Property 'dataSource' is required


I'm trying to connect to a database using Spring 4 and the datasource configuration. I'm following a tutorial, so this is the correct configuration:

package spittr.config;

import ...

@Configuration
public class DataConfig {

    @Bean
    public JndiObjectFactoryBean dataSource() {
        JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
        jndiObjectFB.setJndiName("java:jboss/datasources/jdbc/SpitterDS");
        jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
        return jndiObjectFB;
    }

    @Bean
    public JdbcOperations jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

Everything works fine, but as you can see I'm returning a JndiObjectFactoryBean instead of Datasource. If I understood Spring well (which probably I don't, otherwise I would have understood here), if you don't specify the Bean name, Spring will setting the name of the bean as the returned type with the first letter as lowercase. For example, the following lines of code will return a bean which id is "myFantasticBean" (with "m" lowercase)

@Bean
public MyFantasticBean createMyBean() {
    return new MyFantasticBean();
}

I see a lot of people online using this version of DataConfig, where method dataSource() returns an object of type DataSource (as it should be):

package spittr.config;

import ...

@Configuration
public class DataConfig {

    @Bean
    public DataSource dataSource() {
        JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
        jndiObjectFB.setJndiName("java:jboss/datasources/jdbc/SpitterDS");
        jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFB.getObject();
    }

    @Bean
    public JdbcOperations jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

But if I use this DataSource creation, I get the following error:

bin/content/10_SpringWeb_BE_JDBC.war/WEB-INF/classes/spittr/data/JdbcSpitterRepository.class"]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jdbcTemplate' defined in class path resource [spittr/config/DataConfig.class]: Bean instantia
tion via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.jdbc.core.JdbcOperations]: Factory method 'jdbcTemplate' threw exception; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required

I've even found the solution, modyfing the dataSource() method that now becomes like this:

package spittr.config;

import ...

@Configuration
public class DataConfig {

    @Bean
    public DataSource dataSource(){
        final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
        DataSource dataSource = dsLookup.getDataSource("java:jboss/datasources/jdbc/SpitterDS");
        return dataSource;
    }

    @Bean
    public JdbcOperations jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

But I don't really get why this works and the previous doesn't. Can someone explain to me what I'm doing wrong?

Thank you a lot


Solution

  • Spring wires all beans by type. This means it will look for a bean definition with return type DataSource, when using java configuration. This is what you have when using the JndiDataSourceLookup so Spring finds it without issue. It cannot derive a DataSource from JndiObjectFactoryBean unless you cast it on returning it.

    @Bean
    public DataSource dataSource() {
       JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();     
       jndiObjectFB.setJndiName("java:jboss/datasources/jdbc/SpitterDS");
       jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
       return (DataSource) jndiObjectFB.getObject();
    }
    

    Also, you are a bit mistaken when you state

    If I understood Spring well (which probably I don't, otherwise I would have understood here), if you don't specify the Bean name, Spring will setting the name of the bean as the returned type with the first letter as lowercase. For example, the following lines of code will return a bean which id is "myFantasticBean" (with "m" lowercase)

    This is only true when using component scanning.

    So if you have a class annotated with e.g. @Service as such:

    @Service
    public class MyFantasticBean { .. }
    

    Then yes, the name will be myFantasticBean because the name cannot be derived from something else. It's important to realize the name will not be the same as the type when using Java configuration.

    @Bean
    public MyFantasticBean createMyBean() {
        return new MyFantasticBean();
    }
    

    In your example, MyFantasticBean is the bean type that spring will search for when you want to inject it. And createMyBean will be the name of the bean. So if you would have multiple instances of beans with type MyFantasticBean, you could then use the bean name to specify which to inject. In your case that would be

    @Qualifier("createMyBean")
    @Autowired
    private MyFantasticBean myFantasticBean;