3.4. Alternative entity representation

In this book, so far, we've always talked about a domain model implementation based on Java classes—we called them POJOs, persistent classes, JavaBeans, or entities. An implementation of a domain model that is based on Java classes with regular properties, collections, and so on, is type-safe. If you access a property of a class, your IDE offers autocompletion based on the strong types of your model, and the compiler checks whether your source is correct. However, you pay for this safety with more time spent on the domain model implementation—and time is money.

In the following sections, we introduce Hibernate's ability to work with domain models that aren't implemented with Java classes. We're basically trading type-safety for other benefits and, because nothing is free, more errors at runtime whenever we make a mistake. In Hibernate, you can select an entity mode for your application, or even mix entity modes for a single model. You can even switch between entity modes in a single Session.

These are the three built-in entity modes in Hibernate:

  • POJO—A domain model implementation based on POJOs, persistent classes. This is what you have seen so far, and it's the default entity mode.

  • MAP—No Java classes are required; entities are represented in the Java application with HashMaps. This mode allows quick prototyping of fully dynamic applications.

  • DOM4J—No Java classes are required; entities are represented as XML elements, based on the dom4j API. This mode is especially useful for exporting or importing data, or for rendering and transforming data through XSLT processing.

There are two reasons why you may want to skip the next section and come back later: First, a static domain model implementation with POJOs is the common case, and dynamic or XML representation are features you may not need right now. Second, we're going to present some mappings, queries, and other operations that you may not have seen so far, not even with the default POJO entity mode. However, if you feel confident enough with Hibernate, read on.

Let's start with the MAP mode and explore how a Hibernate application can be fully dynamically typed.

3.4.1. Creating dynamic applications

A dynamic domain model is a model that is dynamically typed. For example, instead of a Java class that represents an auction item, you work with a bunch of values in a Java Map. Each attribute of an auction item is represented by a key (the name of the attribute) and its value.

Mapping entity names

First, you need to enable this strategy by naming your business entities. In a Hibernate XML mapping file, you use the entity-name attribute:

<hibernate-mapping>

<class entity-name="ItemEntity" table="ITEM_ENTITY">
  <id name="id" type="long" column="ITEM_ID">
    <generator class="native"/>
  </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="USER_ENTITY">
  <id name="id" type="long" column="USER_ID">
    <generator class="native"/>
  </id>

  <property name="username"
      type="string"
      column="USERNAME"/>

  <bag name="itemsForSale" inverse="true" cascade="all">
    <key column="USER_ID"/>
    <one-to-many entity-name="ItemEntity"/>
  </bag>

</class>

</hibernate-mapping>

There are three interesting things to observe in this mapping file.

First, you mix several class mappings in one, something we didn't recommend earlier. This time you aren't really mapping Java classes, but logical names of entities. You don't have a Java source file and an XML mapping file with the same name next to each other, so you're free to organize your metadata in any way you like.

Second, the <class name="..."> attribute has been replaced with <class entity-name="...">. You also append ...Entity to these logical names for clarity and to distinguish them from other nondynamic mappings that you made earlier with regular POJOs.

Finally, all entity associations, such as <many-to-one> and <one-to-many>, now also refer to logical entity names. The class attribute in the association mappings is now entity-name. This isn't strictly necessary—Hibernate can recognize that you're referring to a logical entity name even if you use the class attribute. However, it avoids confusion when you later mix several representations.

Let's see what working with dynamic entities looks like.

Working with dynamic maps

To create an instance of one of your entities, you set all attribute values in a Java Map:

Map user = new HashMap();
user.put("username", "johndoe");

Map item1 = new HashMap();
item1.put("description", "An item for auction");
item1.put("initialPrice", new BigDecimal(99));
item1.put("seller", user);

Map item2 = new HashMap();
item2.put("description", "Another item for auction");
item2.put("initialPrice", new BigDecimal(123));
item2.put("seller", user);

Collection itemsForSale = new ArrayList();
itemsForSale.add(item1);
itemsForSale.add(item2);
user.put("itemsForSale", itemsForSale);

session.save("UserEntity", user);

The first map is a UserEntity, and you set the username attribute as a key/value pair. The next two maps are ItemEntitys, and here you set the link to the seller of each item by putting the user map into the item1 and item2 maps. You're effectively linking maps—that's why this representation strategy is sometimes also called "representation with maps of maps."

The collection on the inverse side of the one-to-many association is initialized with an ArrayList, because you mapped it with bag semantics (Java doesn't have a bag implementation, but the Collection interface has bag semantics). Finally, the save() method on the Session is given a logical entity name and the user map as an input parameter.

Hibernate knows that UserEntity refers to the dynamically mapped entity, and that it should treat the input as a map that has to be saved accordingly. Hibernate also cascades to all elements in the itemsForSale collection; hence, all item maps are also made persistent. One UserEntity and two ItemEntitys are inserted into their respective tables.


FAQ

Can I map a Set in dynamic mode? Collections based on sets don't work with dynamic entity mode. In the previous code example, imagine that itemsForSale was a Set. A Set checks its elements for duplicates, so when you call add(item1) and add(item2), the equals() method on these objects is called. However, item1 and item2 are Java Map instances, and the equals() implementation of a map is based on the key sets of the map. So, because both item1 and item2 are maps with the same keys, they aren't distinct when added to a Set. Use bags or lists only if you require collections in dynamic entity mode.

Hibernate handles maps just like POJO instances. For example, making a map persistent triggers identifier assignment; each map in persistent state has an identifier attribute set with the generated value. Furthermore, persistent maps are automatically checked for any modifications inside a unit of work. To set a new price on an item, for example, you can load it and then let Hibernate do all the work:

Long storedItemId = (Long) item1.get("id");

Session session = getSessionFactory().openSession();
session.beginTransaction();

Map loadedItemMap = (Map) session.load("ItemEntity", storedItemId);

loadedItemMap.put("initialPrice", new BigDecimal(100));

session.getTransaction().commit();
session.close();

All Session methods that have class parameters such as load() also come in an overloaded variation that accepts entity names. After loading an item map, you set a new price and make the modification persistent by committing the transaction, which, by default, triggers dirty checking and flushing of the Session.

You can also refer to entity names in HQL queries:

List queriedItemMaps =
  session.createQuery("from ItemEntity where initialPrice >= :p")
    .setParameter("p", new BigDecimal(100))
    .list();

This query returns a collection of ItemEntity maps. They are in persistent state.

Let's take this one step further and mix a POJO model with dynamic maps. There are two reasons why you would want to mix a static implementation of your domain model with a dynamic map representation:

  • You want to work with a static model based on POJO classes by default, but sometimes you want to represent data easily as maps of maps. This can be particularly useful in reporting, or whenever you have to implement a generic user interface that can represent various entities dynamically.

  • You want to map a single POJO class of your model to several tables and then select the table at runtime by specifying a logical entity name.

You may find other use cases for mixed entity modes, but they're so rare that we want to focus on the most obvious.

First, therefore, you'll mix a static POJO model and enable dynamic map representation for some of the entities, some of the time.

Mixing dynamic and static entity modes

To enable a mixed model representation, edit your XML mapping metadata and declare a POJO class name and a logical entity name:

<hibernate-mapping>

<class name="model.ItemPojo"
   entity-name="ItemEntity"

   table="ITEM_ENTITY">
  ...
  <many-to-one name="seller"
       entity-name="UserEntity"
       column="USER_ID"/>

</class>

<class name="model.UserPojo"
   entity-name="UserEntity"
   table="USER_ENTITY">
  ...
  <bag name="itemsForSale" inverse="true" cascade="all">
    <key column="USER_ID"/>
    <one-to-many entity-name="ItemEntity"/>
  </bag>

</class>

</hibernate-mapping>

Obviously, you also need the two classes, model.ItemPojo and model.UserPojo, that implement the properties of these entities. You still base the many-to-one and one-to-many associations between the two entities on logical names.

Hibernate will primarily use the logical names from now on. For example, the following code does not work:

UserPojo user = new UserPojo();
...
ItemPojo item1 = new ItemPojo();
...
ItemPojo item2 = new ItemPojo();
...
Collection itemsForSale = new ArrayList();
...

session.save(user);

The preceding example creates a few objects, sets their properties, and links them, and then tries to save the objects through cascading by passing the user instance to save(). Hibernate inspects the type of this object and tries to figure out what entity it is, and because Hibernate now exclusively relies on logical entity names, it can't find a mapping for model.UserPojo. You need to tell Hibernate the logical name when working with a mixed representation mapping:

...
session.save("UserEntity", user);

Once you change this line, the previous code example works. Next, consider loading, and what is returned by queries. By default, a particular SessionFactoryis in POJO entity mode, so the following operations return instances of model.ItemPojo:

Long storedItemId = item1.getId();
ItemPojo loadedItemPojo =
  (ItemPojo) session.load("ItemEntity", storedItemId);

List queriedItemPojos =
  session.createQuery("from ItemEntity where initialPrice >= :p")
    .setParameter("p", new BigDecimal(100))
    .list();

You can switch to a dynamic map representation either globally or temporarily, but a global switch of the entity mode has serious consequences. To switch globally, add the following to your Hibernate configuration; e.g., in hibernate.cfg.xml:

<property name="default_entity_mode">dynamic-map</property>

All Session operations now either expect or return dynamically typed maps! The previous code examples that stored, loaded, and queried POJO instances no longer work; you need to store and load maps.

It's more likely that you want to switch to another entity mode temporarily, so let's assume that you leave the SessionFactory in the default POJO mode. To switch to dynamic maps in a particular Session, you can open up a new temporary Session on top of the existing one. The following code uses such a temporary Session to store a new auction item for an existing seller:

Session dynamicSession = session.getSession(EntityMode.MAP);

Map seller = (Map) dynamicSession.load("UserEntity", user.getId() );
Map newItemMap = new HashMap();
newItemMap.put("description", "An item for auction");
newItemMap.put("initialPrice", new BigDecimal(99));
newItemMap.put("seller", seller);

dynamicSession.save("ItemEntity", newItemMap);

Long storedItemId = (Long) newItemMap.get("id");

Map loadedItemMap =
  (Map) dynamicSession.load("ItemEntity", storedItemId);

List queriedItemMaps =
  dynamicSession
    .createQuery("from ItemEntity where initialPrice >= :p")
   .setParameter("p", new BigDecimal(100))
   .list();

The temporary dynamicSession that is opened with getSession() doesn't need to be flushed or closed; it inherits the context of the original Session. You use it only to load, query, or save data in the chosen representation, which is the EntityMode.MAP in the previous example. Note that you can't link a map with a POJO instance; the seller reference has to be a HashMap, not an instance of UserPojo.

We mentioned that another good use case for logical entity names is the mapping of one POJO to several tables, so let's look at that.

Mapping a class several times

Imagine that you have several tables with some columns in common. For example, you could have ITEM_AUCTION and ITEM_SALE tables. Usually you map each table to an entity persistent class, ItemAuction and ItemSale respectively. With the help of entity names, you can save work and implement a single persistent class.

To map both tables to a single persistent class, use different entity names (and usually different property mappings):

<hibernate-mapping>

<class name="model.Item"
   entity-name="ItemAuction"
   table="ITEM_AUCTION">

 <id name="id" column="ITEM_AUCTION_ID">...</id>
 <property name="description" column="DESCRIPTION"/>
 <property name="initialPrice" column="INIT_PRICE"/>

</class>

<class name="model.Item"
   entity-name="ItemSale"
   table="ITEM_SALE">

 <id name="id" column="ITEM_SALE_ID">...</id>
 <property name="description" column="DESCRIPTION"/>
 <property name="salesPrice" column="SALES_PRICE"/>

</class>

</hibernate-mapping>

The model.Item persistent class has all the properties you mapped: id, description, initialPrice, and salesPrice. Depending on the entity name you use at runtime, some properties are considered persistent and others transient:

Item itemForAuction = new Item();
itemForAuction.setDescription("An item for auction");
itemForAuction.setInitialPrice( new BigDecimal(99) );
session.save("ItemAuction", itemForAuction);

Item itemForSale = new Item();
itemForSale.setDescription("An item for sale");

itemForSale.setSalesPrice( new BigDecimal(123) );
session.save("ItemSale", itemForSale);

Thanks to the logical entity name, Hibernate knows into which table it should insert the data. Depending on the entity name you use for loading and querying entities, Hibernate selects from the appropriate table.

Scenarios in which you need this functionality are rare, and you'll probably agree with us that the previous use case isn't good or common.

In the next section, we introduce the third built-in Hibernate entity mode, the representation of domain entities as XML documents.

3.4.2. Representing data in XML

XML is nothing but a text file format; it has no inherent capabilities that qualify it as a medium for data storage or data management. The XML data model is weak, its type system is complex and underpowered, its data integrity is almost completely procedural, and it introduces hierarchical data structures that were outdated decades ago. However, data in XML format is attractive to work with in Java; we have nice tools. For example, we can transform XML data with XSLT, which we consider one of the best use cases.

Hibernate has no built-in functionality to store data in an XML format; it relies on a relational representation and SQL, and the benefits of this strategy should be clear. On the other hand, Hibernate can load and present data to the application developer in an XML format. This allows you to use a sophisticated set of tools without any additional transformation steps.

Let's assume that you work in default POJO mode and that you quickly want to obtain some data represented in XML. Open a temporary Session with the EntityMode.DOM4J:

Session dom4jSession = session.getSession(EntityMode.DOM4J);

Element userXML =
  (Element) dom4jSession.load(User.class, storedUserId);

What is returned here is a dom4j Element, and you can use the dom4j API to read and manipulate it. For example, you can pretty-print it to your console with the following snippet:

try {
  OutputFormat format = OutputFormat.createPrettyPrint();
  XMLWriter writer = new XMLWriter( System.out, format);
  writer.write( userXML );
} catch (IOException ex) {
  throw new RuntimeException(ex);
}

If we assume that you reuse the POJO classes and data from the previous examples, you see one User instance and two Item instances (for clarity, we no longer name them UserPojo and ItemPojo):

<User>
  <id>1</id>
  <username>johndoe</username>
  <itemsForSale>
  <Item>
  <id>2</id>
  <initialPrice>99</initialPrice>
  <description>An item for auction</description>
  <seller>1</seller>
  </Item>
  <Item>
  <id>3</id>
  <initialPrice>123</initialPrice>
  <description>Another item for auction</description>
  <seller>1</seller>
  </Item>
  </itemsForSale>
</User>

Hibernate assumes default XML element names—the entity and property names. You can also see that collection elements are embedded, and that circular references are resolved through identifiers (the <seller> element).

You can change this default XML representation by adding node attributes to your Hibernate mapping metadata:

<hibernate-mapping>

<class name="Item" table="ITEM_ENTITY" node="item">

  <id name="id" type="long" column="ITEM_ID" node="@id">
    <generator class="native"/>
  </id>

  <property name="initialPrice"
      type="big_decimal"
      column="INIT_PRICE"
      node="item-details/@initial-price"/>

  <property name="description"
      type="string"
      column="DESCRIPTION"
      node="item-details/@description"/>

  <many-to-one name="seller"
       class="User"
       column="USER_ID"
       embed-xml="false"

       node="@seller-id"/>

</class>

<class name="User" table="USERS" node="user">

  <id name="id" type="long" column="USER_ID" node="@id">
    <generator class="native"/>
  </id>

  <property name="username"
      type="string"
      column="USERNAME"
      node="@username"/>

  <bag name="itemsForSale" inverse="true" cascade="all"
   embed-xml="true" node="items-for-sale">
    <key column="USER_ID"/>
    <one-to-many class="Item"/>
  </bag>

</class>

</hibernate-mapping>

Each node attribute defines the XML representation:

  • A node="name" attribute on a <class> mapping defines the name of the XML element for that entity.

  • A node="name" attribute on any property mapping specifies that the property content should be represented as the text of an XML element of the given name.

  • A node="@name" attribute on any property mapping specifies that the property content should be represented as an XML attribute value of the given name.

  • A node="name/@attname" attribute on any property mapping specifies that the property content should be represented as an XML attribute value of the given name, on a child element of the given name.

The embed-xml option is used to trigger embedding or referencing of associated entity data. The updated mapping results in the following XML representation of the same data you've seen before:

<user id="1" username="johndoe">
  <items-for-sale>
  <item id="2" seller-id="1">
  <item-details initial-price="99"
    description="An item for auction"/>
  </item>

  <item id="3" seller-id="1">
  <item-details initial-price="123"
    description="Another item for auction"/>
  </item>
  </items-for-sale>
</user>

Be careful with the embed-xml option—you can easily create circular references that result in an endless loop!

Finally, data in an XML representation is transactional and persistent, so you can modify queried XML elements and let Hibernate take care of updating the underlying tables:

Element itemXML =
  (Element) dom4jSession.get(Item.class, storedItemId);

itemXML.element("item-details")
    .attribute("initial-price")
   .setValue("100");

session.flush(); // Hibernate executes UPDATEs

Element userXML =
  (Element) dom4jSession.get(User.class, storedUserId);

Element newItem = DocumentHelper.createElement("item");
Element newItemDetails = newItem.addElement("item-details");
newItem.addAttribute("seller-id",
       userXml.attribute("id").getValue() );
newItemDetails.addAttribute("initial-price", "123");
newItemDetails.addAttribute("description", "A third item");

dom4jSession.save(Item.class.getName(), newItem);

dom4jSession.flush(); // Hibernate executes INSERTs

There is no limit to what you can do with the XML that is returned by Hibernate. You can display, export, and transform it in any way you like. See the dom4j documentation for more information.

Finally, note that you can use all three built-in entity modes simultaneously, if you like. You can map a static POJO implementation of your domain model, switch to dynamic maps for your generic user interface, and export data into XML. Or, you can write an application that doesn't have any domain classes, only dynamic maps and XML. We have to warn you, though, that prototyping in the software industry often means that customers end up with the prototype that nobody wanted to throw away—would you buy a prototype car? We highly recommend that you rely on static domain models if you want to create a maintainable system.

We won't consider dynamic models or XML representation again in this book. Instead, we'll focus on static persistent classes and how they are mapped.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.0.37