Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 86 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
86
Dung lượng
568,78 KB
Nội dung
144 CHAPTER 3 Domain models and metadata 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 repre- sentation 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" Alternative entity representation 145 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 SessionFactory 146 CHAPTER 3 Domain models and metadata is 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 tempo- rary Session on top of the existing one. The following code uses such a tempo- rary 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 Alternative entity representation 147 only to load, query, or save data in the chosen representation, which is the Entity- Mode.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 map- ping 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 exam- ple, 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 , descrip- tion , 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"); 148 CHAPTER 3 Domain models and metadata 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 com- pletely procedural, and it introduces hierarchical data structures that were out- dated 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 Enti- tyMode.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); } Alternative entity representation 149 If we assume that you reuse the POJO classes and data from the previous exam- ples, 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 refer- ences 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" 150 CHAPTER 3 Domain models and metadata 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 prop- erty 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 prop- erty 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> Alternative entity representation 151 <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 docu- mentation 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. 152 CHAPTER 3 Domain models and metadata 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. 3.5 Summary In this chapter, we focused on the design and implementation of a rich domain model in Java. You now understand that persistent classes in a domain model should to be free of crosscutting concerns, such as transactions and security. Even persistence- related concerns should not leak into the domain model implementation. You also know how important transparent persistence is if you want to execute and test your business objects independently and easily. You have learned the best practices and requirements for the POJO and JPA entity programming model, and what concepts they have in common with the old JavaBean specification. We had a closer look at the implementation of persistent classes, and how attributes and relationships are best represented. To be prepared for the next part of the book, and to learn all the object/rela- tional mapping options, you needed to make an educated decision to use either XML mapping files or JDK 5.0 annotations, or possibly a combination of both. You’re now ready to write more complex mappings in both formats. For convenience, table 3.1 summarizes the differences between Hibernate and Java Persistence related to concepts discussed in this chapter. Table 3.1 Hibernate and JPA comparison chart for chapter 3 Hibernate Core Java Persistence and EJB 3.0 Persistent classes require a no-argument con- structor with public or protected visibility if proxy- based lazy loading is used. The JPA specification mandates a no-argument constructor with public or protected visibility for all entity classes. Persistent collections must be typed to interfaces. Hibernate supports all JDK interfaces. Persistent collections must be typed to interfaces. Only a subset of all interfaces (no sorted collec- tions, for example) is considered fully portable. Persistent properties can be accessed through fields or accessor methods at runtime, or a com- pletely customizable strategy can be applied. Persistent properties of an entity class are accessed through fields or accessor methods, but not both if full portability is required. Summary 153 In the next part of the book, we show you all possible basic and some advanced mapping techniques, for classes, properties, inheritance, collections, and associa- tions. You’ll learn how to solve the structural object/relational mismatch. The XML metadata format supports all possible Hibernate mapping options. JPA annotations cover all basic and most advanced mapping options. Hibernate Annotations are required for exotic mappings and tuning. XML mapping metadata can be defined globally, and XML placeholders are used to keep metadata free from dependencies. Global metadata is only fully portable if declared in the standard orm.xml metadata file. Table 3.1 Hibernate and JPA comparison chart for chapter 3 (continued) Hibernate Core Java Persistence and EJB 3.0 [...]... A typical Hibernate property mapping defines a POJO’s property name, a database column name, and the name of a Hibernate type, and it’s often possible to omit the type So, if description is a property of (Java) type java. lang.String, Hibernate uses the Hibernate type string by default (we come back to the Hibernate type system in the next chapter) Hibernate uses reflection to determine the Java type... a better way to handle this kind of requirement is to use an SQL schema (a kind of namespace), as already discussed in chapter 3, section 3. 3.4, “Handling global metadata.” You can set a naming strategy implementation in Java Persistence in your persistence. xml file with the hibernate. ejb.naming_strategy option Now that we have covered the concepts and most important mappings for entities, let’s map... names in backticks, to force Hibernate to use quoted identifiers everywhere You should consider renaming tables or columns with reserved keyword names whenever possible Quoting with backticks works with annotation mappings, but it’s an implementation detail of Hibernate and not part of the JPA specification 4 .3. 6 Implementing naming conventions We often encounter organizations with strict conventions for... by Hibernate for immutable persistent classes, so you don’t have to write useless accessor methods You can map an immutable entity using annotations: @Entity @org .hibernate. annotations.Entity(mutable = false) @org .hibernate. annotations.AccessType("field") public class Bid { Again, the native Hibernate @Entity annotation extends the JPA annotation with additional options We have also shown the Hibernate. .. Chapter 4 starts with regular class and property mappings, and explains how you can map fine-grained Java domain models Next, in chapter 5, you’ll see how to map more complex class inheritance hierarchies and how to extend Hibernate' s functionality with the powerful custom mapping type system In chapters 6 and 7, we show you how to map Java collections and associations between classes, with many sophisticated... in the memory space of the virtual machine (If you’re a Java guru, we acknowledge that String is a special case Assume we used a different class to make the same point.) Persistence complicates this picture With object/relational persistence, a persistent object is an in-memory representation of a particular row of a database table Along with Java identity (memory location) and object equality, you... mapped explicitly in the XML mapping file On the other hand, if a class is mapped with annotations, all of its properties are considered persistent by default You can mark properties with the @javax .persistence. Transient annotation to exclude them, or use the transient Java keyword (which usually only excludes fields for Java serialization) In a JPA XML descriptor, you can exclude a particular field... direct annotation is available: @Entity @org .hibernate. annotations.GenericGenerator( name = "hibernate- uuid", strategy = "uuid" ) class name MyEntity { @Id @GeneratedValue(generator = "hibernate- uuid") @Column(name = "MY_ID") String id; } The @GenericGenerator Hibernate extension can be used to give a Hibernate identifier generator a name, in this case hibernate- uuid This name is then referenced by... the database system throws an exception at startup or runtime If you quote a table or column name with backticks in the mapping document, Hibernate always quotes this identifier in the generated SQL The following property declaration forces Hibernate to generate SQL with the quoted column name "DESCRIPTION" Hibernate also knows that Microsoft SQL Server needs the variation [DESCRIPTION] and that MySQL... identifier generators can be configured with options In a native Hibernate XML mapping, you define options as pairs of keys and values: MY_SEQUENCE INCREMENT BY 1 START WITH 1 You can use Hibernate identifier generators with annotations, even if no direct . convenience, table 3. 1 summarizes the differences between Hibernate and Java Persistence related to concepts discussed in this chapter. Table 3. 1 Hibernate and JPA comparison chart for chapter 3 Hibernate. the standard orm.xml metadata file. Table 3. 1 Hibernate and JPA comparison chart for chapter 3 (continued) Hibernate Core Java Persistence and EJB 3. 0 Part 2 Mapping concepts and strategies This. and JPA comparison chart for chapter 3 Hibernate Core Java Persistence and EJB 3. 0 Persistent classes require a no-argument con- structor with public or protected visibility if proxy- based lazy