Hibernate的map映射关系查询&原生HQL转SQL
序:
先说说遇到的境况,目前情况是2张表,主表跟从表通过主键关联,从表是键值对类型数据,即:一列记录key,一列记录value。使用原生HQL通过从表数据进行数据过滤,查询总是无结果。
于是本来是想先转为原生sql检查下,看看为啥查询没有结果,转为原sql后原因找到了,发现是sql问题,怀疑是hibernat或封装的Finder有问题(由于时间紧没有深究,另外基本上只有查询都是基于这不敢擅动。关于hibernate的查询基于Finder大家可以自行了解)。然后顺着这个思路是想就用原生sql查询,然后再转为实体对象,这是又遇到一个坑就是查询的字段跟实体对应不上,实际上就是hbm.xml实体映射文件的字段名跟表字段名不一致,到此已经纠结快一天了,之前在群里求助也是石沉大海,我于是也就回头是岸。其实如果实体跟字段名对应,这条路也是能走通的。写下此文也是希望以后有人遇到这样的问题不至于稳步到人活找不到一点思路。
1、首先看看表关系
主表
从表
2、再看看实体类
椭圆标示的是属性指定了实体类的,一会在下面的xml映射关系可以看到
3、再看看hbm.xml映射关系
可以看到其他关系都指定了实体类,就是这个attr没有指定,attr是map集合关系,对于它的查询过滤也是很奇怪的处理,百度到的资料也不多,不过还是找到了。
4、先补充主表map属性HQL的查询过滤方法
private void appendQuery(Finder f, Map<String, Object> parmMp) {
if(!CollectionUtils.isEmpty(parmMp)) {
//循环map性能居中 ,小数据量最佳
for (String key :parmMp.keySet()){
Object parmObj = parmMp.get(key);
switch (key) {
case "queryNO":
f.append(" and bean.author=:queryNO").setParam("queryNO", parmObj);
break;
case "queryJfGx":
Object jfObj = parmMp.get("queryJf");
String queryZd = parmMp.get("queryZd").toString();
f.append(" and bean.").append(queryZd).append("=:queryZd").setParam("queryZd", queryZd);
switch (parmObj.toString()) {
case "gt":
f.append(" and bean.attr[attr_value] >=:queryJf");
break;
case "lt":
f.append(" and bean.attr[attr_value] <=:queryJf");
break;
case "eq":
f.append(" and bean.attr[attr_value] =:queryJf");
break;
}
f.setParam("queryJf", jfObj);
break;
default:
break;
}
}
}
}
这是我封装的map属性扩展查询,实际上只需要3个字段,我太过map传参,以后只用维护action和dao就ok了,parmMp现在的参数下面解释下:
queryZd:查询字段,也就是从表的key值
queryJfGx:查询关系,这里有位要做积分的大于等于小于区间判断,所以需要关系
queryJf:积分值
map映射关系的查询关键点:
bean.attr[attr_value]
bean是主表实体别名,attr是主表属性名,[]包含的是存在从表里的attr_value的值。
5、下面补充原生HQL转SQL
// 获取当前session
Session session = this.getSession();
// 得到session工厂实现类
SessionFactoryImpl sfi = (SessionFactoryImpl)session.getSessionFactory();
QueryTranslatorImpl queryTranslator = new QueryTranslatorImpl(f.getOrigHql(), f.getOrigHql(), Collections.EMPTY_MAP, sfi);
queryTranslator.compile(Collections.EMPTY_MAP, false);
// 得到sql
String sql = queryTranslator.getSQLString();
System.out.println("sql:"+sql);
System.out.println("hql:"+f.getOrigHql());
//获取参数名list(这里是在封装的Finder在原生设置参数值setParam会放入参数名到list的)
List<String> parmLs = f.getParams();
//获取参数值list(这里是在封装的Finder在原生设置参数值setParam会放入参数值到list的)
List<Object> valLs = f.getValues();
// 赋参数值 (这个方法有一定的bug,如果一个参数值被用多次,而在setParam值设置一次,可能会有问题)
if (valLs != null && valLs.size() > 0) {
for (int i = 0; i < valLs.size(); i++) {
sql = sql.replaceFirst("\\?", "\\'" + valLs.get(i).toString() + "\\'");
}
}
//执行sql
SQLQuery query = getSession().createSQLQuery(sql);
//转换为实体,下面这行如果表字段与实体字段在映射关系配置的一致应该是可以的,现在因为不一致不能设置这一行
//query.addEntity(Content.class);
//这一行与上面的类似,效果是一样的,如果处理下得到的sql,都增加as取别名与实体一致应该也是可以的
hibernate实体类用addEntity
不受Hibernate管理的实体类用setResultTransformer
query.setResultTransformer(Transformers.aliasToBean(Content.class));
List list = query.list();
//下面是封装的页面信息实体,带页码、每页展示数量、页面数据list
Pagination p = new Pagination(pageNo, pageSize, list.size());
query.setFirstResult(p.getFirstResult());
query.setMaxResults(p.getPageSize());
if (f.isCacheable()) {
query.setCacheable(true);
}
List pageLs = query.list();
p.setList(pageLs);
//下面是用上面的方法发现不能直接转实体后,考虑使用复制对象对查询结果进行拷贝的,发现因为名字对不上所以也是不能成功直接报错,其实如果自己手动写set,一个个取查询结果集里的字段在设置值到对象,应该也是能行的,可是这个工作量太大,以后有字段增减也是很麻烦,所以直接我就不想试了,回头看看群里的求助信息,半天过去了,石沉大海,我也就回头是岸了。真的是没有谁能对你境况感同身受啊,一切靠自己,但是我经历了,我写下此文希望能为后来想我这样的人提供个思路,技术重在交流,没有交流就没有进步,我为技术而生。。。。。。
private void setPaginationList(Pagination p, List pageLs) {
for(int i = 0;i < pageLs.size();i++) {
Content ct = new Content();
try {
BeanUtils.copyProperties(ct,pageLs.get(i));
System.out.println(ct.getTitle());
System.out.println("----------");
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}