Native Query的自定義轉換器

weixin_34344677發表於2011-03-03

Hibernate管理的物件通常都有hbm配置檔案或註解。有一些專案需要定義一些DTO物件,比如為了組裝報表資料。對於這類不受hibernate管理的物件。在hibernate使用native sql的時候,可以自定義一些transformer來實現查詢結果集到List<T>的自動對映,而不必再人工的去做這個轉換。

下面是在泛型DAO裡面定義的一個泛型方法。注意,這個方法的泛型是與泛型DAO類定義的泛型不同。

ContractedBlock.gifExpandedBlockStart.gifView Code
@SuppressWarnings("unchecked")
protected <T1> List<T1> getResultByNativeQuery(final String sql,
final Class<T1> targetClass, final Object[] args,final int...otherArgs) {
List
<T1> list = (List<T1>) getHibernateTemplate().executeFind(
new HibernateCallback() {
@Override
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
Query query
= session
.createSQLQuery(sql)
.setResultTransformer(
new WithoutHibernateMappingTransformer<T1>(
targetClass));
if (args != null) {
for (int i = 0; i < args.length; i++) {
query.setParameter(i, args[i]);
}
}
int position=args.length;
for (int i = 0; i < otherArgs.length; i++) {
query.setParameter(position
+i, otherArgs[i]);
}
return query.list();
}
});
return list;
}

對應的Transfer需要實現自Hibernate提供的介面:ResultTransformer

ContractedBlock.gifExpandedBlockStart.gifView Code
import java.math.BigDecimal;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.commons.lang.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.property.ChainedPropertyAccessor;
import org.hibernate.property.PropertyAccessor;
import org.hibernate.property.PropertyAccessorFactory;
import org.hibernate.property.Setter;
import org.hibernate.transform.ResultTransformer;

public class WithoutHibernateMappingTransformer<TT> implements
ResultTransformer {

private static final long serialVersionUID = -5199190581393587893L;
private final Class<TT> resultClass;
private Setter[] setters;
private PropertyAccessor propertyAccessor;

public WithoutHibernateMappingTransformer(Class<TT> resultClass) {
if (resultClass == null)
throw new IllegalArgumentException("resultClass cannot be null");
this.resultClass = resultClass;
propertyAccessor
= new ChainedPropertyAccessor(new PropertyAccessor[] {
PropertyAccessorFactory.getPropertyAccessor(resultClass,
null),
PropertyAccessorFactory.getPropertyAccessor(
"field") });
}

public Object transformTuple(Object[] tuple, String[] aliases) {
Object result;
try {
if (setters == null) {
setters
= new Setter[aliases.length];
for (int i = 0; i < aliases.length; i++) {
String alias
= convertColumnToProperty(aliases[i]);
if (alias != null) {
try {
setters[i]
= propertyAccessor.getSetter(
resultClass, alias);
}
catch (PropertyNotFoundException e) {
continue;
}
}
}
}
result
= resultClass.newInstance();
for (int i = 0; i < aliases.length; i++) {
if (setters[i] != null) {
if (tuple[i].getClass().equals(BigDecimal.class)) {
setters[i].set(result, ((BigDecimal) tuple[i])
.longValue(),
null);
}
else{
setters[i].set(result, tuple[i],
null);
}
}
}
}
catch (InstantiationException e) {
throw new HibernateException("Could not instantiate resultclass: "
+ resultClass.getName());
}
catch (IllegalAccessException e) {
throw new HibernateException("Could not instantiate resultclass: "
+ resultClass.getName());
}
return result;
}

/**
* Converts the specified 'XXX_YYY_ZZZ'-like column name to its
* 'xxxYyyZzz'-like Java property name.
*
*
@param columnName
* the column name
*
@return the Java property name
*/
public String convertColumnToProperty(String columnName) {
columnName
= columnName.toLowerCase();
StringBuffer buff
= new StringBuffer(columnName.length());
StringTokenizer st
= new StringTokenizer(columnName, "_");
while (st.hasMoreTokens()) {
buff.append(StringUtils.capitalize(st.nextToken()));
}
buff.setCharAt(
0, Character.toLowerCase(buff.charAt(0)));
return buff.toString();
}


@SuppressWarnings(
"unchecked")
public List transformList(List collection) {
return (List<TT>)collection;
}

public static void main(String args[]) {
WithoutHibernateMappingTransformer
<String> transfer = new WithoutHibernateMappingTransformer<String>(
String.
class);
System.out.println(transfer.convertColumnToProperty(
"CPCID"));
System.out.println(transfer.convertColumnToProperty(
"XXX_YYY_ZZZ"));
}
}

另外,對於Hibernate管理的類,可以使用Query query = session.createSQLQuery(sql).addEntity(entityClass)來實現對映。不過,這種方式就需要hbm檔案裡有相應的property對映Oracle的欄位。

注意:

1。Oracle的欄位別名都是大寫,所以不能使用Hibernate提供的現成方式,這樣會導致通過大寫的別名去java類裡找相應的欄位,這會找不到的:

sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS") .setResultTransformer(Transformers.aliasToBean(CatDTO.class))

2. 轉換器中針對Oracle返回型別與應用之間的資料型別顯示進行了轉換。比如Oracle返回的BigDecimal與Long之間的轉換。

3.約定是Oracle中的下劃線來分隔單詞。具體參測試。

4.即使對於Hibernate的受管物件,也是可以使用轉換器的。前提實體裡定義了相應欄位的set方法。

5. 注意到jdk5以後,java的返回型別值是可以被複蓋的。比如 Object可以使用T這樣的泛型變數替代。

6.在使用轉換器的過程中,發現資料庫欄位如果是date類似,那麼oracle在某些jdbc驅動下會將date轉換為java中的java.sql.Date. 由於java.sql.Date會trunc掉時分秒,只保留年月日,這樣導致應用程式中對應的屬性只有年月日,而資料庫中的欄位即包含年月日+時分秒. 解決的辦法有二種:1.將資料庫的欄位由date改為timestamp. oracle的所有驅動都會將timestamp轉換成java中的java.sql.Timestamp型別,而不存在trunc時分秒. 缺點是這樣應用程式中改動起來比較麻煩.

2.我推薦的方式: 資料庫仍然使用date,但是針對datesource新增oracle.jdbc.V8Compatible=true的連線屬性.

比如

<bean id="parentDataSource"
class
="org.springframework.jdbc.datasource.DriverManagerDataSource"
abstract
="true">
<property name="driverClassName"
value
="oracle.jdbc.driver.OracleDriver" />
<!--注意如何注入key-value的properties屬性-->
<property name="connectionProperties">
<props>
<prop key="oracle.jdbc.V8Compatible">true</prop>
</props>
</property>
</bean>

或者在jdbc的應用中:

public static void main(String[] args) {     
    try {     
           Class.forName("oracle.jdbc.OracleDriver");     
           Properties prop=new Properties();     
           prop.setProperty("user","system");     
           prop.setProperty("password","dba");     
           prop.setProperty("oracle.jdbc.V8Compatible","true");     
           java.sql.Connection connection1 = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:orcl", prop);     
           System.out.println(connection1);     
           System.out.println(connection1.getMetaData().getDriverName()+" "+connection1.getMetaData().getDriverVersion());     
           ResultSet rs = connection1.createStatement().executeQuery("select date1,date2 from t_test");     
    
        while (rs.next()) {     
            String value1 = rs.getString("DATE1");     
            System.out.println("DATE1=" + value1);     
            String value2 = rs.getString("DATE2");     
            System.out.println("DATE2=" + value2);     
        }     
       }     
       catch (Exception exception1) {     
           exception1.printStackTrace();     
       }     
    
} 

相關文章