`
peterwei
  • 浏览: 250386 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

@Transactional-隔离级别-二级缓存的扯谈

阅读更多
引子

今天下班后,以前同事小胖问我Spring  Service类中的注解@Transactional readOnly=true的作用。做为他眼中的高人,我自然要装下A-C。居然想都没有想就说是注解事务控制,然后给他扯了一通数据库的隔离级别,什么read uncommit之类的,说readOnly是隔离级别最低的,在spring查询方法里用,性能最高。
ps:之前的项目多数基于xml,亲自用annotation的机会很少,都是小弟们在实战。

示例:
@Component("channelService")
@Transactional(readOnly = true)
public class ChannelServiceImpl implements IChannelService {
    @Resource(name = "productService")
    private IProductService productService;

    /**
     * 根据频道名字查询频道。没有就返回NULL。
     *
     * @param name 频道名称不能为空。
     * @return (假设频道名称是加了唯一约束的,否则可能结果不止一条导致异常)
     */
    @MethodCache(expire = 3600)
    @Transactional(readOnly = true)
    public ChannelVO getChannelByName(String name) {
        if (name == null || "".equals(name.trim())) {
            throw new IllegalArgumentException("name=" + name);
        }
        ShopChannels channel = (ShopChannels) channelDao.createCriteria()
                .add(Restrictions.eq("name", name))
                .uniqueResult();
        if (channel == null) {
            return new ChannelVO();
        }
        ChannelVO vo = new ChannelVO();
        DozerMapper.getInstance().map(channel, vo);
        //增加频道对应商品分类id 只取第一个分类
        Set<ProductClass> listCates = channel.getProductClasses();
        if (listCates != null && !listCates.isEmpty()) {
            vo.setProductClass("" + listCates.iterator().next().getSid());
        }
        List<Integer> list = new ArrayList<Integer>();
        Iterator<ProductClass> iterator = listCates.iterator();
        while (iterator.hasNext()) {
            ProductClass productClass = iterator.next();
            list.add(productClass.getSid());
        }
        vo.setAllProductClass(list);
        return vo;
}
}


数据库隔离相关内容

在一个应用中,依据事务的隔离级别将会有三种情况发生。
  ◆脏读(dirty read):当一个事务读取另一个事务尚未提交的修改时,产生脏读。
  ◆ 不可重复读(non-repeatable read):同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。:
  ◆ 幻像读(phantom read):同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读。

1.Read Committed:
假设A事务对正在读取数据Data放置了共享锁,那么Data不能被其它事务改写,所以当B事务对Data进行读取时总和A读取的Data数据是一致的,所以避免了脏读。由于在A没有提交之前可以对Data进行改写,那么B读取到的某个值可能会在其读取后被A更改从而导致了该值不能被重复取得;或者当B再次用相同的where字句时得到了和前一次不一样数据的结果集,也就是幻像数据。

2.Read Uncommitted:
假设A事务即不发布共享锁,也不接受独占锁,那么并发的B或者其它事务可以改写A事务读取的数据,那么并发的C事务读取到的数据的状态和A的或者B的数据都可能不一致,那么。脏读、不可重复读、幻象数据都可能存在。

3.Repeatable Read:
(注意MSDN原文中的第一句话:在查询中使用的所有数据上放置锁,所以不存在脏读的情况)。
假设A事务对读取的所有数据Data放置了锁,以阻止其它事务对Data的更改,在A没有提交之前,新的并发事务读取到的数据如果存在于Data中,那么该数据的状态和A事务中的数据是一致的,从而避免了不可重复的读取。但在A事务没有结束之前,B事务可以插入新记录到Data所在的表中,那么其它事务再次用相同的where字句查询时,得到的结果数可能上一次的不一致,也就是幻像数据。

4.Serializable:
在数据表上放置了排他锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行,这是最严格的锁。它防止了脏读、不可重复读取和幻象数据。

以下是对照表:




一般的应用作隔离级别时,往往采用(2),(3)两种,(1),(4)两种前者轻后者重,但是Hibernate为了提高performance和scalability,在数据库一层中采用的是(2)的隔离级别,然后在程序中进行控制,从而实现了(3)的隔离级别,因此提高了企业应用的事务处理效率。当然Hibernate对于数据库一层的隔离级别也可以显示指定。Hibernate在程序中的控制方法有:version number, timestamp,对于遗留database也有optimistic-lock,而version number有时不能解决特殊的事务并发引起来的(3)问题,那么就需要针对特殊情况进行细粒度的事务控制,可以看一下LOCKMODE。

回家反思缓存策略

回家一想,不对,应该不完全是隔离级别的东西,自已可能肯定搞错了,于是赶紧翻看hibernate3.3 reference.果然没说对。应该是二级缓存的策略问题。但不知为什么reference中没有讲到query cache annotation的东西,只是一页带过。提高性能中二级缓存策略倒是讲了不少。
备忘:

Chapter 19. Improving performance
19.2. The Second Level Cache

19.2.1. Cache mappings
The <cache> element of a class or collection mapping has the following form:
<cache 
    usage="transactional|read-write|nonstrict-read-write|read-only"  
    region="RegionName"                                              
    include="all|non-lazy"                                           
/>
usage (required) specifies the caching strategy: transactional, read-write, nonstrict-
read-write or read-only
region (optional: defaults to the class or collection role name): specifies the name of the
second level cache region
include (optional: defaults to all) non-lazy: specifies that properties of the entity mapped
with lazy="true" cannot be cached when attribute-level lazy fetching is enabled
Alternatively, you  can  specify  <class-cache> and  <collection-cache>  elements  in
hibernate.cfg.xml.
The usage attribute specifies a cache concurrency strategy.

直接简要的看各种策略说明:

Strategy: read only
If your application needs to read, but not modify, instances of a persistent class, a read-only
cache can be used. This is the simplest and optimal performing strategy. It is even safe for use
in a cluster.
如果应用只需要查询,不需要修改,那么read-only缓存将使用,这是最简单、最理想的性能策略,它在集群环境下也是安全的。

Strategy: read/write
If  the  application  needs  to  update  data, a  read-write  cache  might  be  appropriate.This  cache  strategy  should  never  be  used  if  serializable  transaction  isolation  level  is required. 
如果应用需要修改,read-write cache是最合适的。这个cache策略永远不会使用,如果数据库事务隔离级别是可序列化serializable的。这里看和隔离级别又有点关系。

Strategy: nonstrict  read/write
If the application only occasionally needs to update data (i.e. if it is extremely unlikely that two
transactions would try to update the same item simultaneously), and strict transaction isolation
is not required, a nonstrict-read-write cache might be appropriate. If the cache is used in a
JTA environment, you must specify hibernate.transaction.manager_lookup_class. In other
environments, you should ensure that the transaction is completed when Session.close() or
Session.disconnect() is called.
如果应用仅仅偶尔的修改数据,并不需要严格的数据库隔离级别。Nonstrict-read-write 缓存是最合适的。

Strategy: transactional
The transactional cache strategy provides support for fully  transactional cache providers such
as JBoss TreeCache. Such a cache can only be used in a JTA environment and you must specify
hibernate.transaction.manager_lookup_class.
The transactional cache strategy提供完全的事务缓存机制,例如JBoss TreeCache.这样的缓存只能用在JTA环境中,你也必需指定相关类。

Important
None of the cache providers support all of the cache concurrency strategies.
The following table shows which providers are compatible with which concurrency strategies.




回归正题@Transactional

再想想,还是不对,hibernate中并没有提到最开始annotation的东西,于是继续纠结Spring3.0 Reference Documentation。

Using @Transactional
In addition to the XML-based declarative approach to transaction configuration, you can use an
annotation-based approach. Declaring transaction semantics directly in the Java source code puts the declarations much closer to the affected code. There is not much danger of undue coupling, because code that is meant to be used transactionally is almost always deployed that way anyway.
annotation事务控制,代替xml的。其它废话少翻。
You can place the @Transactional annotation before an interface definition, a method on an Interface, a class definition, or a public method on a class. However, the mere presence of the
@Transactional annotation is not enough to activate the transactional behavior. The
@Transactional annotation is simply metadata that can be consumed by some runtime infrastructure that is @Transactional-aware and that can use the metadata to configure the appropriate beans with transactional behavior. In the preceding example, the <tx:annotation-driven/> element switches
onthetransactionalbehavior.

//文档例子:
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}


@Transactional  settings
The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics; for example, “start a brand new read-only transaction when this method is invoked。
@Transactional 注解是规定接口,类或者方法必需应用事务的元数据语法。

看看spring默认的设置:
//默认传播机制:PROPAGATION_REQUIRED
* Propagation setting is PROPAGATION_REQUIRED.
//默认隔离级别:ISOLATION_DEFAULT
* Isolation level is ISOLATION_DEFAULT.
//默认事务是read/write.到此知道查询配readOnly的作用。
* Transaction is read/write.
//事务默认超时时间取决于事务系统,也有可能没有,如果事务系统不支持。
* Transaction timeout defaults to the default timeout of the underlying transaction system, or to none if time outs are not supported.
//任何的RuntimeExcetipn将触发回滚,任何的checkedException不触发回滚。
* Any RuntimeException triggers rollback,and any checkedException does not.


明细配置:








花了一个小时整理这混乱的东西,小胖快来看吧。哈哈。思路混乱,大家能看就看,想拍就拍。只是为我和小胖两人理解。大家有想法,欢迎一起扯淡。
  • 大小: 42.1 KB
  • 大小: 29.9 KB
  • 大小: 25.5 KB
  • 大小: 59.2 KB
分享到:
评论
16 楼 peak 2011-04-12  
@Transactional 灵活性更好,以前都是xml配置,发现对不同的情况要设置各种各样的切点和策略,麻烦死了,而且别人看起来也是晕晕的。
15 楼 caoyangx 2011-04-11  
个人猜想ReadOnly=true,也就是不让Spring的事务管理为Connection调用Commit或Rollback而已,或者将IsReadOnly=true,不过Connection的AutoCommit=true了,设置了ReadOnly=true估计也不能提交。
14 楼 peterwei 2011-04-10  

引用
数据缓存策略失效

org的意思应该是另外的非hibernate管理的JDBC对数据库进行了增删改查的东西。导致缓存失效。
引用
内部数据锁机制失效

这个要org来回答
引用
引用
但对数据要求不严格的时候,还是会使用他——比如给外部做查询服务,结果只是看看,不做业务数据的。

什么叫做对数据要求不严格的时候呢??
什么叫做不做业务数据的??

org的意思是只是查查看,数据准确性要求不高。比方说一个统计功能,缓存里可能有3000个量,但实际数据库有3050个。不过也无所谓,大概对就行。
13 楼 liwenjie 2011-04-10  
skzr.org 写道
感觉Hibernate二级缓存用在一个生产系统,特别是有现金流或者物品交换的系统中,可能会带来隐藏问题。

  • 如果全部使用hibernate,只要不是集群基本问题不大。
  • 现实中,生产环境中还是存在直接使用jdbc,这个就导致一些针对hibernate管理的数据缓存策略和内部数据锁机制失效。
  • 呵呵,我现在只用hibernate进行crud,完全不使用二级缓存,性能问题时,根据场景自己做应用级的缓存。
  • 当然了,但对数据要求不严格的时候,还是会使用他——比如给外部做查询服务,结果只是看看,不做业务数据的。


现实中,生产环境中还是存在直接使用jdbc,这个就导致一些针对hibernate管理的数据缓存策略和内部数据锁机制失效。

说的好!

引用
数据缓存策略失效
我的理解是因为和jdbc在同一个事务,连接未关闭,未保存到数据库中??为了让jdbc查到数据,必须手动flush session

引用
内部数据锁机制失效
这个不了解了

引用
但对数据要求不严格的时候,还是会使用他——比如给外部做查询服务,结果只是看看,不做业务数据的。

什么叫做对数据要求不严格的时候呢??
什么叫做不做业务数据的??

请赐教
12 楼 skzr.org 2011-04-09  
感觉Hibernate二级缓存用在一个生产系统,特别是有现金流或者物品交换的系统中,可能会带来隐藏问题。

  • 如果全部使用hibernate,只要不是集群基本问题不大。
  • 现实中,生产环境中还是存在直接使用jdbc,这个就导致一些针对hibernate管理的数据缓存策略和内部数据锁机制失效。
  • 呵呵,我现在只用hibernate进行crud,完全不使用二级缓存,性能问题时,根据场景自己做应用级的缓存。
  • 当然了,但对数据要求不严格的时候,还是会使用他——比如给外部做查询服务,结果只是看看,不做业务数据的。


曾经解决过因误用一个问题:
  • 环境:开启Open session in view
  • 问题:某个数据对象经常被误更新,莫名其妙
  • 原因:po到view后,view为了处理方便,把数据规整了下,但是此时po还在受管状态(因为Open session in view),所以数据库也被更改了。
  • 最后:规定所有po都不允许在业务处理之外进行修改。

在使用时需要特别小心。
11 楼 peterwei 2011-04-08  
yanwt 写道
楼主现在在哪家公司啊?

私下和你说。
10 楼 yanwt 2011-04-08  
楼主现在在哪家公司啊?
9 楼 beyondsphinx 2011-04-08  
chrislee1982 写道
个人认为还是不要使用注解定义事物的好,每个service下的类都这样定义一下,多累啊!

还不如使用XML配置的好,几句就搞定!

@Transactional 注解事务是可以被继承的
8 楼 icanfly 2011-04-08  
好像跟缓存是没有关系的,好像是数据库driver级别有关系,我也是像楼上一样好像在哪里看过
7 楼 finallygo 2011-04-08  
peterwei 写道
rocketball 写道
我有一个不理解的地方,如果设置了ReadOnly=true,那还是什么设置事物的必要,不是增加额外的开销麽

但是spring默认是read-write啊,也就是readonly=false。只有在save,update有数据更改的时候才有用。readonly的话级别低,一般配一些query的方法,性能肯定高的。这是我的理解,不一定对。

他的意思是,如果只是查询的话,那就不用对这些方法进行事务拦截了,效率自然比做事务要高一些
我在问答中有看到有人问这个readonly问题,好像这个与orm框架还有数据库驱动有关系
6 楼 rocketball 2011-04-08  
chrislee1982 写道
个人认为还是不要使用注解定义事物的好,每个service下的类都这样定义一下,多累啊!

还不如使用XML配置的好,几句就搞定!

肯定是注解省事,注解可以有选择的加。也可以根据业务层不一样选择不同的事物级别
5 楼 rocketball 2011-04-08  
peterwei 写道
rocketball 写道
我有一个不理解的地方,如果设置了ReadOnly=true,那还是什么设置事物的必要,不是增加额外的开销麽

但是spring默认是read-write啊,也就是readonly=false。只有在save,update有数据更改的时候才有用。readonly的话级别低,一般配一些query的方法,性能肯定高的。这是我的理解,不一定对。

注解的话,在配置中加上AOP,这时可以Transactional放到每一个Public方法上面,这就就没必要把整个类都设置成Transactional,对于不需要加事物的方法,就不用加Transactional了
4 楼 chrislee1982 2011-04-08  
个人认为还是不要使用注解定义事物的好,每个service下的类都这样定义一下,多累啊!

还不如使用XML配置的好,几句就搞定!
3 楼 无根V稻草 2011-04-08  
学习了,对spring了解很浅,只知道一般对查询来说要设置readonly=true
2 楼 peterwei 2011-04-08  
rocketball 写道
我有一个不理解的地方,如果设置了ReadOnly=true,那还是什么设置事物的必要,不是增加额外的开销麽

但是spring默认是read-write啊,也就是readonly=false。只有在save,update有数据更改的时候才有用。readonly的话级别低,一般配一些query的方法,性能肯定高的。这是我的理解,不一定对。
1 楼 rocketball 2011-04-08  
我有一个不理解的地方,如果设置了ReadOnly=true,那还是什么设置事物的必要,不是增加额外的开销麽

相关推荐

Global site tag (gtag.js) - Google Analytics