Hibernate学习笔记(二)

一、修改一条记录

    更新数据库中的一条记录有两种方式:

    ① 加载持久化对象,修改对象属性,然后session清理缓存的时候会自动同步更新数据库中的数据。

    ② 更新游离对象,通过session的update方法更新数据库中的相应数据。


二、Hibernate与数据库触发器协同工作的问题及解决办法

    问题1:触发器使session缓存中的持久化对象与数据库中数据不一致。

    解决方法:执行完持久化操作后马上调用session的flush方法将改变同步到数据库,然后调用refresh方法重新加载持久化对象。

    问题2:session的update方法盲目激发触发器。

    产生问题的原因:使用update或者saveOrUpdate方法更新游离对象时,session无法判断对象属性是否和数据库中一致,为保险起见,session总是会将游离对象的属性更新到数据库中。实际上,如果游离对象的属性和数据库中是一致的,这条update语句就是多余的,触发器也不应该被激发。

    解决方法:在映射文件的<class>元素中设置select-before-update属性为true,从而在session的update和saveOrUpdate方法更新一个游离对象的时候,先执行查询语句,比较游离对象的属性和数据库是否一致,不一致的时候才执行更新语句。这样会避免盲目激发触发器,但是会有一定程度的效率损失。


三、批量处理数据

    一般说来,应该尽量避免在应用层进行批量操作,而应该在数据库层直接批量操作。如果批量操作的逻辑比较复杂,则应该通过运行存储过程来完成。

    在应用层进行批量操作主要有以下方式:

    ① 通过session批量操作

    通过session执行批量操作具有以下约束。

    1)需要在Hibernate配置文件中设置单次批量处理的数目,合理的取值通常是10~50。如 hibernate.jdbc.batch_size=20,并且保证每次向数据库发送的批量SQL语句数目与这个batch_size一致。

    2)如果采用identity标示符生成器,则hibernate无法在jdbc层进行批量插入操作。

    3)批量操作时,建议关闭hibernate二级缓存。


    批量插入数据的部分代码如下:

for(int i=0;i<100000;i++){
    Customer c = new Customer(...);
    session.save(c);
    if(i%20==0){
        session.flush();
        session.clear();
    }
}
    如前文所述,为保证上述代码顺利执行,需要遵循以下约束:hibernate.jdbc.batch_size=20;Customer对象标示符生成器不是“identity”;关闭二级缓存。


    批量更新数据的部分代码如下:

ScrollableResults customers = session.createQuery("from Customer").scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while(customers.next()){
    Customer c = (Customer) customers.get(0);
    c.setAge(c.getAge() + 1);
    if(++count % 20 == 0){
        session.flush();
        session.clear();
    }
}
    上述代码中,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);

    ② 通过StatelessSession进行批量操作

    主要代码如下:

StatelessSession session = SessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();

ScrollableResults customers = session.createQuery("from Customer").scroll(ScrollMode.FORWARD_ONLY);
while(customers.next()){
    Customer c = (Customer) customers.get(0);
    c.setAge(c.getAge() + 1);
    session.update(c);
}

tx.commit();
session.close();
    StatelessSession与session用法相似,二者主要区别如下:

    1)前者没有缓存,通过StatelessSession加载、保存、更新的对象处于游离状态。

    2)前者不会与二级缓存交互。

    3)调用前者的方法时,sql语句立即执行,而后者需要等到清理缓存的时候才执行。

    4)前者不会进行脏检查,所以上述程序中修改内存中Customer对象的属性之后,还要调用update方法更新数据库中相应数据。而后者得到的是持久化对象,修改对象属性之后,hibernate会自动在session关闭前清理缓存同步数据。

    5)前者不会对任何关联对象进行级联操作。

    6)前者所做操作可以被Interceptor拦截器捕捉到,但是会被hibernate事件处理系统忽略。

    7)前者两次加载主键相同的对象时,获得的是两个内存地址不同的对象,用“==”比较获得的是false。


    ③通过HQL批量操作

    通过Query.executeUpdate方法批量更新或删除数据,自定义HQL语句做方法参数。


    ④通过jdbc API批量操作

    hibernate3中通过session.connection()获得数据库连接,之后可以通过jdbc API进行数据库操作。但是此方法已经被废弃,hibernate4中甚至已经删除了此方法,应该采用如下方法获取connection:

session.doWork(new Work() {
	@Override
	public void execute(Connection connection) throws SQLException {
		// TODO Auto-generated method stub
	}
});

    ⑤hibernate调用存储过程

    hibernate没有专门的接口调用存储过程,在需要的时候先获得connection,然后通过jdbc API调用存储过程。


四、Hibernate检索策略

检索策略作用域可选策略默认值受影响的检索方法
类级别立即检索
延迟检索
延迟检索session的load()方法
关联级别立即检索
延迟检索
迫切左外连接检索
延迟检索session的load()和get()方法,以及Query API和Criteria API。
例外情况是Query API会忽略映射文件中设定的迫切左外连接检索策略。

    ①类级别的检索策略

    如果程序加载一个持久化对象的目的是为了访问他的属性,可以使用立即检索。如果加载一个持久化对象的目的仅仅是获得它的引用,可以使用延迟检索。如下是适用延迟检索的情况:

Customer c = (Customer)session.load(Customer.class,new Long(1));
Order o = new Order();
o.setCustomer(c);
session.save(o);

    ②迫切左外连接

List result = session.createQuery("from Customer c left jion fetch c.orders o where c.name like 'T%'").list();
    使用迫切左外连接检索策略时,Query的list()方法返回的集合中存放Customer对象的引用,每个Customer对象的orders集合都被初始化,存放所有关联的Order对象。使用迫切左外连接检索策略时,查询结果中可能包含重复元素,可以通过HashSet进行过滤:

HashSet set = new HashSet(result);

    说明:迫切连接会使延迟加载失效;左外连接、右外连接以及内连接的查询结果可能会包含重复对象;左右外连接情况下主表有记录,次表没有记录的时候次表返回的对象为null。内连接情况下,只要有一方的表中没有记录,相应的另一方也不会出现在查询结果中。也就是内连接查询出的两个关联的对象都不为null,如果有一方为null,这条记录就不会出现在查询结果中了。


五、Hql方式检索一条记录

    如果Hql的查询结果只有一条记录,调用Query的uniqueResult()方法,该方法返回一个Object对象。如果查询结果包含多条记录,但是只需要抽取其中的一条使用,可以在调用uniqueResult()方法前先调用setMaxResult(1)方法限定只返回一条记录。


六、集合过滤

    一对多关系中多的一方通常是保存在set中,不利于排序、筛选、分页等需求。可以使用hibernate的集合过滤功能解决此问题。

List result = session.createFilter(customer.getOrders(),"where this.price>100 order by this.price").list();
    Session的createFilter()方法用来过滤集合,它具有以下特点:

①返回Query类型的实例。

②它的第一个参数指定一个持久化对象的集合。这个集合是否已经初始化没有关系,但是它所属的对象必须是持久化对象,也就是上述代码中customer对象必须是持久化对象。

③它的第二个参数指定过滤条件,它由合法的HQL查询语句组成。

④无论持久化对象的集合是否已经被初始化,Query的list()方法都会执行SQL查询语句,到数据库中检索Order对象。如果orders集合已经初始化了,Query的list()方法不会新建Order对象,仅返回已存在的Order对象的引用。如果orders集合没有初始化,Query的list()方法会创建相应的Order对象返回对它们的引用,但是不会初始化Customer对象的orders集合。


七、处理并发问题

    悲观锁的实现方式:

    1、在应用程序中显式指定采用数据库系统的独占锁来锁定数据资源。

select... for update
    Hibernate中使用get()和load()方法加载对象可以采用如下方式声明使用悲观锁:

Account a = (Account) session.get(Account.class,new Long(1),LockMode.UPGRADE);
    2、在数据库的ACCOUNTS(对应Account类)表中增加lock(锁)字段,可以是boolean类型,“true”表示锁定状态,“false”表示空闲状态。当一个事务先查询ACCOUNTS表中的ID为1的记录,然后再修改这条记录的时候,按以下步骤执行:

    (1)根据lock字段判断这条记录是否处于空闲状态。

    (2)如果处于锁定状态就一直等待,直到这条记录变成空闲状态。如果需要也可以撤销事务,抛出异常,通知客户系统正忙,稍后再执行该事务。

    (3)如果记录处于空闲状态,就把lock字段改为“true”,锁定这条记录。

    (4)更新这条记录,并把lock字段改为“false”,解除锁定。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值