数据库并发策略是目前WebLogic Server版本中的默认并发策略。它提供了数据持久性和性能间的折中考虑。原理很简单:WebLogic Server并不自己管理锁,而是为每个试图访问该bean的每个事务创建一个新的bean实例,并将并发控制和死锁检测委派给底层数据库。这就像多个客户端对单个数据库进行并行数据库操作;数据库的隔离水平和锁定策略将规定哪些更新、选择和插入会进行,按照何种顺序,以及哪些(如果有的话)会失败。直接好处是该策略在集群环境中的良好适用性——只要集群中的所有节点共享一个数据库,EJB容器就不需要为数据同步细节而烦恼。
该策略明显比排他性策略更具伸缩性,并且对于某些应用程序效果尤为出众,但是也无法摆脱一些严重的性能限制。即使这样,容器仍保持了一个实体bean实例池,并且这些实例不包含事务间的任何中间状态。这是实例池化而不是缓存数据。池化无状态实例的整体思想可能来自于早期的JVM实现,那时对象创建还是一项很昂贵的操作,并且从性能的角度来看缓存对象实例是有好处的。在现代的JVM中情况并非如此,因为大部分情况下对象的创建非常快,但是由于该行为是EJB规范中描述的,所有供应商都应支持它。然而,当使用数据库并发策略时,容器从缓存中取出“无状态的”bean实例,并且必须执行一条SQL选择操作以获得最新数据并填充实例字段。
这种方法可能还不错,因为我们不用担心“不新鲜的”bean实例(当数据库中的数据被从同一集群中的另一个节点或者从不同应用程序中更新时),但是性能也同样受到明显影响。您总是在每次事务的开始以一个额外的select操作结束,即使您只是打算更新bean中的数据而对之前的值并不感兴趣。因此,在主要或仅是执行更新或插入操作的应用程序中使用实体bean意义不大——容器可能花大量时间做不必要的选择操作,然后再抛弃数据。
排他性和数据库并发策略至少存在一个共同问题:更新丢失的可能性。可以想象两个客户端几乎同时更新映射到一个实体bean的表中的同一条记录。如果数据库中没有锁,先完成的更新操作的结果会被其次完成的更新所覆盖。这是否是可接受的结果取决于您的业务需求。更新丢失通常是不可接受或者不想要的;因此,应用程序需要某种机制来避免或检测更新丢失的情况,并且有机会恢复。当应用程序部署再多个节点上时使用排他性策略将不能控制更新丢失问题。但是如我之前所述,您不应再考虑该该策略。
数据库策略通过将并发控制委派给数据库,提供了进行读数据操作时在数据库中使用排他性锁的选择。这是通过将weblogic-cmp-jar.xml中的use-select-for-update元素设置为true(默认为false)来实现的。顾名思义,该动作告诉WebLogic Server在加载实体bean数据时使用“select for update”。生成的数据库锁一致存在,直到事务完成,因此其他事务不可能在第一个事务运行期间读取或更改数据。该项技术也许在“select for update”上组合了“no wait”选项,可能解决更新丢失问题以及任何可能的死锁——只不过代价很高。
该方法的一个缺点是性能会降低,因为一旦一个事务锁住一个实体实例,其他事务就不能访问同样的实体数据,即使它们需要的是只读访问。这基本上就是另一种排他性策略,唯一的区别是这次它可用于集群环境,因为所有的锁定都发生在(共享)数据库中而不是服务器实例上。第二个缺点是bean的行为在某种程度上依赖于底层数据库的锁策略实现。比如,有些数据库支持细粒度、行级的锁定,而有些则不然。在后一种情况中,对整个记录页的访问可能被作为单个实体加载操作的结果而被阻止。