在项目中有这个一个需求,查询一个表,显示为datagrid,但是需要显示的一列是个关联表中的信息,是一对多的,但是需要把这个‘多’以逗号分隔显示在这一列上。

举例:

A1表                      A2表

id  name                 id  A1_id  name

1   name1              1        1     A2_name1

2   name2              2        1    A2_name2  

                 3        2    A2_name3

查询后得到的显示在页面上的datagrid 应该为:

id      name      A2_name

1      name1     A2_name1, A2_name2  

2      name2      A2_name3

如果用hibernate你会怎么做呢? 写sql语句,一个sql能直接写出这样的结果的sql吗? 我想了一下,很难,因为显示结果需要改变 A2表的维度,需要将关联的多条记录转化为 一条记录上的一列的值。有人说实话存储过程吧, 如果用存储过程了,那hibernate就失去了使用它的意义,在数据库移植上就会有问题。我们的原则是指做基本的sql处理,其他的都是业务逻辑代替。有人说,用hibernate查询A1表,然后得到结果的List,然后遍历这个List,拿到A1的id,关联查询A2表,或者A2的结果集,然后遍历A2,将A2的那么拼成字符串,然后构建新的Map对象,将A1的结果集的数据全都加入,然后将构建好的A2关联信息的以逗号分隔的字符串,加入到Map的一个列中去。 嗯,这样可以实现我们最终要的结果,但是,hibernate获得的结果集List,hibernate本身就需要循环jdbc的Resultset,然后将数据填充到实体对象或者Map对象当中去,然后组织成一个List对象。 然后你又需要再次遍历这个List对象,讲所有的数据取出来,关联查询,再重新组织成一个新的Map对象。 数据量小,列比较少,还可以将就,但是数据量大,列大,又该如何? 研究了一上午hibernate的方法,好像就没有这么干的。不知道hibernate是否本来就可以支持。 如果hibernate本身就有办法支持的话,请知道的朋友告知。 最后没招,就研究了hibernate的Transformers 并读了AliasedTupleSubsetResultTransformer的源代码,于是有了点子。我重写了AliasedTupleSubsetResultTransformer,代码如下:

  1. public class XKAliasToBeanResultTransformer extends AliasedTupleSubsetResultTransformer  
  2. {  
  3.     /**  
  4.      *   
  5.      */  
  6.     private static final long serialVersionUID = 5967847763983844234L;  
  7.  
  8.     private final Class<?> resultClass;  
  9.     private boolean isInitialized;  
  10.     private String[] aliases;  
  11.     private Setter[] setters;  
  12.  
  13.     public XKAliasToBeanResultTransformer(Class<?> resultClass)  
  14.     {  
  15.         if (resultClass == null)  
  16.         {  
  17.             throw new IllegalArgumentException("resultClass cannot be null");  
  18.         }  
  19.         isInitialized = false;  
  20.         this.resultClass = resultClass;  
  21.     }  
  22.  
  23.     /**  
  24.      * {@inheritDoc}  
  25.      */  
  26.     public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength)  
  27.     {  
  28.         return false;  
  29.     }  
  30.  
  31.     public Object transformTuple(Object[] tuple, String[] aliases)  
  32.     {  
  33.         Object result;  
  34.  
  35.         if (!isInitialized)  
  36.         {  
  37.             initialize(aliases);  
  38.         }  
  39.         else  
  40.         {  
  41.             check(aliases);  
  42.         }  
  43.  
  44.         // 关键代码  
  45.         result = Initialization.getInstance().getApplicationContext().getBean(resultClass);  
  46.  
  47.         for (int i = 0; i < aliases.length; i++)  
  48.         {  
  49.             if (setters[i] != null)  
  50.             {  
  51.                 setters[i].set(result, tuple[i], null);  
  52.             }  
  53.         }  
  54.  
  55.         return result;  
  56.     }  
  57.  
  58.     private void initialize(String[] aliases)  
  59.     {  
  60.         PropertyAccessor propertyAccessor = new ChainedPropertyAccessor(new PropertyAccessor[] { PropertyAccessorFactory.getPropertyAccessor(resultClass, null),  
  61.                 PropertyAccessorFactory.getPropertyAccessor("field") });  
  62.         this.aliases = new String[aliases.length];  
  63.         setters = new Setter[aliases.length];  
  64.         for (int i = 0; i < aliases.length; i++)  
  65.         {  
  66.             String alias = aliases[i];  
  67.             if (alias != null)  
  68.             {  
  69.                 this.aliases[i] = alias;  
  70.                 setters[i] = propertyAccessor.getSetter(resultClass, alias);  
  71.             }  
  72.         }  
  73.         isInitialized = true;  
  74.     }  
  75.  
  76.     private void check(String[] aliases)  
  77.     {  
  78.         if (!Arrays.equals(aliases, this.aliases))  
  79.         {  
  80.             throw new IllegalStateException("aliases are different from what is cached; aliases=" + Arrays.asList(aliases) + " cached=" + Arrays.asList(this.aliases));  
  81.         }  
  82.     }  
  83.  
  84.     public boolean equals(Object o)  
  85.     {  
  86.         if (this == o)  
  87.         {  
  88.             return true;  
  89.         }  
  90.         if (o == null || getClass() != o.getClass())  
  91.         {  
  92.             return false;  
  93.         }  
  94.  
  95.         XKAliasToBeanResultTransformer that = (XKAliasToBeanResultTransformer) o;  
  96.  
  97.         if (!resultClass.equals(that.resultClass))  
  98.         {  
  99.             return false;  
  100.         }  
  101.         if (!Arrays.equals(aliases, that.aliases))  
  102.         {  
  103.             return false;  
  104.         }  
  105.  
  106.         return true;  
  107.     }  
  108.  
  109.     public int hashCode()  
  110.     {  
  111.         int result = resultClass.hashCode();  
  112.         result = 31 * result + (aliases != null ? Arrays.hashCode(aliases) : 0);  
  113.         return result;  
  114.     }  

上面“关键代码”的地方,原来的代码,只是resultClass.newInstance(); 我的修改其实很简单,就是使用spring的Context获取的bean而已,这样bean就可以被增强了,可以注入,可以支持事务了。于是,我们就可以在hibernate像实体对象填充数据的时机做任何的事情,包括我上面的需求,一旦hibernate像实体对象填充数据,就要调用set方法,在调用setId这个方法的时候,我们就能获得每条记录的id,然后可以查询关联表,组织处以逗号分隔的关联表多条记录的名称,然后填充到实体对象的某一个属性上。这样,查询完毕,我们获得的List中的实体对象,就已经包含了我们要的多表合并出的那列的值,我们只用json一下,返回前台,轻松的就可以在datagrid上显示出我们要的结果。

这样做的坏处,目前看只有一条,就是无法应对hibernate的升级,如果升级变动大,就需要修改调整代码了。

如果有朋友有更好的办法,可以提出交流,我的这种办法,有点简单粗暴,但是可以应对一切不可能完成的任务。 其实说白了,相当于在hibernate给实体对象填充数据的过程中拦截。