4
votes

Environment:

Java/Spring application that uses JPA/Hibernate for persistence and connects to a Teradata datasource configured in the app container (Tomcat) which is accessed through JNDI.

Versions that I am using:

java: 6
spring: 3.2.4.RELEASE
hibernate.core: 4.2.4.Final
hibernate.entitymanager: 4.2.4.Final
hibernate.validator: 5.0.1.Final
springdata: 1.3.4.RELEASE
javax.validation: 1.1.0.Final

Problem:

There are two Teradata databases in the same server that have a same named table but with different columns:

DatDe001.SFITEM
Columns: [iipcst, iidesc, iivend, updated_at, iisku#, created_at, item_expdt, item_effdt]

DEV_DIG_UMT.SFITEM
Columns: [iipcst, iidesc, iivend, row_updt_tms, iisku#, row_insrt_tms, item_expdt, item_effdt]

As you can see the columns that differ are updated_at -> row_updt_tms and created_at -> row_insrt_tms

I am using a JNDI datasource which is configured using this jdbc url:

jdbc:teradata://<server_ip>/DATABASE=DEV_DIG_UMT,DBS_PORT=1025,COP=OFF,CHARSET=UTF8,TMODE=ANSI  

It is supposed that the jdbc connection will resolve the location of the table using the DATABASE value in that jdbc url. However Hibernate seems to be taking the wrong one: DatDe001.SFITEM when performing the initial schema validation, that is at the moment of context initialization when Spring tries to create the EntityManagerFactory bean:

2013-08-15 13:32:03,635 INFO localhost-startStop-1 org.hibernate.tool.hbm2ddl.TableMetadata - HHH000261: Table found: DatDe001.SFITEM
2013-08-15 13:32:03,635 INFO localhost-startStop-1 org.hibernate.tool.hbm2ddl.TableMetadata - HHH000037: Columns: [iipcst, iidesc, iivend, updated_at, iisku#, created_at, item_expdt, item_effdt]

So as my JPA entity (see the entity below in the post) does not have those columns, the hibernate validation throws an exception (see the summarized stack trace):

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in file [C:\APP\springsource\vfabric-tc-server-developer-2.9.2.RELEASE\base-instance\wtpwebapps\profile-items\WEB-INF\classes\META-INF\spring\applicationContext.xml]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: persistenceUnit] Unable to build EntityManagerFactory
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in file [C:\APP\springsource\vfabric-tc-server-developer-2.9.2.RELEASE\base-instance\wtpwebapps\profile-items\WEB-INF\classes\META-INF\spring\applicationContext.xml]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: persistenceUnit] Unable to build EntityManagerFactory
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1482)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
...
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: persistenceUnit] Unable to build EntityManagerFactory
            at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:924)
            at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:899)
...
Caused by: org.hibernate.HibernateException: Missing column: row_updt_tms in DatDe001.SFITEM
at org.hibernate.mapping.Table.validateColumns(Table.java:366)
at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1305)
at org.hibernate.tool.hbm2ddl.SchemaValidator.validate(SchemaValidator.java:155)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:508)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1790)
at org.hibernate.ejb.EntityManagerFactoryImpl.<init>(EntityManagerFactoryImpl.java:96)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:914)

After I saw that, I was wondering if this behavior will persist when executing a query statement to the db through JPA/hibernate, or if it will point to the right table in that case.

Then just for investigation purposes I changed my JPA entity to have the same columns that DatDe001.SFITEM table:

@Entity
public class Sfitem implements Serializable {
    private static final long serialVersionUID = 1L;

    @EmbeddedId
    private SfitemPK id;

    @Column(name="\"iidesc\"")
    private String iidesc;

    @Column(name="\"iipcst\"")
    private BigDecimal iipcst;

    @Column(name="\"iivend\"")
    private BigDecimal iivend;

    @Temporal
    @Column(name="\"item_expdt\"")
    private Date itemExpdt;

    @Temporal
    @Column(name="\"created_at\"")
    private Date createdAt;

    @Temporal
    @Column(name="\"updated_at\"")
    private Date updatedAt;

    ...
}

I started the application and it got loaded successfully. Instead of showing the exception now the log looked good:

...
2013-08-15 14:42:52,056 INFO localhost-startStop-1 org.hibernate.tool.hbm2ddl.TableMetadata - HHH000261: Table found: DatDe001.SFITEM
2013-08-15 14:42:52,056 INFO localhost-startStop-1 org.hibernate.tool.hbm2ddl.TableMetadata - HHH000037: Columns: [iipcst, iidesc, iivend, updated_at, iisku#, created_at, item_expdt, item_effdt]
2013-08-15 14:42:52,061 DEBUG localhost-startStop-1 org.hibernate.internal.SessionFactoryImpl - Checking 0 named HQL queries
2013-08-15 14:42:52,061 DEBUG localhost-startStop-1 org.hibernate.internal.SessionFactoryImpl - Checking 0 named SQL queries
2013-08-15 14:42:52,063 TRACE localhost-startStop-1 org.hibernate.service.internal.AbstractServiceRegistryImpl - Initializing service [role=org.hibernate.service.config.spi.ConfigurationService]
2013-08-15 14:42:52,113 TRACE localhost-startStop-1 org.hibernate.service.internal.AbstractServiceRegistryImpl - Initializing service [role=org.hibernate.stat.spi.StatisticsImplementor]
...

I tried to execute a query to the table and surprisingly found that this time Hibernate was pointing to the right database/schema: DEV_DIG_UMT, the query failed because now the entity had the columns for the other database: DatDe001, see the log:

2013-08-15 14:50:05,731 TRACE tomcat-http--4 org.hibernate.engine.query.spi.QueryPlanCache - Located HQL query plan in cache (SELECT o FROM Sfitem o WHERE o.id.iisku = :iisku AND o.id.itemEffdt <= :date AND coalesce(o.itemExpdt, cast('9999-12-31' as date)) >= :date)
2013-08-15 14:50:05,766 TRACE tomcat-http--4 org.hibernate.engine.query.spi.QueryPlanCache - Located HQL query plan in cache (SELECT o FROM Sfitem o WHERE o.id.iisku = :iisku AND o.id.itemEffdt <= :date AND coalesce(o.itemExpdt, cast('9999-12-31' as date)) >= :date)
2013-08-15 14:50:05,768 TRACE tomcat-http--4 org.hibernate.engine.query.spi.HQLQueryPlan - Find: SELECT o FROM Sfitem o WHERE o.id.iisku = :iisku AND o.id.itemEffdt <= :date AND coalesce(o.itemExpdt, cast('9999-12-31' as date)) >= :date
2013-08-15 14:50:05,772 TRACE tomcat-http--4 org.hibernate.engine.spi.QueryParameters - Named parameters: {iisku=387671, date=2013-08-08}
2013-08-15 14:50:05,810 DEBUG tomcat-http--4 org.hibernate.SQL - select sfitem0_."iisku#" as iisku1_0_, sfitem0_."item_effdt" as item_eff2_0_, sfitem0_."created_at" as created_3_0_, sfitem0_."iidesc" as iidesc4_0_, sfitem0_."iipcst" as iipcst5_0_, sfitem0_."iivend" as iivend6_0_, sfitem0_."item_expdt" as item_exp7_0_ from sfitem sfitem0_ where sfitem0_."iisku#"=? and sfitem0_."item_effdt"<=? and coalesce(sfitem0_."item_expdt", cast('9999-12-31' as DATE))>=?
2013-08-15 14:50:05,832 DEBUG tomcat-http--4 org.hibernate.engine.jdbc.spi.SqlExceptionHelper - could not prepare statement [select sfitem0_."iisku#" as iisku1_0_, sfitem0_."item_effdt" as item_eff2_0_, sfitem0_."created_at" as created_3_0_, sfitem0_."iidesc" as iidesc4_0_, sfitem0_."iipcst" as iipcst5_0_, sfitem0_."iivend" as iivend6_0_, sfitem0_."item_expdt" as item_exp7_0_ from sfitem sfitem0_ where sfitem0_."iisku#"=? and sfitem0_."item_effdt"<=? and coalesce(sfitem0_."item_expdt", cast('9999-12-31' as DATE))>=?]
com.teradata.jdbc.jdbc_4.util.JDBCException: [Teradata Database] [TeraJDBC 14.00.00.21] [Error 3810] [SQLState 42S22] Column/Parameter 'DEV_DIG_UMT.sfitem0_.created_at' does not exist.
    at com.teradata.jdbc.jdbc_4.util.ErrorFactory.makeDatabaseSQLException(ErrorFactory.java:307)
    at com.teradata.jdbc.jdbc_4.statemachine.ReceiveInitSubState.action(ReceiveInitSubState.java:102)
    at com.teradata.jdbc.jdbc_4.statemachine.StatementReceiveState.subStateMachine(StatementReceiveState.java:320)
    at com.teradata.jdbc.jdbc_4.statemachine.StatementReceiveState.action(StatementReceiveState.java:201)
    at com.teradata.jdbc.jdbc_4.statemachine.StatementController.runBody(StatementController.java:121)
    at com.teradata.jdbc.jdbc_4.statemachine.StatementController.run(StatementController.java:112)
...
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:161)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:182)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:159)
    at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1859)
        at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1836)
        at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1816)
        at org.hibernate.loader.Loader.doQuery(Loader.java:900)
        at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:342)
        at org.hibernate.loader.Loader.doList(Loader.java:2526)
        at org.hibernate.loader.Loader.doList(Loader.java:2512)
        at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2342)
        at org.hibernate.loader.Loader.list(Loader.java:2337)
        at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:495)

This means that hibernate validation and the query executor routines are behaving differently

The entity with the correct fields:

@Entity
public class Sfitem implements Serializable {
    private static final long serialVersionUID = 1L;

    @EmbeddedId
    private SfitemPK id;

    @Column(name="\"iidesc\"")
    private String iidesc;

    @Column(name="\"iipcst\"")
    private BigDecimal iipcst;

    @Column(name="\"iivend\"")
    private BigDecimal iivend;

    @Column(name="\"item_expdt\"")
    private Date itemExpdt;

    @Column(name="\"row_insrt_tms\"")
    private Timestamp rowInsrtTms;

    @Column(name="\"row_updt_tms\"")
    private Timestamp rowUpdtTms;

    ...
}

Persistence.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.TeradataDialect"/>
            <!-- value="create" to build a new database on each run; value="update" to modify an existing database; value="create-drop" means the same as "create" but also drops tables when Hibernate closes; value="validate" makes no changes to the database -->
            <property name="hibernate.hbm2ddl.auto" value="validate"/>
            <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/>
            <property name="hibernate.connection.charSet" value="UTF-8"/>
            <!-- Uncomment the following two properties for JBoss only -->
            <!-- property name="hibernate.validator.apply_to_ddl" value="false" /-->
            <!-- property name="hibernate.validator.autoregister_listeners" value="false" /-->
        </properties>
    </persistence-unit>
</persistence>

Datasource and entity manager beans:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName" value="${datasource.jndiName}"/>
  <property name="lookupOnStartup" value="true"/>
  <property name="resourceRef" value="true" />
</bean>

<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
    <property name="persistenceUnitName" value="persistenceUnit"/>
    <property name="dataSource" ref="dataSource"/>
</bean>

Is that a bug or a configuration issue? Has anyone faced this same issue?

I don't want to configure a default schema in the persistence unit nor in the entities, because the approach we are following is to keep the datasource configuration outside the application and in a single place by using the JNDI datasource defined in the container context. That way we don't need to worry when deploying to different environments (Dev, QA, Prod, etc)

1

1 Answers

1
votes

You may need to fully qualify your table name in your SELECT queries that are being submitted to Teradata.

select sfitem0_."iisku#" as iisku1_0_, sfitem0_."item_effdt" as item_eff2_0_,
       sfitem0_."created_at" as created_3_0_, sfitem0_."iidesc" as iidesc4_0_,
       sfitem0_."iipcst" as iipcst5_0_, sfitem0_."iivend" as iivend6_0_,
       sfitem0_."item_expdt" as item_exp7_0_ 
 from DatDe001.SFITEM sfitem0_ /* Notice database name is included here */
where sfitem0_."iisku#"=? 
  and sfitem0_."item_effdt"<=? 
  and coalesce(sfitem0_."item_expdt", cast('9999-12-31' as DATE))>=?

Edit

You could also construct a string that you execute before each SELECT statement that specifies the schema/database you wish to use as the default database to be used to find objects that are not fully qualified in your SQL:

DATABASE=?

Then possibly use a parameter to provide that value like you are the values for your WHERE clause

Edit 2

You can only specify a single DATABASE parameter for a given connection string. If your requirement is to allow for different names for the database supporting the application front end you will need to parameterize the connection string for each database that the application will need to communicate with on the backend.