enzhou.liu
enzhou.liu

Reputation: 183

Use Spring to init SqlFactory, but got exception when cast data source to its underlying class

I have a Java tomcat project, and in my integration test, we deploy the embedded tomcat to run test against it.

I use spring to initiate a data connection

<bean id="mainDataSource" class="org.springframework.jdbc.datasource.SingleConnectionDataSource">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="${database.url}"/>
        <property name="username" value="dbUserName"/>
        <property name="password" ref="dbPassword"/>
        <property name="autoCommit" value="false"/>
        <property name="suppressClose" value="true"/>
    </bean>

<bean id="dbPassword" class="java.lang.String" factory-bean="passwordFactory" factory-method="unwrapPassword">
        <constructor-arg value="#{systemProperties['database.password']!=null ? systemProperties['database.password'] : '${database.password}'}" />
</bean>

<bean id="dbUserName" class="java.lang.String">
        <constructor-arg value="#{systemProperties['database.username']!=null ? systemProperties['database.username'] : '${database.username}'}" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="mainDataSource" />
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
    <property name="mapperLocations"
                  value="classpath*:mycompany/dao/mappers/**/*.xml" />
</bean>

And I make this configuration be able to switch the username/password using system property. Now I am trying to test the username/password get used, we have following code

    String username = ((SingleConnectionDataSource)sqlSessionFactory.getConfiguration().getEnvironment().getDataSource()).getUsername();
    String password = ((SingleConnectionDataSource)sqlSessionFactory.getConfiguration().getEnvironment().getDataSource()).getPassword();
    LOGGER.info("USERNAME/PASSWORD: " + username/password);

But I got this weird exception:

Constructor threw exception; nested exception is java.lang.ClassCastException: 
org.springframework.jdbc.datasource.SingleConnectionDataSource cannot be cast to org.springframework.jdbc.datasource.SingleConnectionDataSource

And if I put this

LOGGER.info(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource().getClass().toString());
LOGGER.info(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource() instanceof org.springframework.jdbc.datasource.SingleConnectionDataSource? "True": "False");

I got following result

"class org.springframework.jdbc.datasource.SingleConnectionDataSource"
"False"

Wondering what is wrong, why the cast fail, it seems the class/type is correct.

Upvotes: 0

Views: 75

Answers (1)

Jim Garrison
Jim Garrison

Reputation: 86774

This is the dreaded "class A cannot be cast to class A" message.

The cause of this is that you have two copies of the Spring Framework libraries available in Tomcat, one likely in Tomcat's classpath and one in your application's classpath.

Even though the two libraries are identical, you have instances being loaded by different classloaders, and the JVM considers both the class identity AND the classloader that loaded the class when determining compatibility. In your case, Tomcat is returning a SingleConnectionDataSource instance created through one classloader and you are trying to cast to a class created through a different classloader.

This is hard to debug, but I would start by making sure you have only one copy of Spring deployed.

Upvotes: 1

Related Questions