Hibernate SQLQuery执行原生SQL


一、简介
Hibernate对原生SQL查询的支持和控制是通过SQLQuery接口实现的。通过Session接口,我们能够很方便的创建一个SQLQuery(SQLQuery是一个接口,在Hibernate4.2.2之前,默认返回的是SQLQuery的实现类——SQLQueryImpl对象,在下文中出现的SQLQuery如非注明,都是指该子类)对象来进行原生SQL查询:

1、查询的那个对象的简单查询事例

public Note getObject(String noteId){  
    String sql = "select * from note where id = ? ";        
    SessionFactory sef=HebernateTemplete.getSessionFactory();
    Session session=sef.openSession();
    Note obj = new Note();
    Query query = session.createSQLQuery(sql);
    query.setParameter(0, noteId);
    obj = (Note) query.uniqueResult();//获得一个对象
    //List list = query.list();//获取多个对象
    session.clear();
    session.close();
    return obj;
}
注:(1)无论是通过不同类型参数的设置接口来设置SQL参数,还是通过setParameter来设置参数,下标都是从0开始的
(2)除了setParameter,SQLQuery还提供了众多的接口来分别设置不同类型的参数,诸如setBigDecimal、setBinary、setDouble等

2、使用自定义的结果转换器处理查询结果
SQLQuery接口预留了setResultTransformer接口以实现使用用户自定义的ResultTransformer结果集转换器处理查询结果。ResultTransformer接口非常简单,只有两个方法,分别用来转换单行数据和所有结果数据。经过自定义ResultTransformer生成的实体,并未加入Session,因此是非受管实体。

如下代码,示范了如何将单行数据装入LinkedHashMap对象中:

//sql语句查询结果以后
public class ArrayToMap implements ResultTransformer (){ //需要实现ResultTransformer接口
   @Override
   public Object transformTuple(Object[] values, String[] columns) {
      Map<String, Object> map = new LinkedHashMap<String, Object>(1);
      int i = 0;
      for(String column : columns){
         map.put(column, values[i++]);
      }
      return map;
   }
   //如果想要返回list,需要在这个类中创建一个list集合,然后将上面获得的map放到list中
   @Override
   public List transformList(List list) {
      return list;
   }
}


//调用上面的方法将sql查询出来的数据以map的形式封装到一个对象中
public Object getObject(String noteId){  
    String sql = "SELECT l.name investLoanName,vr.invest_money investMoney,vr.time investTime"+ 
                 " FROM vip_invest vr left join loan l on vr.loan_id=l.id "+ 
                 " WHERE  vr.user_id = ? ";     
    SessionFactory sef=HebernateTemplete.getSessionFactory();
    Session session=sef.openSession();
    ResultTransformer rtf= new ResultTransformer(); 
    Query query = session.createSQLQuery(sql);
    query.setParameter(0, noteId);
    List list = (Note) query.list();//获得一组对象
    Object[] objs = list .toArray();//将获取的list集合转化成对象数组
    String[] columns = {investLoanName,investMoney,investTime}//货到sql查询的字段数组
    Object obj = query.setResultTransformer(rtf.transformTuple(objs,columns));
    session.clear();
    session.close();
    return obj;
}

注:
1、如果不设置自定义的ResultTransformer转换器,则Hibernate将每行返回结果的数据按照结果列的顺序装入Object数组中。
2、这里介绍一个工具类: Transformers ,它提供了一些常用的转换器,能够帮助我们快速转换结果集,如 Transformers.aliasToBean(Note.class) 能够将查询结果依别名注入到Note实体中。 (这种方式只限于查询一个对象)

3、使用标量(查询对应的字段)
使用SQLQuery执行原生SQL时,Hibernate会使用ResultSetMetadata来判定返回的标量值的实际顺序和类型。如果要避免过多的使用ResultSetMetadata,或者只是为了更加明确的指名返回值,可以使用addScalar()。
使用方式:
addScalar(“id”, LongType.INSTANCE):第一个参数是指明要查的字段名称,第二个参数是指明字段类型

session.createSQLQuery("select * from note where id = 1")
 .addScalar("id", LongType.INSTANCE)
 .addScalar("name", StringType.INSTANCE)
 .addScalar("createtime", DateType.INSTANCE);

补充: 这个查询指定了SQL查询字符串,要返回的字段和类型.它仍然会返回Object数组,但是此时不再使用ResultSetMetdata,而是明确的将id,name和createtime按照Long, String和Date类型从resultset中取出。同时,也指明了就算query是使用*来查询的,可能获得超过列出的这三个字段,也仅仅会返回这三个字段。
特殊情况1:
对全部或者部分的标量值不设置类型信息也是可以的,代码如下:

session.createSQLQuery("select * from note where id = 1")
 .addScalar("id")
 .addScalar("name")
 .addScalar("createtime", DateType.INSTANCE);

* 没有被指定类型的字段将仍然使用ResultSetMetdata获取其类型。 注意 ,字段不区分大小写,同时不能够指定不存在的字段 !


关于从ResultSetMetaData返回的java.sql.Types是如何映射到Hibernate类型,是由方言(Dialect)控制的。假若某个指定的类型没有被映射,或者不是你所预期的类型,你可以通过Dialet的registerHibernateType调用自行定义。

特殊情况2:
如果仅指定了一个scalar,即需要返回一个结果集:

Date createTime = (Date)session.createSQLQuery("select * from note where id = 1")
 .addScalar("createtime", DateType.INSTANCE)
 .uniqueResult();

补充: 如果我们的SQL语句使用了聚合函数,如count、max、min、avg等,且返回结果仅一个字段,那么Hibernate提供的这种提取标量结果的方式就非常便捷了。

4、实体查询 (即查询实体对象)
上面的查询都是返回标量值(即返回单个的字段值)的,也就是从resultset中返回的“裸”数据。下面展示如何通过addEntity()让原生查询返回实体对象。

(1) 查询一个对象实体

//查询一个对象的所有数据
session.createSQLQuery("select * from note where id = 1").addEntity(Note.class);
//查询一个对象的几个字段数据
session.createSQLQuery("select id,name,createtime from note where id = 1").addEntity(Note.class);

注: 这个查询指定SQL查询字符串,要返回的实体。假设Note被映射为拥有id,name和createtime三个字段的类,以上的两个查询都返回一个List,每个元素都是一个Note实体。

(2) 查询一个实体同时查询这个实体关联的实体
假若实体在映射时有一个many-to-one的关联指向另外一个实体,在查询时必须也返回那个实体,否则会导致发生一个”column not found”的数据库错误。这些附加的字段可以使用*标注来自动返回,但我们希望还是明确指明,看下面这个具有指向Dog的many-to-one的例子:

session.createSQLQuery("select id,note,createtime,author from note where id = ?").addEntity(Note.class);

补充: 查询语句中author字段即为Note实体和Author实体的关联字段,只需在查询时得到该字段的值,Hibernate即可使用该值找到对应的关联实体。如上例中,note.getAuthor()即可返回当前Note所属的Author对象。

(3) 处理关联和集合类(使用addJoin()方法)
多对一
在(2)中查询关联的对象时会导致初始化proxy带来的额外开销,所以通过提前抓取将Author连接获得,而避免初始化proxy带来的额外开销也是可能的。这是通过addJoin()方法进行的,这个方法可以让你将关联或集合连接进来。

session.createSQLQuery("select {note.*}, {author.*} from note note, user author where note.author = author.id")
.addEntity("note", Note.class)
.addJoin("author", "note.author");

一对多
上面的例子是多对一的关联查询,反过来做一对多的关联查询也是可以的。如下的例子中,author.notes表示该用户发表的所有日记(Note),Set集合类型:

session.createSQLQuery("select {author.*},{note.*} from note note, user author where author.id = ? and note.author = author.id")
.addEntity("author", User.class)
.addJoin("note", "author.notes");

注意 : join查询会在每行返回多个实体对象,处理时需要注意 。

多表联查结果处理

objs中包含n条数据,每条数据中包含几个对象就创建几个对象,然后不数据中的对象取出来
Object[] objs = query.list().toArray();
        Map<String,Object> map = null;
        try {
    for(intj=0;j<objs.length;j++){
        map = new HashMap<String,Object>(); 
        vipInvest = new VipInvest();
        loan = new Loan();  
        Object[] obj = (Object[])objs[j];
        vipInvest = (VipInvest)obj[0];
        loan = (Loan)obj[1];
        map.put("vipInvest", vipInvest);
        map.put("loan", loan);
        list.add(map);
    }
} catch (Exception e) {
    e.printStackTrace();
}

(4) 别名和属性引用
在(3)的多表关联查询中,假若SQL查询连接了多个表,同一个字段名可能在多个表中出现多次,这会导致SQL错误。不过在我们可以通过使用占位符来完美地解决这一问
查询多表关联表中所有的字段使用{}占位符

session.createSQLQuery("select {note.*}, {author.*} from note note, user author where note.author = author.id")
 .addEntity("note", Note.class)
 .addJoin("author", "note.author");

注:这个查询指明SQL查询语句,其中包含占位附来让Hibernate注入字段别名,查询并返回的实体。

查询多表关联表中部分字段使用{}占位符
上面使用的{note. }和{author. }标记是作为“所有属性”的简写形式出现的,当然你也可以明确地罗列出字段名。但如下的范例代码中我们让Hibernate来为每个属性注入SQL字段别名,字段别名的占位符是表别名

  • . + 属性名。
    注意 : 属性名区分大小写,而且不能够在where子句中使用占位符 。
SQLQuery query = session.createSQLQuery("select note.id as {note.id},note as {note.note},createtime as {note.createTime},author as {note.author}, {author.*} from note, user author where note.id = ? and note.author = author.id");
query.addEntity("note", Note.class);
query.addJoin("author", "note.author");

扩展:
大多数情况下,上面的别名注入方式可以满足需要,但在使用更加复杂的映射,比如复合属性、通过标识符构造继承树,以及集合类等等情况下,则需要更加复杂的别名注入方式。

在hbm文件中描述结果集映射信息,并在查询中使用

对于一些复杂的结果集映射,往往需要像MyBatis那样在文件中手动配置好,然后在程序中使用。幸运的是Hibernate也提供了类似的功能,你可以使用自己配置的结果集映射来处理返回的结果集数据:

SQLQuery query = session.createSQLQuery("select note.id as {note.id},note as {note.note},createtime as {note.createTime},author as {note.author}, {author.*} from note, user author where note.id = ? and note.author = author.id");
//使用在hbm文件中配置的自定义结果集映射
query.setResultSetMapping("noteAnduthor");
query.list();

执行更新操作

使用SQLQuery执行数据库更新操作比较容易,除了像查询时那样需要指定SQL语句(如有需要还需设置SQL参数)外,仅需调用executeUpdate()方法,即可提交更新操作。代码如下所示:

session.createSQLQuery("update createtime = ? from note where note.id = ?");
query.setDate(0, new Date());
query.setLong(1, 1L);
query.executeUpdate();

executeUpdate方法的返回结果为改变的数据库记录的行数。


原文链接:https://blog.csdn.net/houtingpeng/article/details/50036933?depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-4&utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-4