Hibernate管理的物件通常都有hbm配置檔案或註解。有一些專案需要定義一些DTO物件,比如為了組裝報表資料。對於這類不受hibernate管理的物件。在hibernate使用native sql的時候,可以自定義一些transformer來實現查詢結果集到List<T>的自動對映,而不必再人工的去做這個轉換。
下面是在泛型DAO裡面定義的一個泛型方法。注意,這個方法的泛型是與泛型DAO類定義的泛型不同。
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
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的連線屬性.
比如
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();
}
}