在项目中有这个一个需求,查询一个表,显示为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,代码如下:
- public class XKAliasToBeanResultTransformer extends AliasedTupleSubsetResultTransformer
- {
- /**
- *
- */
- private static final long serialVersionUID = 5967847763983844234L;
- private final Class<?> resultClass;
- private boolean isInitialized;
- private String[] aliases;
- private Setter[] setters;
- public XKAliasToBeanResultTransformer(Class<?> resultClass)
- {
- if (resultClass == null)
- {
- throw new IllegalArgumentException("resultClass cannot be null");
- }
- isInitialized = false;
- this.resultClass = resultClass;
- }
- /**
- * {@inheritDoc}
- */
- public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength)
- {
- return false;
- }
- public Object transformTuple(Object[] tuple, String[] aliases)
- {
- Object result;
- if (!isInitialized)
- {
- initialize(aliases);
- }
- else
- {
- check(aliases);
- }
- // 关键代码
- result = Initialization.getInstance().getApplicationContext().getBean(resultClass);
- for (int i = 0; i < aliases.length; i++)
- {
- if (setters[i] != null)
- {
- setters[i].set(result, tuple[i], null);
- }
- }
- return result;
- }
- private void initialize(String[] aliases)
- {
- PropertyAccessor propertyAccessor = new ChainedPropertyAccessor(new PropertyAccessor[] { PropertyAccessorFactory.getPropertyAccessor(resultClass, null),
- PropertyAccessorFactory.getPropertyAccessor("field") });
- this.aliases = new String[aliases.length];
- setters = new Setter[aliases.length];
- for (int i = 0; i < aliases.length; i++)
- {
- String alias = aliases[i];
- if (alias != null)
- {
- this.aliases[i] = alias;
- setters[i] = propertyAccessor.getSetter(resultClass, alias);
- }
- }
- isInitialized = true;
- }
- private void check(String[] aliases)
- {
- if (!Arrays.equals(aliases, this.aliases))
- {
- throw new IllegalStateException("aliases are different from what is cached; aliases=" + Arrays.asList(aliases) + " cached=" + Arrays.asList(this.aliases));
- }
- }
- public boolean equals(Object o)
- {
- if (this == o)
- {
- return true;
- }
- if (o == null || getClass() != o.getClass())
- {
- return false;
- }
- XKAliasToBeanResultTransformer that = (XKAliasToBeanResultTransformer) o;
- if (!resultClass.equals(that.resultClass))
- {
- return false;
- }
- if (!Arrays.equals(aliases, that.aliases))
- {
- return false;
- }
- return true;
- }
- public int hashCode()
- {
- int result = resultClass.hashCode();
- result = 31 * result + (aliases != null ? Arrays.hashCode(aliases) : 0);
- return result;
- }
- }
上面“关键代码”的地方,原来的代码,只是resultClass.newInstance(); 我的修改其实很简单,就是使用spring的Context获取的bean而已,这样bean就可以被增强了,可以注入,可以支持事务了。于是,我们就可以在hibernate像实体对象填充数据的时机做任何的事情,包括我上面的需求,一旦hibernate像实体对象填充数据,就要调用set方法,在调用setId这个方法的时候,我们就能获得每条记录的id,然后可以查询关联表,组织处以逗号分隔的关联表多条记录的名称,然后填充到实体对象的某一个属性上。这样,查询完毕,我们获得的List中的实体对象,就已经包含了我们要的多表合并出的那列的值,我们只用json一下,返回前台,轻松的就可以在datagrid上显示出我们要的结果。
这样做的坏处,目前看只有一条,就是无法应对hibernate的升级,如果升级变动大,就需要修改调整代码了。
如果有朋友有更好的办法,可以提出交流,我的这种办法,有点简单粗暴,但是可以应对一切不可能完成的任务。 其实说白了,相当于在hibernate给实体对象填充数据的过程中拦截。
转载于:https://blog.51cto.com/zhangtao911/1170110