Hibernate管理的对象通常都有hbm配置文件或注解。有一些项目需要定义一些DTO对象,比如为了组装报表数据。对于这类不受hibernate管理的对象。在hibernate使用native sql的时候,可以自定义一些transformer来实现查询结果集到List<T>的自动映射,而不必再人工的去做这个转换。
下面是在泛型DAO里面定义的一个泛型方法。注意,这个方法的泛型是与泛型DAO类定义的泛型不同。

@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

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(); } }