创建动态的应用程序
动态的领域模型是一个被动态地输入的模型。例如,不是用表示一件拍卖货品的Java类,而是在一个Java Map中使用许多值。1、映射实体名称
首先,要通过命名业务实体启用这个策略。在一个Hibernate XML映射文件中,使用entity-name属性:<hibernate-mapping package="cn.jbit.entity"> <class entity-name="ItemEntity" table="TBL_ITEM_ENTITY"> <id name="id" column="ITEM_ID" type="long"> <generator class="sequence"> <param name="sequence">SEQ_ITEM_ENTITY_ID</param> </generator> </id> <property name="initialPrice" type="big_decimal" column="INIT_PRICE" /> <property name="description" type="string" column="DESCRIPTION" /> <many-to-one name="seller" entity-name="UserEntity" column="USER_ID" /> </class> <class entity-name="UserEntity" table="TBL_USER_ENTITY"> <id name="id" column="USER_ID" type="long"> <generator class="sequence"> <param name="sequence">SEQ_USER_ENTITY_ID</param> </generator> </id> <property name="username" type="string" column="UNAME" /> <bag name="itemsForSale" inverse="true" cascade="all"> <key column="USER_ID" /> <one-to-many entity-name="ItemEntity" /> </bag> </class> </hibernate-mapping>在这个映射文件中有三个值得关注的东西要看。
第一,把几个类映射混合到一个里面了,这是之前我们不建议的做法。这一次,并没有真正映射Java类,而是映射实体的逻辑名称。你没有把Java源文件和同名的XML映射文件放在一起,因此可以用你喜欢的任何方式组织元数据。
第二,<class name="...">属性已经被<class entity-name="..."取代。为了清楚起见,也附加了...Entity给这些逻辑名称,并把它们与你之前用一般的POJO创建的其他非动态映射区别开来。
第三,所有的实体关联,例如<many-to-one>和<one-to-many>,现在也引用逻辑的实体名称。关联映射中的class属性现在是entity-name。这并非严格需要——Hibernate会知道你正在引用一个逻辑的实体名称,即使你使用class属性。然而,当你稍后混合几种表示法时它可以避免混淆。
来看看使用动态的实体看起来是什么样子。
2、使用动态的映射
为了创建其中一个实体的实例,要在Java Map中设置所有的属性值:Map user = new HashMap(); user.put("username", "张三"); Map item1 = new HashMap(); item1.put("initialPrice", new BigDecimal(90)); item1.put("description", "商品描述信息1"); item1.put("seller", user); Map item2 = new HashMap(); item2.put("initialPrice", new BigDecimal(200)); item1.put("description", "商品描述信息2"); item2.put("seller", user); Collection itemsForSale = new ArrayList(); itemsForSale.add(item1); itemsForSale.add(item2); user.put("itemsForSale", itemsForSale); session.save("UserEntity", user);// 保存常见问题:可以在动态模式中映射一个Set(集)吗?基于set的集合不适用于动态的实体模式,在前一个代码示例中,想象itemsForSale是一个Set。Set在它的元素中检查重复元素,因此当你调用add(item1)和add(item2)时,这些对象的equals()方法也被调用。然而,item1和item2是JavaMap实例,一个映射的equals()实现基于映射的键组。因此,由于item1和item2是带有相同键的映射,因此当它们被添加给一个Set时,并没有明显的区别。只有当你在动态的实体模式中需要集合时才使用bag或者list。
Hibernate处理映射就像处理POJO实例一样。例如,使一个映射持久化触发了标识符分配;处于持久化状态的每个映射都有一个包含生成值的标识符属性集。此外,持久化的映射被自动检查在一个工作单元内部的任何修改。例如,在一件货品上设置新的价格,可以加载它,然后让Hibernate完成所有的工作。
Long storedItemId = 2L; Session session = HibernateUtil.getSession(); session.beginTransaction(); ItemPojo itemPojo = (ItemPojo) session.get("ItemEntity",storedItemId); itemPojo.setInitialPrice(new BigDecimal(1000)); session.getTransaction().commit(); session.close();也可以在HQL查询中引用实体名称:
List quriedItemMaps = session.createQuery("from ItemEntity where initialPrice >= :price") .setParameter("price", new BigDecimal(2)).list();这个查询返回ItemEntity映射的集合。它们处于持久化状态。
把这更推进一步,把POJO模型与动态的Map混合。为什么要把领域模型的静态实现和动态的映射实现与动态的映射表示法混合起来,有两个原因:
1)你要默认使用基于POJO类的静态模型,但有时候要所数据轻松地表示为映射的映射。这在生成报表时,或者必须实现一个可以动态地表示不同实体的普通用户界面时,特别有用。
2)要把模型的单个POJO类映射到几张表,然后通过指定一个逻辑的实体名称在运行时选择表。
3、混合动态的和静态的实体模式
要启用混合的模型表示法,得编辑XML映射元数据,并声明POJO类名和逻辑的实体名称:
<hibernate-mapping package="cn.jbit.entity"> <class name="ItemPojo" entity-name="ItemEntity" table="TBL_ITEM_ENTITY"> <id name="id" column="ITEM_ID" type="long"> <generator class="sequence"> <param name="sequence">SEQ_ITEM_ENTITY_ID</param> </generator> </id> <property name="initialPrice" type="big_decimal" column="INIT_PRICE" /> <property name="description" type="string" column="DESCRIPTION" /> <many-to-one name="seller" class="UserPojo" entity-name="UserEntity" column="USER_ID" /> </class> <class name="UserPojo" entity-name="UserEntity" table="TBL_USER_ENTITY"> <id name="id" column="USER_ID" type="long"> <generator class="sequence"> <param name="sequence">SEQ_USER_ENTITY_ID</param> </generator> </id> <property name="username" type="string" column="UNAME" /> <bag name="itemsForSale" inverse="true" cascade="all"> <key column="USER_ID" /> <one-to-many entity-name="ItemEntity" class="ItemPojo" /> </bag> </class> </hibernate-mapping>显然,你还需要两个类ItemPojo和UserPojo,它们实现了这些实体的属性。
从现在开始Hibernate将主要使用逻辑名称。例如,下列代码就不起作用:
UserPojo user = new UserPojo(); ... ItemPojo item1 = new ItemPojo(); ... ItemPojo item2 = new ItemPojo(); ... Collection itemsForSale = new ArrayList(); ... session.save(user);当使用一个混合的表示法映射时,你要告诉Hibernate逻辑名称:
... session.save("UserEntity",user);可以全局地或者临时地切换到一个动态的映射表示法,但是实体模式的全局转换会带来严重的后果。要全局地转换,得把下列代码添加到Hibernate配置中;例如,在hibernate.cfg.xml中:
<property name="default_entity_mode">dynamic-map</property>你更有可能想要临时切换到另一种实体模式,因此假设你把SessionFactory置于默认的POJO模式中。要在一个特定的Session中切换到动态映射,可以在现有会话的上面打开新的临时Sesion。下列代码使用了这样的一个临时的Session来为一位现有的卖家保存一件新的拍卖货品:
Session dynamicSession = session.getSession(EntityMode.MAP); tx = dynamicSession.beginTransaction(); tx.begin(); Map seller = (Map) dynamicSession.get("UserEntity", userId); Map newItemMap = new HashMap(); newItemMap.put("description", "商品3描述"); newItemMap.put("initialPrice", new BigDecimal(1000)); newItemMap.put("seller", seller); dynamicSession.save("ItemEntity", newItemMap);用getSession()打开的临时dynamicSession不需要被清除或关闭;它继承原始Session的上下文。
我们说过,逻辑实体名称的另一个好的用例是把一个POJO映射到几张表,因此我们来看看这个。
3、映射一个类多次
想象你有一些表,它们有一些共同的列。例如,你可能有ITEM_AUCTION和ITEM_SALE表。通常映射每张表给一个实体持久类,分别为ItemAuction和ItemSale。在实体名称的帮助下,你可以保存工作,并实现单个持久化类。<hibernate-mapping package="cn.jbit.entity"> <class name="Item" entity-name="ItemAuctionEntity" table="TBL_ITEM_AUCTION"> <id name="id" column="ITEM_AUCTION_ID"> <generator class="native" /> </id> <property name="description" column="DESCRIPTION" /> <property name="initialPrice" column="INIT_PRICE" /> </class> <class name="Item" entity-name="ItemSaleEntity" table="TBL_ITEM_SALE"> <id name="id" column="ITEM_SALE_ID"> <generator class="native" /> </id> <property name="description" column="DESCRIPTION" /> <property name="salesPrice" column="SALESS_PRICE" /> </class> </hibernate-mapping> Item itemForAuction = new Item(); itemForAuction.setDescription("An item for auction"); itemForAuction.setInitialPrice(new BigDecimal(99)); session.save("ItemAuctionEntity",itemForAuction); Item itemForSale = new Item(); itemForSale.setDescription("An item for sale"); itemForSale.setSalesPrice(new BigDecimal(99)); session.save("ItemSaleEntity",itemForSale);由于逻辑实体名称,Hibernate知道它应该把数据插入到哪张表中。根据用于加载和查询实体的实体名称,Hibernate从适当的表中选择。
需要这项功能的场合很少,你可能和我们一样认为前一个使用案例并不好或者不普遍。
下面将介绍第三种内建的Hibernate实体模式,领域实体用XML文档表示。