开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Transaction 2 】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/75/detail/15832
Transaction 2
内容介绍:
一、Managing Transactions in Spring
二、Database Locks
三、Transaction Isolation Levels
四、Controlling isolation levels
五、Updating Multiple Databases
一、Managing Transactions in Spring
Transactional 在一个事务里执行,接收一个字符串类型的数组,叫做可变常数组的参数,可以传给若干个字符串,对于数组里的每一个数组都把当成一个人,往数据库里面插入一条记录,插入的就是 person 的内容,它有一个名字,把替换成值,插入到数据库里面,数据库里面有一个叫 bookings 的表。
bookings 表有 first_name,有 id,自动生成,有约束,长度只能是5,把拿到的人一个一个的插入到表里面,有query方。
写一个命令行 runner,是命令行里面运行的一个程序,run 调 bookingservice.book,传递进去,alice,bob,carol,五个字符,在 book 方法里都不会违反小于等于五个字符的约束,都可以到数据库里,用一个断言判断,如果调findAllBookings 会返回所有里面已经预定人的 first_name,因为前面插入三条,所以 Size一定等于三,再尝试处理,但是要注意,现在处理会抛异常,book 现在调用的是 chris 和 samuel,chris 是满足条件的,能够写入到数据库里面,但是 samuel 是六个字母,所以写不到数据库,但是 book 方法是对于 person 里的所有人是一个事务,在一个事务里执行,所以因为 samuel 不行,整个事务抛异常,下面可以打印出消息,是什么原因,但是 samuel不成功,chris 也不会成功,所以 chris 满足要求,只有五个能插入,但是因为它俩在一个事务中执行,所以 chris 进不去。
在执行完前面的操作之后,再看里面有哪些已经订阅的人,看不到 chris 和 samuel,samuel 违反约束,而chris是因为事务回滚,跟 samuel在同一个事务里插入,回滚,所以没有被插入到数据库里,很明确的告诉在sping里面,现在事务是如何执行,再预定一个,再传一个buddy,第二个为空,会看到现在仍然会抛异常,仍然是空,没法处理,因为空和 buddy 在一个事务里执行,仍然看不见 buddy,null 违反数据库约束,而 buddy 在同一个事务里执行,所以回滚,可以看到 alice,bob,carol,不是回滚,看不见 chris 和 samuel,samuel 违反数据库约束,chris回滚,下面为 buddy 和空做操作,但是下面因为异常抛出,null 因为违反数据库约束,而 buddy 是因为在同一事务里回滚,执行这么多操作只有前面的三个成功,关键在于每一次调 Book 的时候都是默认是在一个事务里执行,调三次,第一次的三个人在一个事务里,整个提交三个人都在,后面两次都因为有一个人,每一次都传两个人,但都有一个人不符合要求,违反约束,所以导致另外一个人一起都没有被插入进去,告诉事务的用处,数据库的脚本,pom文件,spring-boot-starter-data-jdbc,从 spring 官网下载的例子,很明显的告诉什么叫事务控制以及事务如何提交
或者回滚,原子性的问题,Atomic op 事务的边界在哪里,从哪里开始到哪里结束,第二个问题是事务的隔离性问题。
@Transactional isolation
事务隔离级别 |
说明 |
@Transactional(isolation = Isolation.READ_ UNCOMMITTED) |
读取未提交数据(会出现脏读,不可重复读), 基本不使用 |
读取未提交数据(会出现脏读,不可重复读), 基本不使用 |
读取已提交数据(会出现不可重复读和幻读) |
@Transactional(isolation = lsolation.REPEATABLE_ READ) |
可重复读(会出现幻读) |
@Transactional(isolation = Isolation.SERIALIZABLE) |
串行化 |
解决三个问题,脏读问题,不可重复读问题,幻读问题。第一个级别是指三个问题都存在,第二个级别是把脏读问题解决掉,剩下两个问题不存在,不可重复读,幻读问题存在,串行化是三个问题都解决掉,串行化意味着根本没有并发,也意味着性能极差,虽然安全性很高,但是性能极差,所以事务隔离级别设的越高,如果定义越高,数据的一致性,可靠性越好,但是数据库被访问的效率会越来越低,通常可能在 read_committed 级别上比较多,其它的都不太会出现,可能中间两个用的比较多,另外两个不太会用,因为第一个太不安全,最后一个性能太差。
在 spring 里面有描述,用 Transactional 标签描述。
@Transactional propagation 传播
事务传播行为 |
说明 |
@Transactional(propagation=Propagation.REQUIRED) |
如果有事务,那么加入事务,没有新建一个(默认情况) |
@Transactional(propagation=Propagation.NOT SUPPORTED) |
容器不为这个方法开启事务 |
@Transactional(propagation=Propagation.REQUIRES_ NEW) |
不管是否存在事务,都创建一个新的事务,原来的挂起,新的 |
@Transactional(propagation=Propagation.MANDATORY) |
执行完毕,继续执行老的事务 |
@Transactional(propagation=Propagation.NEVER) |
必须在一个已有的事务中执行,否则抛出异常 |
@Transactional(propagation=Propagation.SUPPORTS) |
必须在一个没有的事务中执行,否则抛出异常(与 |
数据库隔离是靠数据库加锁实现的,有两个分离的客户端要访问同一个数据库,执行的代码是访问数据库,做 select 操作,得到里面的结果,假设两个客户端执行的方法,定义的事务属性。访问同一个数据库,通过自己的方法访问,两个方法的事务属性都是required,要求必须在数据库执行,客户端a和客户端B都要调用 listavailablecabins 方法,预定 book,预定船里面的仓位,邮轮仓位,先把仓位里面现有的船的仓位拿出来,在上面再买一个,所以要告诉订多少个床,让数据库里提交的结果做处理,一个动作,如果有两个客户端在执行会出现什么情况,都要在事务里执行,事务都是 require,看上面有哪些船,船舱可以定,正在看时有另外一个事务过来也要做订票的动作,现在有哪一些船舱可以用,订两张票,订两张床,能不能订到,在做定的操作,但是操作刚开始定,比如船上现在有两张床位要开始定,在没有支付完钱,两张床还没有给,问问能不能订到两张床位,于是数据库里现在的二还没有分配给 client1,于是告诉可以,手速比较快,于是把事务提交掉,client1犹豫半天,比较慢,或者是因为网络的原因,当最后付款的时候两张床位已经被client2预定,所以事务只能回滚,脏读问题,现在有没有两张床,是 client1还没有提交的数据,被 client2读到,读到之后会产生问题,为了解决问题就要锁住。数据库里的数据,读走的数据直接被解决掉,所以没办法再操作,数据被读着,事务还没有提交的时候被别人给改写掉。
第二个问题是不可重复读问题,现在有没有两张床,发现有两张,做动作,注意前面是表示时间的顺序,把床位给设成三张,因为有人可能释放了一张床位,现在可以设置三张,做一系列动作之后,决定要定两张床,为了保险期间再看看有没有两张床,于是会觉得选哪三张,不可重复读是要看的东西,没有改,但是在数据库里面插入符合搜索条件的记录,每一次看都会看到不同的符合条件的记录,也不知道基于谁做相应的后续操作。想读的数据加锁,锁住了,不能改但是可以插入新的数据,数据也符合的搜索条件,所以每一次搜的时候会发现里面数据会在增多增多,变化,所以不知道该如何办。
幻读问题,开启一个事务,有两张床,做操作,读走数据,进行订阅的动作,把数据给写进去,会有订阅的消息写进去,在客户端执行了一部分之后,下订单,左边操作跟之前的没有区别,隔一段时间之后,下两个订单,右边在里面可能改写了一个数据,数据会使右边数据发生变化,幻读问题是做了操作,事务提交完之后,会发现在里面,一开始确实读到一些数据,后来发现数据没了,或者会发现增加,两个方向都会有,第一个问题是读到事务,读走的数据,并且人正在进行修改,把中间状态给改掉,第二个是把数据给读死,但是不读,可以往里面插入符合条件数据,会发现数据在不断的变多,幻读问题是当里面查询条件的时候,事务的数据有,但是真正做操作的时候,可能又回滚掉。
二、Database Locks
1、Read locks
Read locks prevent other transactions from changing data read during a transaction until the transaction ends, thus preventing nonrepeatable reads.
Other transactions can read the data but not write to it. The current transaction is also prohibited from making changes.
2、Write locks
Write locks are used for updates. A write lock prevents other
transactions from changing the data until the current transaction is complete but allows dirty reads by other transactions and by the current transaction itself.
In other words, the transaction can read its own uncommitted changes.
一个事务在读走之后的数据,在没有提交之前允不允许别人看,如果允许别人看会有脏读问题,在上面进行加锁,在数据库里有数据,原始数据被客户端读回来,变成a‘,在中间状态修改的时候,别人读数据仍然只能读到A不能读到a‘,别人在开一个事务再进行处理 a 的数据,处理成‘的时候,数据还没有,如果让别人读到会出问题,a的账户是十块钱,读走之后要做转账操作,变成十五块,如果十五块能够被别人读到,别人想做转账操作也是转五块,读到15块会转个20块,因为某种原因事务不是提交,而是 roll back,因为会把15中间状态读走,加锁操作变成20,所以写回来的时候变成20,全局一个 rollback,相当于做一次转账操作,里面应该是15而不是20,所以会发现数据确实是有问题,不能够读别人,正在处理的东西,会在数据库上加一个锁,告诉别人在事务没有结束,不管提交还是回滚之前都没有办法把数据中间状态给读走,只能读走不能写,只能读走现在里面状态十块钱,不能把15直接给写回来,一直要到事务回滚或者提交掉之后才能做动作,事务把十读走,做转账操作变成15,在没有结束之前,用户想把a十块钱,比如加五块钱,做转账五块钱的操作,写不回,因为事务在读的时候,加了锁,事务在读十回来的时候,只能读不能改,防止在改,所以最多把十读走,想改成15,往回写,写不了,因为是锁着的,如果是rollback把锁放开,15可以写进来,没问题,如果15不进行 rollback,commit 变成15,把锁释放掉,得到锁,但是在提交15的时候,不能提交,因为当时读走的全是十块钱,现在已经变成15,要写回的时候检测出读走的值和现在的值是不一样,是基于一个已经旧的值在做转账的操作,加五块钱,过时的值,所以不允许写回,加锁加上事务提交时比较事务里面的当前值和事务读走之后的值,不会有两个事务在一起写一个数据,还能检测到有一些事务读取过,右边事务,想把数据写回的时候,在它之前已经有人把数据库的值给改写掉,可以检测出来,读锁要做的事情,写锁是一直到当前的事务完成是不允许其它的事务修改表,但是允许其它的事务读当前的状态,在事务读完之后,不允许往回写,写锁比读锁弱一点,不能把数据写回,和读一样。但是当事务在读的时候还是可以读到未提交的状态,在防止不可重复读对的问题,自然也没有脏读问题,防止可以读到别人没有提交的修改,但是不让写,会发现有问题,也是会读到别人的提交问题,但是基于做的修改是没有办法写的,所以也可以保证事务的隔离。
3、Exclusive write locks :
Exclusive write locks are used for updates. An exclusive write lock prevents other transactions from reading or changing the data until the current transaction is complete.
It also prevents dirty reads by other transactions.
独占锁,如果有一个事务读数据,其它的数据既不能读,也不能改数据,独占,把数据锁住,只能自己能读能写,其他人不能读不能写。
4、Snapshots
A snapshot is a frozen view of the data that is taken when a transaction begins. Some databases get around locking by providing every transaction with its own snapshot.
Snapshots can prevent dirty reads, nonrepeatable reads, and phantom reads. They can be problematic because the data is not real-time data; it is old the instant the snapshot is taken.
把数据做成视图,视图冻结住,在别人在读数据的时候,只能读当前的数据,只有当前的事务可以做相应的处理,锁的机制是数据库里面。
三、Transaction Isolation Levels
四个隔离级别。
1、Read Uncommitted
The transaction can read uncommitted data (i.e., data changed by a different transaction that is still in progress).
Dirty reads, nonrepeatable reads, and phantom reads can occur.
Bean methods with this isolation level can read uncommitted changes.
可以读到对方事务里面还没有提交的数据,如果没有提交数据的事务,最后回滚,意味着当前的事务基于操作完全没有意义,因为数据压根不存在,回滚掉了。
2、Read Committed
The transaction cannot read uncommitted data; data that is being changed by a different transaction cannot be read.
Dirty reads are prevented; nonrepeatable reads and phantom reads can occur.
Bean methods with this isolation level cannot read uncommitted data.
不能读没有提交的数据,只有提交的数据才可以读,不会读到一些压根不存在的数据,没有加任何锁,只是对当前正在操作数据,数据库没有提交意味着正在开始,不是在客户端,cache 持久化到硬盘上,commit只有提交掉才能读到正在处理的中间状态。如果只存数据,别人不是在改写读走的数据,而是在数据库里面直接插入其它想要符合条件的记录。
3、Repeatable Read
The transaction cannot change data that is being read by a different transaction.
Dirty reads and nonrepeatable reads are prevented; phantom reads can occur.
Bean methods with this isolation level have the same restrictions as those in the Read Committed level and can execute only repeatable reads.
可以重复读,在表上面做一些写的操作,另外一个 transaction 即使不做写操作,只是读,也要加一个锁上去,让其它的数据不能做修改,不能往表里面做相应的插入动作,进行操作的时候,数据现在被拿走进行操作,另外一个事务在里面,只能读到提交的事务数据,提交的数据写入到硬盘里才可以,没有办法直接进去读,不可重复读的问题,设置成隔离级别之后的样子。
设置成 Repeatable Read,数据只读,没有办法改写的值,只是读,不更新里面的内容,也不让其它的事务在里面进行改写,在后面插入新的值,再读时会发现还有不同条件出现,加锁,把表锁住。
4、Serializable
The transaction has exclusive read and update privileges; different
transactions can neither read nor write to the same data.
Dirty reads, nonrepeatable reads, and phantom reads are prevented.
This isolation level is the most restrictive.
不但要锁表,还要把所有的数据都锁住,隔离级别会更高,加锁机制更强,性能更差
四、Controlling isolation levels
1、You to specify the transaction isolation level using the
database's API.
2、For example:
DataSource source = (javax . sql .DataSource)
jndiCntxt . lookup("java: comp/ env/jdbc/titanDB");
Connection con = source . getConnection( );
con. setTransactionIsolation(Connection. TRANSACTION_ SERIALIZABLE);
如何做隔离级别的设置,begin 和 commit rollback 再划分事务的边界,没有看到过 isolation,无论是source方式还是其他方式,在一个数据源上获取连接的时候,在连接上可以设事务隔离级别,隔离级别设的四种之一,用spring可以在声明文件里面设,TRANSACTION_ SERIALIZABLE 对性能的损耗非常大,力度非常强的锁,把数据库表锁死。
五、Updating Multiple Databases
做转账操作,X 是中国工商银行的数据库,y 是中国银行的数据库,事务控制该如何控制,事务是在两个数据库服务器,即便是在同一台数据库服务器上,比如都是 orade,建两个 schema,两个库,也有同样的问题,两个库俩个内存是隔离的,它有它的缓存,在缓存事务执行的中间状态,从中国工商银行的账户里面存一笔钱,中国银行的账户里面取一笔钱,在中国工商银行的账户里面存一笔钱,在中国银行的账户里面存钱时是操作的缓存,在工商银行账户里面取钱的时候,提交或者回滚事务,它俩之间不通,左边告诉可以提交,右边提交不了,必须回滚,但是它俩之间又不通,所以要做判断是否可以提交,分布式事务是在事务当中涉及到对两个数据库的操作,两个资源管理器之间,数据库是 jms sever,在数据库里写数据之后再邮件到服务器上顺便发个邮件出去,也是一个事务,事务对两个资源管理器进行操作,只要是对两个或者两个以上的资源管理器操作,分布式事务的问题在于资源管理器之间不能直接通信,整个事务提交还是回滚,要靠代码进行实现。
一个 bean 操作两个数据源,另外一个操作第三个数据源,整个在一个事务里面执行,靠事务的propagation传播六个事务属性之一进行事务的传播,导致它俩在一个事务执行,涉及到三个数据源的交互,只要有一个不能成功,其它都成功,也要让整个事务进行回滚。
两阶段提交协议,在第一个阶段是问所有的资源管理器,不再说是数据库,第一个阶段叫准备阶段,能不能提交,准备,提交,如果 unprepared 没准备好,会给两边都发 rollback,都回滚,可以保证事务是一致的,不会出现一个提交,一个没提交的情况,如果上面提交下面没提交,不满足事务的原则性,变成一部分操作提交,一部分操作没提交,不管是否顺备好,做出一个决策提交或者回滚,发请求出去,不管是提高还是回滚,比如工商银行正确的做一个动作,给下面发的时候网络断了,永远发不过,下面收不到,准备好等着第二个动作过来,等不到,因为网络断了,事务上有 timeout,要么提交要么回滚,把债务资源释放掉,准备好认为能提交或者告诉没准备好认为不能提交,做一个猜测,有50%的概率猜错,比如没提交好回滚,猜测的概率比较小,如果准备好,经过处理做出的决策是rollback,结果 rollback 发不过来,按照自己的想法提交,于是猜错,会有50%的概率会猜错,一旦猜错,事务就不完整了,两阶段提交协议也可能会碰上这样的问题,在第二阶段的时候再有一方通信或者几方通信,断掉,最终提交或者回滚动作完全是基于自己的想法,做一个启发式的动作,动作有一定概率失败猜错,数据不完整,人工干预,相当于拿着一张银行卡到提款机上,比如在中国银行提款机上插入一张工商银行的卡,再进行取钱发现涉及到两个数据库,发现有一条断了,发现的钱被扣,但是钱没有吐出来,会打印一个条,几点几分钱没提出来,拿着条找银行,银行会处理,程序没办法自动处理,启发动作,也不知道要人检查,两阶段提交协议里面解决不了的问题,潜在有缺陷。
Concurrency
Since we use O/R mapping, and it is an offline way to access database, we should manage the concurrent access to data.
The core is how to prevent confliction between business
transactions.
Locking is the solution
What kind of lock?
事务要处理并发,有大量的事务在并发的执行,如何做隔离以及如何划分事务边界,如何加锁。
Optimistic Offline Lock
Optimistic Offline Lock solves this problem by validating that the changes
about to be committed by one session don't conflict with the changes of another session.
加锁分两种,一种叫乐观锁,有数据库,有两个会话都在操作数据,并且都在操作129的客户,martin读走数据,在进行编辑的过程当中,david 也读走数据,david 也进行编辑,david 的手快先提交,等martin在提交时会发现现在提交的用户数据,在读走的那一刻和现在数据库里的数据不一致,基于一个陈旧的数据在做修改,所以事务只能回滚,没有在 martin 读走129之后,没有对129做锁定的动作下产生的问题,乐观锁是如果对129做写操作,让david读不走129,性能太差,如果系统里面大量的操作都是读操作,为什么不让 david 读走,里面都在写,乐观是认为写冲突,发生的比较少,所以不应该这样写,martin 读走之后,davi 仍然能把它读取,仍然能写,在数据库里不加锁,在当前的场景下产生冲突的概率比较小,大量操作都是读操作,概率小,加一个乐观锁,只要检测出来martin写回数据的时候,当时读走的129状态和系统的129状态不一样就可以。
Optimistic Offline Lock- How It Works
An Optimistic Offline Lock is obtained by validating that, in the
time since a session loaded a record, another session hasn't
altered it.
It can be acquired at any time but is valid only during the system transaction in which it is obtained.
Thus, in order that a business transaction not corrupt record data it must acquire an Optimistic Offline Lock for each member of its change set during the system transaction in which it applies changes to the database.
The most common implementation is to associate a version
number with each record in your system.
With an RDBMS data store the verification is a matter of adding the version number to the criteria of any SQL statements used to update or delete a record.
Our data is stored in a relational database,
so each table must also store version and modification data.
Here's the schema for a customer table as well as the standard CRUD
SQL necessary to support the Optimistic Offline Lock:
table customer...
create table customer(id bigint primary key, name varchar, createdby varchar, created datetime, modifiedby varchar, modified datetime, version int)
SQL customer CRUD...
INSERT INTO customer VALUES (?,?,?,?,?,?,?)
SELECT * FROM customer WHERE id= ?
UPDATE customer SET name = ?, modifiedBy= ?, modified= ?, version= ? WHEREid= ? and version = ?
DELETE FROM customer WHERE id= ? and version = ?
解决问题,在 customer 表里面增加几个字段,第一个是版本号,只要被改写过,累加一,还可以做得更细致一点,被谁改写,上一次改写的时间是什么,这是可选的,但是版本号必须要有,当把一个数据往回写的时候,首先看版本号是否一致,读走c ustomer 版本号是1,现在写回来,传回来版本号是1,检查数据库里的版本号是不是1,如果也是1,就认可读走之后到现 在写回来之前,没有人改写过,就把写的数据写进去,版本号写成2,david发生的事情,david改成2之后,martin 改完,版本号变成1,1和2之间不匹配,martin 返回的数据里面版本号是1,而现在数据库里面版本号是2,所以有冲突,告诉 martin 有问题,数据库不加锁,靠版本号检测,真正的加锁是当martin读走129用户的时候,做改写的过程当中,david 读不走,只要一读就报错,冲突的概率比较大,为了不频繁发生冲突问题,加锁让它读不走,不会产生写冲突,悲观锁。
Pessimistic Offline Lock-How It Works
You implement Pessimistic Offline Lock in three phases:
determining what type of locks you need, ,
building a lock manager,
and defining procedures for a business transaction to use locks.
Lock types:
exclusive write lock
exclusive read lock
read/write lock
Read and write locks are mutually exclusive.
Concurrent read locks are acceptable.
In choosing the correct lock type think about maximizing
system concurrency, meeting business needs, and minimizing
code complexity.
Also keep in mind that the locking strategy must be understood by domain modelers and analysts.
悲观锁,写锁,独占解锁,独占读锁,悲观锁靠设置数据库隔离级别实现,通过版本号的方式实现,用哪一个,取决于数据模式,只要在性能和可用性冲突之间做权衡,有一个 customer,有一对多,有多个地址,比如家庭住址等,加锁是否把 Customer 数据以及所有的地址加入,比如加载一个 Customer,对应的所有的 address都应该被锁住,同时反过来,如果在读走某一个地址的时候,Customer 关联的其它的 address 全部写入,靠数据库实现不了,数据库在单独一张表,想一次性全锁住,粗略的锁。
With Optimistic Offline Lock, having each item in a group share a version creates the single point of contention, which means sharing the same version, not an equal version.
Incrementing this version will lock the entire group with a shared lock.
Set up your model to point every member of the group at the shared version and you have certainly minimized the path to the point of contention.
粗略锁有乐观和悲观之分,如果是乐观没有锁,让 customer 和 address 共享相同的版本号,无论是从customer或者address 过来改写哪一端,比如改写 address 就把 version 加1,读 customer 就是新的模式,版本号对象是共享的,可以解决问题,并发的乐观的锁。
A shared Pessimistic Offline Lock requires that each member of the group share some sort of lockable token, on which it must then be acquired.
As Pessimistic Offline Lock is often used as a complement to Optimistic Offline Lock, a shared version object makes an excellent candidate for the lockable token role.
悲观锁是在数据库里真有一张表,通过外界关联 customer 和 address,正在锁的时候,把version表锁住,当获取customer 或者 address 必须同时把相应 version 表里面,取不出来就报错,悲观的粗略的锁,粗略的锁要解决问题就是一次性要把多张表的数据锁住,乐观锁是不加锁,靠版本号解决,粗略的锁要靠额外的一张版本表,或者一个版本号对象,再把多张表里面的数据同时锁定,两个东西都不是数据库默认直接能提供的,要靠大家实现。
References 参考文件
Web Applications: What are They? What of Them?
http://www.acunetix.com/websitesecurity/web-applications
The Java EE 8 Tutorial - Transactions
https://javaee.github.io/tutorial/transactions.html# BNCIH
Managing Transactions
https://spring.io guides/gs/managing-transactions/
spring 例子在官网上有,
Transaction 六种属性一般对应哪些应用,不是对应哪些应用,是对应具体的某一个需求,比如转账的时候要记日志,本身记日志会不会影响到事务,完全取决于自己的设计,比如日志系统做得非常稳定,希望日志成为事务的一部分,把它加入到事务里,如果系统本身不太稳定,有可能是一个性能瓶颈,异步记日志本身不要成为转账事务的一部分,把它变成 REQUIRES_ NEW,每一个应用里面可能在不同的功能点上对应着不同的事务数字,事务属性是利用服务器或者它们管理事务的边界,或者人为的通过编码的方式做也可以,session 上面开一个事务,操作完之后把事务提交掉,六个属性基本上已经能覆盖所有能想象到的场景,至于具体哪个场景要如何用,取决于应用本身功能点上是如何设计的。