零碎知识小结:
◆数据类型:
1,<property name="name" type="java.lang.String"/>
type可以是hibernate,java类型或者你自己的类型(需要实现hibernate的一个接口)
2,基本类型一般不需要在映射文件(hbm.xml)中说明,只有在一个java类型和多个数据库数据类型相对应时并且你想要的和hibernate缺省映射不一致时,需要在映射文件中指明类型(如java.util.Date ,数据库DATE,TIME,DATETIME,TIMESTAMP,hibernate缺省会把java.util.Date映射成DATETIME 型,而如果你想映射成TIME,则你必须在映射文件中指定类型)。
◆session是非线程安全的,生命周期较短,代表一个和数据库的连接,在B/S系统中一般不会超过一个请求;内部维护一级缓存和数据库连接,如果session长时间打开,会长时间占用内存和数据库连接。
◆sessionFactory是线程安全的,一个数据库对应一个SessionFactory,生命周期长,一般在整个系统生命周期内有效;SessionFactory保存着和数据库连接的相关信息(user,password,url)和映射信息,以及Hibernate运行时要用到的一些信息。
◆调用session的flush方法,使得一级缓存和数据库进行一次同步。我们在程序中一般不直接调用该方法,而是交由hibernate进行处理,hibernate会尽量把对数据库的操作延迟,在事务提交的时候,或不得不做的时候,一次性的处理,采用批处理能够减少与数据库的交互次数,提高性能。
◆大批量处理
大量操作数据是可能造成内存溢出。
for(int i=0;i<100000;i++)
session.save(obj);
解决方法如下:
1,清除session中的数据
for(int i=0;i<100000;i++){
session.save(obj);
if(i%20==0){
session.flush();//调用flush是因为在清除一级缓存时,可能还有数据未真正的保存到数据库中,所以先做一次同步,再清除一级缓存
session.clear();
}
}
2,用StatelessSession接口:它不和一级缓存,二级缓存交互,也不触发任何事件,监听器,拦截器,通过该接口的操作会立刻发送给数据库,与jdbc的功能一样。
StatelessSession s = sessionFactory.openStatelessSession();该接口的方法与Session类似。
3,Query.executeUpdate()执行批量更新,会清除相关联的类二级缓存(sessionFactory.evict(class)),也可能会造成级联,和乐观锁定出现问题。
在hibernate 3.0之前,要大批量的更新数据,需执行如下操作:
- session s = HibernateUtil.getSession();
- Transaction tx = s.beginTransaction();
- Query q = s.createQuery("from User");
- List<User> users = q.list();
- for(User u:users){
- u.setBirthday(new Date());
- }
- tx.commit();
hibernate 3.0后,可以这样做:
- Query q = s.createQuery("update u set birthday=:bd from User as u");
- q.executeUpdate();
◆HQL
1,查询多个对象selecet art,user from Article art,User user where art.author.id=user.id and art.id=:id 这种方式返回的是Object[],Object[0]:article,Object[1]:user。
2,分页 query.setFirstResult, qurey.setMaxResults.
查询记录总数query.iterate("select count(*) from Person").next;
3,批量更新 query.executeUpdate()可能造成二级缓存有失效数据。
◆Criteria
1,排序Criteria.addOrder(Order.desc(propertyName));
2,关联查询criteria.setFetchMode("propertyName",FetchMode.SELECT)与映射文件中关联关系的fetch作用一致。
3,投影Projections.rowCount(),groupPropery....
4,分页Projections.rowCount();criteria.setFirstResult();criteria.setMaxResults()
5,DetachedCriteria 可在session未创建时,根据用户的组合查询条件来构造查询。即它可在session外创建(在其他层创建比如在Service中创建)然后用 detachedCriteria.getExecutableCriteria(Session)方法创建Criteria对象来完成查询。
示例:
在外部构建好离线查询对象DetachedCriteria。一般的criteria对象的创建需要与session对象关联,而离线查询对象的创建则不需要与session相关。
DetachedCriteria dc = DetachedCriteria.forClass(User.class);
String name = request.getParameter("name");
int age = request.getParameter("age");
dc.add(Restrictions.eq("name",name));
dc.add(Restrictions.eq("age",age));
把构建好的离线查询对象传递给dc方法,进行查询。
static List dc(DetachedCriteria dc){
Session s = HibernateUtil.getSession();
Criteria c = dc.getExecutableCriteria(s);//这时才与session关联构造出criteria对象,实施查询。
List rs = c.list();
s.close();
return rs;
}
6,Example查询,Example.create(obj);criteria.add(example);
N+1次查询和懒加载
1,用Query.iterator可能会有N+1次查询。
2,懒加载时获取关联对象
3,如果打开对查询的缓存即使用list也可能有N+1次查询。
示例代码:
public class Main {
public static void main(String[] args) {
User user = new User();
user.setBirthday(new Date());
Name name = new Name();
name.setFirstName("firstName");
name.setLastName("lastName");
user.setUserName(name);
for (int i = 0; i < 10; i++) {
user.getUserName().setFirstName("name" + i);
saveUser(user);
}
iterate();
}
static void iterate() {
Session s = HibernateUtil.getSession();
Query q = s.createQuery("from User");
Iterator<User> users = q.iterate();
while(users.hasNext()) {
System.out.println(users.next().getUserName().getFirstName());
}
}
public static void saveUser(User user) {
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();
session.close();
}
先向数据库中插入10条数据。然后查询所有的数据,打印出firstname,输出的sql语句为:
....//前面为10条插入语句
....
Hibernate: select user0_.id as col_0_0_ from user user0_
name0
name1
name2
name3
name4
name5
name6
name7
name8
name9
没有产生10条具体查询的语句,是因为配置了二级缓存和User类的缓存。主键生成方式为hilo而不是native,在保存10个对象后,同时会在二级缓存中进行缓存。等到查询时,先获得满足条件的id ,这就是第一条查询语句的作用。到真正读取时,根据id先到一级缓存,再到二级,最后到数据库中去查。在这里都在二级缓存中命中了。所以没产生另外的10条select语句。
把主键的生成方式改为:native。这样在插入数据后就不会在二级缓存中保存数据了。输出的sql语句为:
Hibernate: select user0_.id as col_0_0_ from user user0_
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name0
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name1
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name2
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name3
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name4
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name5
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name6
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name7
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name8
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
name9
一共产生了11条查询语句,这就是所谓的N+1次查询问题。
懒加载也会产生n+1次查询。
拦截器与事件
拦截器与事件都是hibernate的扩展机制,Interceptor接口是老的实现机制,现在改成事件监听机制;他们都是hibernate的回调接口,hibernate在save,delete,update...等会回调这些类。
通过实现监听器接口,编写自己的监听器实现,然后在hibernate配置文件中,注册监听器。
使用<event type="save"></event>标签。type表示监听器对哪种操作感兴趣。
示例:
<event type="save">
<listener class="hibernatetest.SaveListener"/>
</event>
执行后,监听器的动作得到了执行,但是对象却未保存到数据库中去。
分析:
由于我们编写了自己的监听器,hibernate认为我们有了更好的实现,所以系统默认的监听器会被放弃使用。 defaultSaveOrUpdateEventListener该监听器是在发生save动作时,执行保存。我们一般在自己的监听器中做一些其他的事情,所以要保存对象,需要手动的添加监听器的默认实现,在监听器依次执行相关操作,默认的监听器就会保存对象了。
根据监听器配置的先后顺序,在事件发生时,依次调用。
正确做法:
- <event type="save">
- <listener class="hibernatetest.SaveListener"/>
- <listener class="org.hibernate.event.def.defaultSaveOrUpdateEventListener"/>
- </event>
SQL和命名查询。
session.createSQLQuery(arg0)
参数是原生的sql语句。不过不推荐使用,sql会依赖于底层的具体数据库,减弱了程序的可移植性。
session.getNamedQuery(arg0)
命名查询可以放在映射文件中。只不过是把HQL预先定义好,以后可以拿来就用而已。
用Map代替Domain对象;将对象转化为XML。