1. HQL(Hibernate Query Language)语言
l FROM子句
l JOINING关联
l WHERE子句
l SELECT子句
l ORDER BY与GROUP BY比较
l 子查询
l 命名查询
2. Criteria Queries条件查询
l 条件查询-Criteria Query
l 限制-Restrictions
l 关联
l 投影
l ORDERING和GROUPING
l 通过example对象查询
3. Native SQL查询 (Hibernate会依据底层数据库自动选择)
l 批处理-Batch Processing
l Native SQL查询
l 命名SQL查询
Session基本管理知识-Session
Hibernate在对数据库进行操作之前,必须先取得Session实例,相当于JDBC在对数据库操作之前,必须先取得Connection实例, Session是Hibernate操作的基础,它不是设计为线程安全(Thread-safe)的,建议一个Session最好由一个线程来使用。
Session实例由SessionFactory开启获得,例如:
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
....
session.close();
通过Session,可以对数据库进行新增、删除、更新,例如使用save()新增一条数据:
通过Session新增数据:
User user = new User();
user.setName(“hhp");
user.setAge(new Integer(30));
session.save(user);
使用get()或load()方法取得id为1的数据:
User user = (User) session.get(User.class, new Integer(1));
User user = (User) session.load(User.class, new Integer(1));
注意:
Session.get()如果未能发现相符合的数据,则get()方法会返回null,而load()方法会丢出ObjectNotFoundException,在高级的应用中,load()方法可以返回代理对象,并可充分利用缓冲机制。
推荐大家使用session.load()
在Hibernate 3中,取消了find()方法,您必须通过Query或Criteria来进行数据查询。
接下来看看使用Session更新与删除数据,可使用delete(Object)删除数据:
User user = (User) session.get(User.class, new Integer(1));
session.delete(user);
delete(Object)在缓存中操作,必须执行flush()才从数据库中删除。
为提高执行性能,推荐最好采用批处理方式,单条任务执行flush()会降低性能。
注意:关闭Session后的对象需要重新开启Session更新
如果您开启了一个Session,从数据表中取出数据显示到使用者接口上,之后关闭Session,当使用者在接口上操作完毕并进行储存时,这时您要重新开启一个Session,使用update()方法将对象中的数据更新至对应的数据表中:
User user = (User) session.get(User.class, new Integer(2));
session.close();
....
user.setAge(new Integer(27));
session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.update(user);
tx.commit();
session.close();
Session提供了一个saveOrUpdate()方法,为数据的储存或更新提供了一个统一的操作接口,藉由定义映像文件时,设定< id>标签的unsaved-value来决定什么是新的值必需插入,什么是已有的值必须更新:
<id name="id" column="id" type="java.lang.Integer" unsaved-value="null">
<generator class="native"/>
</id>
unsaved-value可以设定的值包括:
any:总是储存
none:总是更新
null:id为null时储存(预设)
valid:id为null或是指定值时储存
这样设定之后,您可以使用Session的saveOrUpdate()方法来取代update()方法。
Session是Hibernate运作的中心,对象的生命周期、事务的管理、数据库的存取,都与Session息息相关,就如同在编写JDBC时需关心 Connection的管理,以有效的方法创建、利用与回收Connection,以减少资源的消耗,增加系统执行效率一样,有效的Session管理,也是Hibernate应用时需关注的焦点。
Session是由SessionFactory所创建,SessionFactory是线程安全的(Thread-safe),您可以让多个线程同时存取SessionFactory而不会有数据共享的问题,然而Session则不是设计为线程安全的,所以试图让多个线程共享一个 Session,将会发生数据共享而发生混乱的问题。
Thread-Specific Stronge 模式管理Session共享,Session不是手工生成,由第三方分配管理。
在Hibernate参考手册中的 Quickstart with Tomcat 中,示范了一个HibernateUtil,它使用了ThreadLocal类别来建立一个Session管理的辅助类,这是Hibernate的 Session管理一个广为应用的解决方案,ThreadLocal是 Thread-Specific Storage 模式 的一个操作实例。
由于Thread-Specific Stroage模式可以有效隔离线程所使用的数据,所以避开Session的多线程之间的数据共享问题,以下列出Hibernate参考手册中的 HibernateUtil类 。
ThreadLocal举例:
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory
sessionFactory =
new Configuration().configure().buildSessionFactory();
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
log.error("Initial SessionFactory creation failed.", ex);
throw new ExceptionInInitializerError(ex);
}
}
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() {
Session s = (Session) session.get();
// Open a new Session, if this Thread has none yet
if (s == null) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() {
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}
在同一个线程中,Session被暂存下来了,但无须担心数据库连结Connection持续占用问题,Hibernate会在真正需要数据库操作时才(从连接池中)取得Connection。
在程序中可以这么使用HibernateUtil:
Session session = HibernateUtil.currentSession();
User user = (User) session.load(User.class, new Integer(1));
System.out.println(user.getName());
HibernateUtil.closeSession();
在Web应用程序中,可以藉助Filter来进行Session管理,在需要的时候开启Session,并在Request结束之后关闭Session
HQL查询 (不用Session与数据库交互)
Hibernate提供的查询方案1
HQL(Hibernate Query Language)语言
l FROM子句
l JOINING关联
l WHERE子句
l SELECT子句
l ORDER BY与GROUP BY比较
l 子查询
l 命名查询
注意:
l 完全支持关系操作
l HQL查询语句以对象格式返回查询结果,易于操作
l HQL支持多态查询
l 支持很多高级特性
l HQL查询语句,除了Java类和属性的名称外,查询语句对大小写不敏感
l 数据库的无依赖性
Query接口
可以通过org.hibernate.Query接口的实例来进行查询,通过Query接口,您可以先设定查询参数,之后通过setXXX()等方法,将指定的参数值填入,而不用每次都编写完整的HQL,直接来看个例子:
Session session = sessionFactory.openSession();
Query query = session.createQuery("select user.name from User as user where user.age > ?");
query.setInteger(0, 25);
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
session.close();
在设定参数值时,必须依照 ? 所设定的顺序,并使用对应型态的setXXX()方法,一个执行的例子如下:
Hibernate: select user0_.name as col_0_0_ from user user0_ where user0_.age>?
hhp
tiantian
zhangy
您可以使用命名参数(Named Parameter)来取代这个方法,这可以不用依照特定的顺序来设定参数值,并拥有较好的可读性,直接来看个例子:
Session session = sessionFactory.openSession();
Query query = session.createQuery("select user.name from User as user where user.age > :minAge");
query.setInteger("minAge", 25);
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
session.close();
命名参数要点:
设定命名参数时,在建立Query时先使用‘ : ’后跟着参数名,之后就可以在setXXX()方法中直接指定参数名来设定参数值,而不用依照特定的顺序。
也可以将HQL编写在程序之外,以避免硬编码(Hard code)在程序之中,在需要修改HQL时就很方便,在*.hbm.xml中使用<query/>标签,并在& lt;![CDATA[与]] >之间编写HQL,编写的位置是在</hibernate-mapping>之前,例如:
<hibernate-mapping>
<class name=“com.cstp.User" table="user">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"/>
</id>
<property name="name" column="name" type="java.lang.String"/>
<property name="age" column="age" type="java.lang.Integer"/>
</class>
<query name="onlyfun.caterpillar.QueryUser">
<![CDATA[
select user.name from User as user where user.age > :minAge
]]>
</query>
</hibernate-mapping>
对应上面的配置,<query>的name属性用来设定查询外部HQL时的名称依据,使用的例子如下:
Session session = sessionFactory.openSession();
Query query = session.getNamedQuery(“com.cstp.QueryUser");
query.setInteger("minAge", 25);
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
session.close();
使用HQL查询最简单的例子,就是查询指定类别对应表格的所有数据,例如:
Session session = sessionFactory.openSession();
Query query = session.createQuery("from User");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
User user = (User) iterator.next();
System.out.println(user.getId() + "/t" +
user.getAge() + "/t" +
user.getName());
}
Query的属性setFetchSize()可以用来设定抓取记录的条数,告诉底层JDBC驱动对每一个请求传输多少行记录 (可用于分页)
Query query = session.createQuery(“from book”);
query.setFetchSize(100);
List books = query.list();
也可以指定类别的全名,例如:
Query query = session.createQuery("from com.cstp.User");
HQL本身不区分大小写,不过要注意类别的名称必须区分大小写。
#在查询类别对应的表格时,需注意到继承的问题,Hibernate会自动判定继承关系,如果查询的类别是某类别的父类别,则会返回与父类别、子类别对应的所有表格数据,例如如果查询java.lang.Object,由于Object在Java中是所有类别的父类别,所以下面这个查询会返回数据库中所有表格的数据:
Query query = session.createQuery("from java.lang.Object");
如果要针对某个属性作查询,则可以如下:
Session session = sessionFactory.openSession();
Query query = session.createQuery("select user.name from User as user");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
如果要查询两个以上的属性,则如下,查询的结果会以数组的方式传回:
Session session = sessionFactory.openSession();
Query query = session.createQuery("select user.age, user.name from User as user");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
Object[] obj = (Object[]) iterator.next();
System.out.println(obj[0] + "/t" + obj[1]);
}
如果User类别提供有适当的构造方法,则可以在使用HQL时直接指定新建一个对象传回,例如若User如下设计:
public class User {
private Integer id;
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
....
}
则在使用HQL查询时可以如下:
Session session = sessionFactory.openSession();
Query query = session.createQuery("select new User(user.name, user.age) from User as user");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
User user= (User) iterator.next();
System.out.println(user.getAge() + "/t" + user.getName());
}
要注意的是,这个返回的User实例并未与数据库有任何关联,可以试着取得id属性,可以发现它的值是null,如果试图使用Session的 saveOrupdate()方法,则会新增一条数据而不是更新原有的数据。
可以使用distinct去除数据重复的记录:
Query query = session.createQuery("select distinct user.age from User as user");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
可以使用distinct去除数据重复的记录,也可以如下:
Query query = session.createQuery(“from Book where isbn = ?”);//采用位置参数
query.setString(0,”1232245”);
Book book = (Book)query.uniqueResult();
或者用别名:
Query query = session.createQuery(“from Book where isbn = :isbn”);//采用别名
query.setString(isbn,”1232245”);
Book book = (Book)query.uniqueResult();
您也可以在HQL中使用函数,例如取得记录的条数:
Query query = session.createQuery("select count(*) from User as user");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
使用avg()取得属性的平均值:
Query query = session.createQuery("select avg(user.age) from User as user");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
使用upper()函数将字符串转为大写:
Query query = session.createQuery("select upper(user.name) from User as user");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
可以使用where子句来限定查询的条件,除了 = 运算之外,还有 >、>=、<、<=、!= 或 <>等比较运算,例如:
Session session = sessionFactory.openSession();
Query query = session.createQuery("from User user where user.name=‘hhp'");
List names = query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
User user = (User) iterator.next();
System.out.println(user.getAge() + "/t" + user.getName());
}
也可以在where子句上进行表达式,例如:
Query query = session.createQuery("from User user where (user.age / 10 = 3)");
也可以在where子句上使用and、or,例如:
Query query = session.createQuery("from User user where (user.age > 20) and (user.name = ‘hhp')");
is not nullL与is null则可以测试字段值是否为空值,例如:
Query query = session.createQuery("from User user where user.name is not null");
between可以测试字段值是否在指定的范围之内,例如:
Query query = session.createQuery("from User user where user.age between 20 and 30");
可以使用in或not in来测试字段值是否在您指定的集合中,例如:
Query query = session.createQuery("from User user where user.name in('caterpillar', 'momor')");
like或not like可以让您进行模糊条件搜寻,例如想搜寻名称中含有cstp开头的数据:
Query query = session.createQuery("from User user where user.name like 'cstp%'");
可以对查询结果使用order by进行排序:
Query query = session.createQuery("from User user order by user.age");
可使用desc反排序:
Query query = session.createQuery("from User user order by user.age desc");
可同时指定两个以上的排序方式,例如先按照"age"反序排列,如果"age"相同,则按照"name"顺序排列:
Query query = session.createQuery("from User user order by user.age desc, user.name");
可以配合GROUP BY子句,自动将指定的字段依相同的内容群组,例如依字段"sex"分组并作平均:
Query query = session.createQuery("select user.sex, avg(user.age) from User user group by user.sex");
还可以结合having子句,例如只将平均大于20的数据分组显示出来:
Query query = session.createQuery("select user.sex, avg(user.age) from User user group by user.sex having avg(user.age) > 20");
在Hibernate 2时,HQL只用于查询数据,要更新或删除数据,则是依赖于Session的update()、saveOrUpdate()、delete()等方法,在Hibernate 3中,HQL新增了update与delete语句,可以直接使用HQL指定更新或删除,例如使用update子句进行更新:
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
Query query = session.createQuery("update User set name=‘tiantian' where name=‘hhp'");
query.executeUpdate();
tx.commit();
session.close();
使用delete子句进行数据删除:
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
Query query = session.createQuery("delete User where name=‘zhangy'");
query.executeUpdate();
tx.commit();
session.close();
Join关联
join 语法的形式
HQL支持两种关联join的形式:implicit(隐式) 与explicit(显式)。
上一节中给出的查询都是使用explicit(显式)形式的,其中form子句中明确给出了join关键字。这是建议使用的方式。
implicit(隐式)形式不使用join关键字。关联使用“点号”来进行“引用”。
implicit join可以在任何HQL子句中出现.implicit join在最终的SQL语句中以inner join的方式出现。 <——需查证?
如:
from Cat as cat where cat.mate.name like '%s%'
Hibernate提供的查询方案2
Criteria Queries条件查询
l 条件查询-Criteria Query
l 限制-Restrictions
l 关联
l 投影
l ORDERING和GROUPING
l 通过(Example)示例对象查询
Criteria对SQL进行封装,让开发人员可以用对象的方式来对数据库进行操作,例如下面的查询User表格中的所有数据:
Criteria criteria = session.createCriteria(User.class);
// 查询user所有字段
List users = criteria.list();
Iterator iterator = users.iterator();
System.out.println("id /t name/age");
while(iterator.hasNext()) {
User user = (User) iterator.next();
System.out.println(user.getId() +
" /t " + user.getName() +
"/" + user.getAge());
}
Hibernate实际上使用以下的SQL来查询数据库:
select this_.id as id0_, this_.name as name0_0_, this_.age as age0_0_ from user this_
注意:
Criteria实际上只是个容器,如果想要设定查询条件,则要使用add()方法加入Restrictions的条件限制,例如查询age大于20且小于40的数据:
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.gt("age", new Integer(20)));
criteria.add(Restrictions.lt("age", new Integer(40)));
List users = criteria.list();
也可以使用逻辑组合来进行查询,例如结合age等于(eq)20或(or)age为空(isNull)的条件:
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.or(
Restrictions.eq("age", new Integer(20)),
Restrictions.isNull("age")
));
List users = criteria.list();
也可以使用sqlRestriction()方法来提供SQL语法作限定查询,例如查询name以cater开头的数据:
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.sqlRestriction("{alias}.name LIKE (?)", "cater%", Hibernate.STRING));
List users = criteria.list();
请对比理解上述语句含义:
Hibernate实际上使用以下的SQL来查询数据库:
select this_.id as id0_, this_.name as name0_0_, this_.age as age0_0_ from user this_
其中alias将被替换为与User类别相关的名称,而?将被替换为cater%,也就是第二个参数所提供的值,在SQL撰写时,不必再写WHERE,如果有多个查询条件,例如BETWEEN子句的查询,则可以如下:
Criteria criteria = session.createCriteria(User.class);
Integer[] ages = {new Integer(20), new Integer(40)};
Type[] types = {Hibernate.INTEGER, Hibernate.INTEGER};
criteria.add(Restrictions.sqlRestriction("{alias}.age BETWEEN (?) AND (?)", ages, types));
List users = criteria.list();
Restrictions的几个常用限定查询方法如下表所示:
您可以使用Criteria进行查询,并使用Order对结果进行排序,例如使用Oder.asc()由小到大排序(反之则使用desc()):
Criteria criteria = session.createCriteria(User.class);
criteria.addOrder(Order.asc("age"));
List users = criteria.list();
setMaxResults()方法可以限定查询回来的笔数,如果配合setFirstResult()设定传回查询结果第一笔数据的位置,就可以实现简单的分页,例如传回第51条之后的50条数据(如果有的话):
Criteria criteria = session.createCriteria(User.class);
criteria.setFirstResult(51);
criteria.setMaxResult(50);
List users = criteria.list();
Criteria 高级查询-投影(Projections)
您可以对查询结果进行统计动作,使用Projections的avg()、rowCount()、count()、max()、min()、 countDistinct()等方法,例如对查询结果的"age"作平均:
Criteria criteria = session.createCriteria(User.class);
criteria.setProjection(Projections.avg("age"));
List users = criteria.list();
Iterator iterator = users.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
还可以配合Projections的groupProperty()来对结果进行分组,例如以"age"进行分组,也就是如果数据中"age"如果有 20、20、25、30,则以下会显示20、25、30:
Criteria criteria = session.createCriteria(User.class);
criteria.setProjection(Projections.groupProperty("age"));
List users = criteria.list();
Iterator iterator = users.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
如果想结合统计与分组功能,则可以使用ProjectionList,例如下面的程序会计算每个年龄各有多少个人:
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.groupProperty("age"));
projectionList.add(Projections.rowCount());
Criteria criteria = session.createCriteria(User.class);
criteria.setProjection(projectionList);
List users = criteria.list();
Iterator iterator = users.iterator();
while(iterator.hasNext()) {
Object[] o = (Object[]) iterator.next();
System.out.println(o[0] + "/t" + o[1]);
}
如果有一个已知的对象,则可以根据这个对象作为查询的依据,看看是否有属性与之类似的对象,例如:
User user = new User();
user.setAge(new Integer(30));
Criteria criteria = session.createCriteria(User.class);
criteria.add(Example.create(user));
List users = criteria.list();
Iterator iterator = users.iterator();
System.out.println("id /t name/age");
while(iterator.hasNext()) {
User ur = (User) iterator.next();
System.out.println(ur.getId() +
" /t " + ur.getName() +
"/" + ur.getAge());
}
在这个例子中,user对象中有已知的属性"age"为30,使用Example会自动过滤掉user的空属性,并以之作为查询的依据,也就是找出 "age"同为30的数据。
Criteria可以进行复合查询,即在原有的查询基础上再进行查询,例如在Room对User的一对多关联中,在查询出所有的Room数据之后,希望再查询users中"age"为30的user数据:
Criteria roomCriteria = session.createCriteria(Room.class);
Criteria userCriteria = roomCriteria.createCriteria("users");
userCriteria.add(Restrictions.eq("age", new Integer(30)));
List rooms = roomCriteria.list(); // 只列出users属性中有user之"age"为30的Room
Iterator iterator = rooms.iterator();
Criteria与Session绑定,其生命周期跟随着Session结束而结束,使用Criteria时进行查询时,每次都要于执行时期动态建立对象,并加入各种查询条件,随着Session的回收,Criteria也跟着回收。
为了能够重复使用Criteria对象,在Hibernate 3中新增了DetchedCriteria(分离条件查询),您可以先建立DetchedCriteria实例,并加入各种查询条件,并于需要查询时再与Session绑定,获得一个绑定Session的Criteria对象,例如:
// 先建立DetchedCriteria对象
DetachedCriteria detchedCriteria = DetachedCriteria.forClass(User.class);
// 加入查询条件
detchedCriteria.add(Restrictions.ge("age",new Integer(25)));
Session session = sessionFactory.openSession();
// 绑定Session并返回一个Criteria实例
Criteria criteria = detchedCriteria.getExecutableCriteria(session);
List users = criteria.list();
Iterator iterator = users.iterator();
System.out.println("id /t name/age");
while(iterator.hasNext()) {
User ur = (User) iterator.next();
System.out.println(ur.getId() +
" /t " + ur.getName() +
"/" + ur.getAge());
}
Hibernate提供的查询方案3
Native SQL查询
l 批处理-Batch Processing
l Native SQL查询
l 命名SQL查询
批量处理(Batch processing)
使用Hibernate将 100 000 条记录插入到数据库,怎么办?
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....); session.save(customer);
}
tx.commit();
session.close();
该程序会有什么问题?
这段程序大概运行到 50 000 条记录左右会失败并抛出 内存溢出异常(OutOfMemoryException) 。
原因:
这是因为 Hibernate 把所有新插入的 客户(Customer)实例在 session级别的缓存区进行了缓存的缘故。
首先,如果你要执行批量处理并且想要达到一个理想的性能, 那么使用JDBC的批量(batching)功能是至关重要。将JDBC的批量抓取数量(batch size)参数设置到一个合适值 (比如,10-50之间):
hibernate.jdbc.batch_size 20
注意,假若你使用了identiy标识符生成器,Hibernate在JDBC级别透明的关闭插入语句的批量执行。
你也可能想在执行批量处理时关闭二级缓存:
hibernate.cache.use_second_level_cache false
但是,这不是绝对必须的,因为我们可以显式设置CacheMode来关闭与二级缓存的交互。
批量插入(Batch inserts)
如果要将很多对象持久化,你必须通过经常的调用 flush() 以及稍后调用 clear() 来控制第一级缓存的大小。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ )
{
Customer customer = new Customer(.....);
session.save(customer);
if ( i % 20 == 0 )
{
//20, same as the JDBC batch size
//20,与JDBC批量设置相同
//flush a batch of inserts and release memory:
//将本批插入的对象立即写入数据库并释放内存
session.flush();
session.clear();
}
}
tx.commit();
session.close();
批量更新(Batch updates)
此方法同样适用于检索和更新数据。此外,在进行会返回很多行数据的查询时, 你需要使用 scroll() 方法以便充分利用服务器端游标所带来的好处。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers")
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
if ( ++count % 20 == 0 )
{
//flush a batch of updates and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();
StatelessSession (无状态session)接口
作为选择,Hibernate提供了基于命令的API,可以用detached object的形式把数据以流的方法加入到数据库,或从数据库输出。
StatelessSession没有持久化上下文,也不提供多少高层的生命周期语义。特别是,无状态session不实现第一级cache,也不和第二级缓存,或者查询缓存交互。它不实现事务化写,也不实现脏数据检查。
用stateless session进行的操作甚至不级联到关联实例。stateless session忽略集合类(Collections)。
通过stateless session进行的操作不触发Hibernate的事件模型和拦截器。无状态session对数据的混淆现象免疫,因为它没有第一级缓存。无状态session是低层的抽象,和低层JDBC相当接近。
StatelessSession session = sessionFactory.openStatelessSession(); Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers") .scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() )
{
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
session.update(customer);
}
tx.commit();
session.close();
注意在上面的例子中,查询返回的Customer实例立即被脱管(detach)。它们与任何持久化上下文都没有关系。
StatelessSession 接口定义的insert(), update() 和 delete()操作是直接的数据库行级别操作,其结果是立刻执行一条INSERT, UPDATE 或 DELETE 语句。因此,它们的语义和Session 接口定义的save(), saveOrUpdate() 和delete() 操作有很大的不同。
Native SQL 查询
Hibernate提供了对SQL的支持,您可以指定您所要建立的SQL,并将实体类与数据表格关联,举个例子来说,如果您打算使用像以下的SQL语句:
SELECT * FROM user WHERE age > 20
则您可以如下建立SQL查询:
// SQL,并指定别名为user
String sql = "select {user.*} from User user where user.age > 20";
Session session = sessionFactory.openSession();
// 建立 SQLQuery
SQLQuery sqlQuery = session.createSQLQuery(sql);
// 将别名user与实体类User关联在一起
sqlQuery.addEntity("user", User.class);
Iterator iterator = sqlQuery.list().iterator();
while(iterator.hasNext()) {
User user = (User) iterator.next();
System.out.println(user.getAge() + "/t" + user.getName());
}
session.close();
addEntity()是将实体类与别名连结在一起的方法,大括号指定要查询的数据,Hibernate根据所给定的SQL自动生成以下的句子:
select user.id as id0_, user.name as name0_0_, user.age as age0_0_ from User user where user.age > 20
返回的结果则由Hibernate进行封装为所指定别名关联之实体类,如此您可以得到使用SQL的弹性,但无需处理繁琐的ResultSet。
您也可以将SQL语句定义在映像文件中,例如:
User.hbm.xml
<hibernate-mapping>
<class name=“com.cstp.User" table="user">
....
</class>
<sql-query name=“com.cstp.QueryUser">
<![CDATA[
select {user.*} from User user where user.age > 20
]]>
<return alias="user" class=“com.cstp.User"/>
</sql-query>
</hibernate-mapping>
注意:
定义的时候,使用<return>标签指定别名与实体类之关联,配合映像文件中的定义,您可以如下运行Hibernate:
Session session = sessionFactory.openSession();
Query query = session.getNamedQuery(“com.cstp.QueryUser");
Iterator iterator = query.list().iterator();
while(iterator.hasNext()) {
User user = (User) iterator.next();
System.out.println(user.getAge() + "/t" + user.getName());
}
session.close();
也可以设定查询参数,例如:
....
<sql-query name=“com.cstp.QueryUser">
<![CDATA[
select {user.*} from User user where user.age > :age
]]>
<return alias="user" class=“com.cstp.User"/>
</sql-query>
....
使用Hibernate查询时如下:
Session session = sessionFactory.openSession();
Query query = session.getNamedQuery(“com.cstp.QueryUser");
query.setInteger("age", 20);
Iterator iterator = query.list().iterator();
while(iterator.hasNext()) {
User user = (User) iterator.next();
System.out.println(user.getAge() + "/t" + user.getName());
}
session.close();
Hibernate 3的映射文件中新增了<sql-insert>、<sql-update>与< sql-delete>三个标签,您可以在这三个标签中使用SQL自定义您的INSERT、UPDATE、DELETE,也就是储存、更新、删除数据时的行为,例如:
<hibernate-mapping>
<class name=“com.cstp.User" table="user">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"/>
</id>
<property name="name" column="name" type="java.lang.String"/>
<property name="age" column="age" type="java.lang.Integer"/>
<sql-insert>
INSERT INTO user (name, age) VALUES (?, ?)
</sql-insert>
<sql-update>
UPDATE user SET name=?, age=?, WHERE id=?
</sql-update>
<sql-delete>
DELETE FROM user WHERE id=?
</sql-delete>
</class>
</hibernate-mapping>
对应的顺序是映射文件中属性出现的顺序,假设您储存对象:
session = sessionFactory.openSession();
tx = session.beginTransaction();
session.save(user);
tx.commit();
session.close();
则执行结果中显示的SQL语句会是您自定义的语句,而不是由 Hibernate自动生成的语句:
Hibernate:
INSERT INTO user (name, age) VALUES (?, ?)
数据识别问题(Data Identity)
讨论一下对象识别问题,对数据库而言,其识别一条数据唯一性的方式是根据主键值,如果手上有两份数据,它们拥有同样的主键值,则它们在数据库中代表同一个字段的数据。
对Java而言,要识别两个对象是否为同一个对象有两种方式,
一种是根据对象是否拥有同样的内存位置来决定,在Java语法中就是透过== 运算来比较,
一种是根据equals()、hasCode()中的定义。
先探讨第一种Java的识别方式在Hibernate中该注意的地方,在Hibernate中,如果是在同一个session中根据相同查询所得到的相同数据,则它们会拥有相同的Java识别,举个实际的例子来说明:
Session session = sessions.openSession();
Object obj1 = session.load(User.class, new Integer(1));
Object obj2 = session.load(User.class, new Integer(1));
session.close();
System.out.println(obj1 == obj2);
上面这个程序片段将会显示true的结果,表示obj1与obj2是参考至同一对象,但如果是以下的情况则会显示false:
Session session1 = sessions.openSession();
Object obj1 = session1.load(User.class, new new Integer(1));
session1.close();
Session session2 = sessions.openSession();
Object obj2 = session2.load(User.class, new Integer(1));
session2.close();
System.out.println(obj1 == obj2);
注意:
Hibernate并不保证不同时间所取得的数据对象,其是否参考至内存的同一位置,使用==来比较两个对象的数据是否代表数据库中的同一条数据是不可行的,事实上就算在Java程序中也不会这么比较两个对象是否相同。
要比较两个对象是否相同,会通过equals()方法,而Object预设的 equals()本身即比较对象的内存参考,如果您要有必要比较通过查询后两个对象的数据是否相同(例如当对象被储存至Set时)您必须实例化 equals()与hashCode()。
一个实例equals()与hashCode()的方法是根据数据库的identity,方法之一是通过getId()方法取得对象的id值并加以比较,例如若id的型态是String,一个实例的例子如下:
public class User {
....
public boolean equals(Object o) {
if(this == o) return true;
if(id == null || !(o instanceof User)) return false;
final User user == (User) o;
return this.id.equals(user.getId());
}
public int hashCode() {
return id == null ? System.identityHashCode(this) : id.hashcode();
}
}
注意:
这个例子是个不被鼓励的例子,因为当一个对象被new出来而还没有save()时,它并不会被赋予id值,这时候就不适用这个方法。
另一个比较被采用的方法是根据对象中真正包括的的属性值来作比较,在 Hibernate 官方参考手册 中给了一个例子:
public class Cat {
...
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Cat)) return false;
final Cat cat = (Cat) other;
if (!getName().equals(cat.getName())) return false;
if (!getBirthday().equals(cat.getBirthday())) return false;
return true;
}
public int hashCode() {
int result;
result = getName().hashCode();
result = 29 * result + getBirthday().hashCode();
return result;
}
}
这个例子不是简单的比较id属性,而是一个根据业务键值(Business key)实例equals()与hasCode()的例子,当然留下的问题就是您如何在实例时利用相关的业务键值,这就要根据您实际的业务需求来决定了。
愿意的话,还可以使用org.apache.commons.lang.builder.EqualsBuilder与 org.apache.commons.lang.builder.HashCodeBuilder来协助定义equals()与hashCode(),例如:
public class User {
....
public boolean equals(Object obj) {
if(obj == this) {
return true;
}
if(!(obj instanceof User)) {
return false;
}
User user = (User) obj;
return new EqualsBuilder()
.append(this.name, user.getName())
.append(this.phone, user.getPhone())
.isEquals();
}
public int hashCode() {
return new HashCodeBuilder()
.append(this.name)
.append(this.phone)
.toHashCode();
}
}