1)Hibernate的延迟机制的实现:主要是使用CGLIB,主要在一个拦截器中,如果lazy的标志是true,那么它会生成一个代理的对象(用CGLIB生成),如果不是true的话,就直接查询数据库(假设还没有在一级缓存中存在,如果在缓存存在的话就到缓存中拿), get方法不允许使用lazy机制,而load可以,而在Hibernate3之后load默认是lazy的,并且可以进一步设置属性的延迟。
2)在级联操作中,多对一的被关联表是先插入record然后在commit时再update外键值
Hibernate: insert into tsendtable (covercd, remark, polno) values (?, ?, ?)
Hibernate: update tsendtable set spint1=? where polno=?
一般将many一方作为主控方,这样它能够主动获取one方的id。当inverse为false时便是主控方。它会维护关联关系。
###一直以来都知道当save对象后,该对象便成为持久化对象,Hibernate会自动将id邦定到对象中(pmc.getId()便能返回id值,但实际此时record还没有在数据库中插入),但一直以来不知道Hibernate是怎么做到的,现在也不是很清楚,但经过一个测试实例,得到结论是,当第一次save一个对象时,Hibernate会发一个查询最大的sequence值的sql,之后就不触发了。也就是说Hiberate还是通过发出一个sql查询到id最大值后再绑到持久化对象中:
public void testIdsave(){
Transaction tx=sess.beginTransaction();
for(int i=0; i<3;i++){
PMMConfig pmc=new PMMConfig();
pmc.setCovercd("PMM "+101+i);
sess.save(pmc);
System.out.println("pmc.getId "+pmc.getId());
}
tx.commit();
// sess.close();
}
Hibernate: select max(seqno) from tscmpmtable
pmc.getId 42
pmc.getId 43
pmc.getId 44
###inverse & cascade
----cascade 是表明当一个对象保存(delete/update/query)时是否需要对关联的对象作同样的操作,它是一个indicator
----inverse负责表明谁去 管理关联关系,也就是说谁负责去给外键赋值,如果是one 方是主控方(User),则它在save时先把多方(Addr),保存,然后通过一个update语句去update 外键(addr.user_id),如果many方是主控方,那么需要显示指明要插入的是什么外键值,因为addr并没知道外键值是什么。而如果是one为主控方,它会将自己的id去update外键值。(深入浅出P174)
###inverse:我们说Hibernate Inverse设立不当会导致性能低下,其实是说Inverse设立不当,会产生多余重复的SQL语句甚至致使JDBC exception的throw------->多余重复的sql是指即使addr已经保存并且已赋值给外键了,他还是会使用update语句去update每个记录的外键。至于是jdbc exception是指在如果外键不为null时便会发生,因为one为主控方时,它首先插一个null值到外键,然后再用update语句来update。
###one 方的inverse为false,执行delete时执行的sql是:
Hibernate: select config0_.seqno as seqno0_0_,..from tsclmp config0_ where config0_.seqno=?
Hibernate: select promo0_.spint1 as spint4_1_,..from dpromo promo0_ where promo0_.spint1=?
Hibernate: update dpromo set spint1=null where spint1=?
Hibernate: delete from dpromo where polno=?
Hibernate: delete from dpromo where polno=?
Hibernate: delete from tsclmp where seqno=?
----save 时执行的语句是:
Hibernate: select max(seqno) from tsclmp
Hibernate: select max(polno) from dpromo
Hibernate: insert into tsclmp (a, b, c, d, e, f, g, h) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into dpromo (a, b, c, d) values (?, ?, ?, ?)
Hibernate: insert into dpromo (a, b, c, d) values (?, ?, ?, ?)
Hibernate: update dpromo set a=? where p=? ====>会多执行这两条update sql
Hibernate: update dpromo set a=? where p=?
##缓存的作用主要是用来提高性能,可以简单理解成一个Map;使用缓存涉及到三个操作:把数据放入缓存,从缓存中获取数据,删除缓存中的无效数据.
一级缓存,即session级共享.使用load,get,save,update等方法时都会将对象放在一级缓存中,你可以理解成是一个容器来保存的.
一般使用update一般不会直接从数据库加载数据,因为在你第一次用save方法保存数据时,在一级缓存中已有相关记录.所以当你使用update语句更新数据时,是直接从一级缓存中获取数据的(一级如果没找到,可以会从二级去找),最后更新后再修改数据库记录.
通过load加载上来的数据,一般在一级和二级缓存中都会有记录的.所在当要查找相关记录时,hibernate会首先从一级和二级缓存中去找.
嗯,session是通过对象的id判断缓存中已经有了某个对象;但好像记得session中缓存是对象的形式.
其实hibernate缓存机制不是很难理解,只要楼主把原理弄懂,比如一级和二级缓存缓存过程中的先后顺序及其相关的方法对应的缓存级别.相信楼主很快可以掌握的.
##
若跟踪session可以发现persistenceContext->entityEntities->map->entries->table[i],我认为这个是hibernate的缓存。当get调用时,table[i]中key为这个对象,value中同样也保存着一些属性,比如:实体信息(无id)/existsInDatabase=true/loadedState=加载上来的状态/deletedState=true,当load调用时,table为空,代理对象在哪里?
session.delete()根本就无视懒加载,hibernate作者问题?
hibernate在update时,为什么情愿先select后update,直接update不是更好,毕竟update大部分时间都是会发的。
##一般情况下,get会直接查询数据库,返回你需要的对象,当没找到对象时,返回null。
load会返回一个代理对象,当你真正用到它的时候,才查询数据库。所以当没找到对象时,抛异常。
你看到的delete时发送select,是因为load的延时性。
update方法不会发送select。你一定是用了merge方法。
update就直接用你传的pojo去update,这个pojo变为持久化对象,纳入Session的缓存管理范围。
merge才会发送select,得到一个具有相同持久化标示符的对象,纳入Session的缓存管理范围,然后把你传的pojo中的属性set进去,最后返回那个缓存中的持久态对象。
这导致update和merge有个很大的区别就是,update过的pojo,将变成持久态对象,而如果此时Session中已有相同id的持久态对象的话,update将抛异常,因为Session里,持有相同持久化标示符的对象,只能有一个。而merge过的pojo,还是个脱管态对象。如果此时Session中已有相同id的持久态对象的话,merge将不发送select语句,而直接拿这个对象来用,返回值也是它。
5)
Hibernate批量更新和批量删除批量添加 1(转)转载▼
<!-- 正文开始 -->
批量处理数据是指在一个事务中处理大量数据。以下程序在一个事务中批量更新CUSTOMERS表中年龄大于零的所有记录的AGE字段:
Transaction tx = session.beginTransaction();
Iterator customers=
session.createQuery("from Customer c where c.age>0").list().iterator();
while(customers.hasNext()){
Customer customer=(Customer)customers.next();
customer.setAge(customer.getAge()+1);
}
tx.commit();
session.close();
如果CUSTOMERS表中有1万条年龄大于零的记录,那么Hibernate会一下子加载1万个Customer对象到内存。当执行tx.commit()方法时,会清理缓存,Hibernate执行1万条更新CUSTOMERS表的update语句:
update CUSTOMERS set AGE=? …. where ID=i;
update CUSTOMERS set AGE=? …. where ID=j;
……
update CUSTOMERS set AGE=? …. where ID=k;
以上批量更新方式有两个缺点:
占用大量内存,必须把1万个Customer对象先加载到内存,然后一一更新它们。
执行的update语句的数目太多,每个update语句只能更新一个Customer对象,必须通过1万条update语句才能更新1万个Customer对象,频繁地访问数据库,会大大降低应用的性能。
一般说来,应该尽可能避免在应用层进行批量操作,而应该在数据库层直接进行批量操作,例如直接在数据库中执行用于批量更新或删除的SQL语句,如果批量操作的逻辑比较复杂,则可以通过直接在数据库中运行的存储过程来完成批量操作。
并不是所有的数据库系统都支持存储过程。例如目前的MySQL就不支持存储过程,因此不能通过存储过程来进行批量更新或批量删除。
当然,在应用层也可以进行批量操作,主要有以下方式:
(1)通过Session来进行批量操作。
(2)通过StatelessSession来进行批量操作。
(3)通过HQL来进行批量操作。
(4)直接通过JDBC API来进行批量操作。
9.4.1 通过Session来进行批量操作
Session的save()以及update()方法都会把处理的对象存放在自己的缓存中。如果通过一个Session对象来处理大量持久化对象,应该及时从缓存中清空已经处理完毕并且不会再访问的对象。具体的做法是在处理完一个对象或小批量对象后,立刻调用flush()方法清理缓存,然后再调用clear()方法清空缓存。
通过Session来进行批量操作会受到以下约束:
(1)需要在Hibernate的配置文件中设置JDBC单次批量处理的数目,合理的取值通常为10到50之间,例如:
hibernate.jdbc.batch_size=20
在按照本节介绍的方法进行批量操作时,应该保证每次向数据库发送的批量SQL语句数目与这个batch_size属性一致。
(2)如果对象采用"identity"标识符生成器,则Hibernate无法在JDBC层进行批量插入操作。
(3)进行批量操作时,建议关闭Hibernate的第二级缓存。本书的姊妹篇《精通Hibernate:高级篇》对第二级缓存做了详细介绍。Session的缓存为Hibernate的第一级缓存,通常它是事务范围内的缓存,也就是说,每个事务都有单独的第一级缓存。SessionFactory的外置缓存为Hibernate的第二级缓存,它是应用范围内的缓存,也就是说,所有事务都共享同一个第二级缓存。在任何情况下,Hibernate的第一级缓存总是可用的。而默认情况下,Hibernate的第二级缓存是关闭的,此外也可以在Hibernate的配置文件中通过如下方式显式关闭第二级缓存:
hibernate.cache.use_second_level_cache=false
1.批量插入数据
以下代码一共向数据库中插入十万条CUSTOMERS记录,单次批量插入20条CUSTOMERS记录:
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
session.flush(); //清理缓存,执行批量插入20条记录的SQL insert语句
session.clear(); //清空缓存中的Customer对象
}
}
tx.commit();
session.close();
在以上程序中,每次执行session.flush()方法,就会向数据库中批量插入20条记录。接下来session.clear()方法把20个刚保存的Customer对象从缓存中清空。
为了保证以上程序顺利运行,需要遵守以下约束。
在Hibernate的配置文件中,应该把hibernate.jdbc.batch_size属性也设为20。
关闭第二级缓存。因为假如使用了第二级缓存,那么所有在第一级缓存(即Session的缓存)中创建的Customer对象还要先复制到第二级缓存中,然后再保存到数据库中,这会导致大量不必要的开销。
Customer对象的标识符生成器不能为"identity"。
- public class MedicineDao {
- public void saveMedicines(List<Medicine> ms) {
- Session session = null;
- if (ms != null && ms.size() > 0) {
- try {
- session = HibernateUtil.getSession(); // 获取Session
- session.beginTransaction(); // 开启事物
- Medicine medicine = null; // 创建药品对象
- // 循环获取药品对象
- for (int i = 0; i < ms.size(); i++) {
- medicine = (Medicine) ms.get(i); // 获取药品
- session.save(medicine); // 保存药品对象
- // 批插入的对象立即写入数据库并释放内存
- if (i % 10 == 0) {
- session.flush();
- session.clear();
- }
- }
- session.getTransaction().commit(); // 提交事物
2.批量更新数据
进行批量更新时,如果一下子把所有对象到加载到Session的缓存中,然后再在缓存中一一更新它们,显然是不可取的。为了解决这一问题,可以使用可滚动的结果集org.hibernate.ScrollableResults,Query的scroll()方法返回一个ScrollableResults对象。以下代码演示批量更新Customer对象,该代码一开始利用ScrollableResults对象来加载所有的Customer对象:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers= session.createQuery("from Customer")
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.setAge(customer.getAge()+1); //更新Customer对象的age属性
if ( ++count % 20 == 0 ) { //单次批量操作的数目为20
session.flush();//清理缓存,执行批量更新20条记录的SQL update语句
session.clear();//清空缓存中的Customer对象
}
}
tx.commit();
session.close();
在以上代码中,Query的scroll()方法返回的ScrollableResults对象中实际上并不包含任何Customer对象,它仅仅包含了用于在线定位数据库中CUSTOMERS记录的游标。只有当程序遍历访问ScrollableResults对象中的特定元素时,它才会到数据库中加载相应的Customer对象。
为了保证以上程序顺利运行,需要遵守以下约束:
在Hibernate的配置文件中,应该把hibernate.jdbc.batch_size属性也设为20。
关闭第二级缓存。假如已经在配置文件中启用了第二级缓存,也可以通过以下方式在程序中忽略第二级缓存:
ScrollableResults customers= session.createQuery("from Customer")
//忽略第二级缓存
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
9.4.2 通过StatelessSession来进行批量操作
Session具有一个用于保持内存中对象与数据库中相应数据保持同步的缓存,位于Session缓存中的对象为持久化对象。但在进行批量操作时,把大量对象存放在Session缓存中会消耗大量内存空间。作为一种替代方案,可以采用无状态的StatelessSession来进行批量操作。
以下代码利用 StatelessSession来进行批量更新操作:
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.setAge(customer.getAge()+1); //在内存中更新Customer对象的age属性;
session.update(customer);//立即执行update语句,更新数据库中相应CUSTOMERS记录
}
tx.commit();
session.close();
从形式上看,StatelessSession与Session的用法有点相似。StatelessSession与Session相比,有以下区别:
(1)StatelessSession没有缓存,通过StatelessSession来加载、保存或更新后的对象都处于游离状态。
(2)StatelessSession不会与Hibernate的第二级缓存交互。
(3)当调用StatelessSession的save()、update()或delete()方法时,这些方法会立即执行相应的SQL语句,而不会仅仅计划执行一条SQL语句。
(4)StatelessSession不会对所加载的对象自动进行脏检查。所以在以上程序中,修改了内存中Customer对象的属性后,还需要通过StatelessSession的update()方法来更新数据库中的相应数据。
(5)StatelessSession不会对关联的对象进行任何级联操作。举例来说,通过StatelessSession来保存一个Customer对象时,不会级联保存与之关联的Order对象。
(6)StatelessSession所做的操作可以被Interceptor拦截器捕获到,但会被Hibernate的事件处理系统忽略。
(7)通过同一个StatelessSession对象两次加载OID为1的Customer对象时,会得到两个具有不同内存地址的Customer对象,例如:
StatelessSession session = sessionFactory.openStatelessSession();
Customer c1=(Customer)session.get(Customer.class,new Long(1));
Customer c2=(Customer)session.get(Customer.class,new Long(1));
System.out.println(c1==c2); //打印false
9.4.3 通过HQL来进行批量操作
Hibernate3中的HQL(Hibernate Query Language,Hibernate查询语言)不仅可以检索数据,还可以用于进行批量更新、删除和插入数据。批量操作实际上直接在数据库中完成,所处理的数据不会被保存在Session的缓存中,因此不会占用内存空间。
Query.executeUpdate()方法和JDBC API中的PreparedStatement.executeUpdate()很相似,前者执行用于更新、删除和插入的HQL语句,而后者执行用于更新、删除和插入的SQL语句。
1.批量更新数据
以下程序代码演示通过HQL来批量更新Customer对象:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlUpdate =
"update Customer c set c.name = :newName where c.name = :oldName";
int updatedEntities = session.createQuery( hqlUpdate )
.setString( "newName", "Mike" )
.setString( "oldName", "Tom" )
.executeUpdate();
tx.commit();
session.close();
以上程序代码向数据库发送的SQL语句为:
update CUSTOMERS set NAME="Mike" where NAME="Tom"
2.批量删除数据
Session的delete()方法一次只能删除一个对象,不适合进行批量删除操作。以下程序代码演示通过HQL来批量删除Customer对象:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer c where c.name = :oldName";
int deletedEntities = session.createQuery( hqlDelete )
.setString( "oldName", "Tom" )
.executeUpdate();
tx.commit();
session.close();
以上程序代码向数据库提交的SQL语句为:
delete from CUSTOMERS where NAME="Tom"
3.批量插入数据
插入数据的HQL语法为:
insert into EntityName properties_list select_statement
以上EntityName表示持久化类的名字,properties_list表示持久化类的属性列表,select_statement表示子查询语句。
HQL只支持insert into ... select ... 形式的插入语句,而不支持"insert into ... values ... "形式的插入语句。
下面举例说明如何通过HQL来批量插入数据。假定有DelinquentAccount和Customer类,它们都有id和name属性,与这两个类对应的表分别为DELINQUENT_ACCOUNTS和CUSTOMERS表。DelinquentAccount.hbm.xml和Customer.hbm.xml文件分别为这两个类的映射文件。以下代码能够把CUSTOMERS表中的数据复制到DELINQUENT_ACCOUNTS表中:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where c.id>1";
int createdEntities = s.createQuery( hqlInsert )
.executeUpdate();
tx.commit();
session.close();
以上程序代码向数据库提交的SQL语句为:
insert into DELINQUENT_ACCOUNTS(ID,NAME) select ID,NAME from CUSTOMERS where ID>1
9.4.4 直接通过JDBC API来进行批量操作
当通过JDBC API来执行SQL insert、update和delete语句时,SQL语句中涉及到的数据不会被加载到内存中,因此不会占用内存空间。
以下程序直接通过JDBC API来执行用于批量更新的SQL语句:
Transaction tx = session.beginTransaction();
//获得该Session使用的数据库连接
java.sql.Connection con=session.connection();
//通过JDBC API执行用于批量更新的SQL语句
PreparedStatement stmt=con.prepareStatement("update CUSTOMERS set AGE=AGE+1 "
+"where AGE>0 ");
stmt.executeUpdate();
tx.commit();
以上程序通过Session的connection()方法获得该Session使用的数据库连接,然后通过它创建PreparedStatement对象并执行SQL语句。值得注意的是,应用程序仍然通过Hibernate的Transaction接口来声明事务边界。
值得注意的是,在Hibernate3中,尽管Session的connection()方法还存在,但是已经被废弃,不提倡使用了,不过Hibernate3提供了替代方案:org.hibernate.jdbc.Work接口表示直接通过JDBC API来访问数据库的操作,Work接口的execute()方法用于执行直接通过JDBC API来访问数据库的操作:
public interface Work {
//直接通过JDBC API来访问数据库的操作
public void execute(Connection connection) throws SQLException;
}
Session的doWork(Work work)方法用于执行Work对象指定的操作,即调用Work对象的execute()方法。Session会把当前使用的数据库连接传给execute()方法。
以下程序演示了通过Work接口以及Session的doWork()方法来执行批量操作的过程:
Transaction tx=session.beginTransaction();
//定义一个匿名类,实现了Work接口
Work work=new Work(){
public void execute(Connection connection)throws SQLException{
//通过JDBC API执行用于批量更新的SQL语句
PreparedStatement stmt=connection
.prepareStatement("update CUSTOMERS set AGE=AGE+1 "
+"where AGE>0 ");
stmt.executeUpdate();
}
};
//执行work
session.doWork(work);
tx.commit();
当通过JDBC API中的PreparedStatement接口来执行SQL语句时,SQL语句中涉及到的数据不会被加载到Session的缓存中,因此不会占用内存空间。