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
836,06 KB
Nội dung
232 CHAPTER 5 Inheritance and custom types We left out the usual mandatory housekeeping methods in this example. The important additional method is setParameterValues() of the Parameterized- Type interface. Hibernate calls this method on startup to initialize this class with a convertTo parameter. The nullSafeSet() methods uses this setting to convert to the target currency when saving a MonetaryAmount . The nullSafeGet() method takes the currency that is present in the database and leaves it to the client to deal with the currency of a loaded MonetaryAmount (this asymmetric implementation isn’t the best idea, naturally). You now have to set the configuration parameters in your mapping file when you apply the custom mapping type. A simple solution is the nested <type> map- ping on a property: <property name="initialPrice"> <column name="INITIAL_PRICE"/> <column name="INITIAL_PRICE_CUR"/> <type name="persistence.MonetaryAmountConversionType"> <param name="convertTo">USD</param> </type> </property> However, this is inconvenient and requires duplication if you have many monetary amounts in your domain model. A better strategy uses a separate definition of the type, including all parameters, under a unique name that you can then reuse across all your mappings. You do this with a separate <typedef> , an element (you can also use it without parameters): <typedef class="persistence.MonetaryAmountConversionType" name="monetary_amount_usd"> <param name="convertTo">USD</param> </typedef> <typedef class="persistence.MonetaryAmountConversionType" name="monetary_amount_eur"> <param name="convertTo">EUR</param> </typedef> What we show here is a binding of a custom mapping type with some arguments to the names monetary_amount_usd and monetary_amount_eur . This definition can be placed anywhere in your mapping files; it’s a child element of <hibernate- mapping> (as mentioned earlier in the book, larger applications have often one or several MyCustomTypes.hbm.xml files with no class mappings). With Hibernate extensions, you can define named custom types with parameters in annotations: Creating custom mapping types 233 @org.hibernate.annotations.TypeDefs({ @org.hibernate.annotations.TypeDef( name="monetary_amount_usd", typeClass = persistence.MonetaryAmountConversionType.class, parameters = { @Parameter(name="convertTo", value="USD") } ), @org.hibernate.annotations.TypeDef( name="monetary_amount_eur", typeClass = persistence.MonetaryAmountConversionType.class, parameters = { @Parameter(name="convertTo", value="EUR") } ) }) This annotation metadata is global as well, so it can be placed outside any Java class declaration (right after the import statements) or in a separate file, pack- age-info.java , as discussed in chapter 2, section 2.2.1, “Using Hibernate Anno- tations.” A good location in this system is in a package-info.java file in the persistence package. In XML mapping files and annotation mappings, you now refer to the defined type name instead of the fully qualified class name of your custom type: <property name="initialPrice" type="monetary_amount_usd"> <column name="INITIAL_PRICE"/> <column name="INITIAL_PRICE_CUR"/> </property> @org.hibernate.annotations.Type(type = "monetary_amount_eur") @org.hibernate.annotations.Columns({ @Column(name = "BID_AMOUNT"), @Column(name = "BID_AMOUNT_CUR") }) private MonetaryAmount bidAmount; Let’s look at a different, extremely important, application of custom mapping types. The type-safe enumeration design pattern can be found in almost all appli- cations. 5.3.7 Mapping enumerations An enumeration type is a common Java idiom where a class has a constant (small) number of immutable instances. In CaveatEmptor, this can be applied to credit cards: for example, to express the possible types a user can enter and the applica- tion offers (Mastercard, Visa, and so on). Or, you can enumerate the possible rat- ings a user can submit in a Comment , about a particular auction. 234 CHAPTER 5 Inheritance and custom types In older JDKs, you had to implement such classes (let’s call them CreditCard- Type and Rating ) yourself, following the type-safe enumeration pattern. This is still the right way to do it if you don’t have JDK 5.0; the pattern and compatible custom mapping types can be found on the Hibernate community website. Using enumerations in JDK 5.0 If you use JDK 5.0, you can use the built-in language support for type-safe enumer- ations. For example, a Rating class looks as follows: package auction.model; public enum Rating { EXCELLENT, OK, BAD } The Comment class has a property of this type: public class Comment { private Rating rating; private Item auction; } This is how you use the enumeration in the application code: Comment goodComment = new Comment(Rating.EXCELLENT, thisAuction); You now have to persist this Comment instance and its Rating . One approach is to use the actual name of the enumeration and save it to a VARCHAR column in the COMMENTS table. This RATING column will then contain EXCELLENT , OK , or BAD , depending on the Rating given. Let’s write a Hibernate UserType that can load and store VARCHAR -backed enu- merations, such as the Rating . Writing a custom enumeration handler Instead of the most basic UserType interface, we now want to show you the EnhancedUserType interface. This interface allows you to work with the Comment entity in XML representation mode, not only as a POJO (see the discussion of data representations in chapter 3, section 3.4, “Alternative entity representa- tion”). Furthermore, the implementation you’ll write can support any VARCHAR - backed enumeration, not only Rating , thanks to the additional Parameterized- Type interface. Look at the code in listing 5.6. Creating custom mapping types 235 public class StringEnumUserType implements EnhancedUserType, ParameterizedType { private Class<Enum> enumClass; public void setParameterValues(Properties parameters) { String enumClassName = parameters.getProperty("enumClassname"); try { enumClass = ReflectHelper.classForName(enumClassName); } catch (ClassNotFoundException cnfe) { throw new HibernateException("Enum class not found", cnfe); } } public Class returnedClass() { return enumClass; } public int[] sqlTypes() { return new int[] { Hibernate.STRING.sqlType() }; } public boolean isMutable public Object deepCopy public Serializable disassemble public Object replace public Object assemble public boolean equals public int hashCode public Object fromXMLString(String xmlValue) { return Enum.valueOf(enumClass, xmlValue); } public String objectToSQLString(Object value) { return '\'' + ( (Enum) value ).name() + '\''; } public String toXMLString(Object value) { return ( (Enum) value ).name(); } public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException { String name = rs.getString( names[0] ); return rs.wasNull() ? null : Enum.valueOf(enumClass, name); } public void nullSafeSet(PreparedStatement st, Listing 5.6 Custom mapping type for string-backed enumerations B C D E F G H 236 CHAPTER 5 Inheritance and custom types Object value, int index) throws SQLException { if (value == null) { st.setNull(index, Hibernate.STRING.sqlType()); } else { st.setString( index, ( (Enum) value ).name() ); } } } The configuration parameter for this custom mapping type is the name of the enumeration class it’s used for, such as Rating . It’s also the class that is returned from this method. A single VARCHAR column is needed in the database table. You keep it portable by letting Hibernate decide the SQL datatype. These are the usual housekeeping methods for an immutable type. The following three methods are part of the EnhancedUserType and are used for XML marshalling. When you’re loading an enumeration, you get its name from the database and create an instance. When you’re saving an enumeration, you store its name. Next, you’ll map the rating property with this new custom type. Mapping enumerations with XML and annotations In the XML mapping, first create a custom type definition: <typedef class="persistence.StringEnumUserType" name="rating"> <param name="enumClassname">auction.model.Rating</param> </typedef> You can now use the type named rating in the Comment class mapping: <property name="rating" column="RATING" type="rating" not-null="true" update="false" access="field"/> B C D E F G H Creating custom mapping types 237 Because ratings are immutable, you map it as update="false" and enable direct field access (no setter method for immutable properties). If other classes besides Comment have a Rating property, use the defined custom mapping type again. The definition and declaration of this custom mapping type in annotations looks the same as the one you did in the previous section. On the other hand, you can rely on the Java Persistence provider to persist enumerations. If you have a property in one of your annotated entity classes of type java.lang.Enum (such as the rating in your Comment ), and it isn’t marked as @Transient or transient (the Java keyword), the Hibernate JPA implementation must persist this property out of the box without complaining; it has a built-in type that handles this. This built-in mapping type has to default to a representa- tion of an enumeration in the database. The two common choices are string rep- resentation, as you implemented for native Hibernate with a custom type, or ordinal representation. An ordinal representation saves the position of the selected enumeration option: for example, 1 for EXCELLENT , 2 for OK , and 3 for BAD . The database column also defaults to a numeric column. You can change this default enumeration mapping with the Enumerated annotation on your property: public class Comment { @Enumerated(EnumType.STRING) @Column(name = "RATING", nullable = false, updatable = false) private Rating rating; } You’ve now switched to a string-based representation, effectively the same repre- sentation your custom type can read and write. You can also use a JPA XML descriptor: <entity class="auction.model.Item" access="PROPERTY"> <attributes> <basic name="rating"> <column name="RATING" nullable="false" updatable="false"/> <enumerated>STRING</enumerated> </basic> </attributes> </entity> You may (rightfully) ask why you have to write your own custom mapping type for enumerations when obviously Hibernate, as a Java Persistence provider, can per- sist and load enumerations out of the box. The secret is that Hibernate Annota- tions includes several custom mapping types that implement the behavior defined 238 CHAPTER 5 Inheritance and custom types by Java Persistence. You could use these custom types in XML mappings; however, they aren’t user friendly (they need many parameters) and weren’t written for that purpose. You can check the source (such as org.hibernate.type.EnumType in Hibernate Annotations) to learn their parameters and decide if you want to use them directly in XML. Querying with custom mapping types One further problem you may run into is using enumerated types in Hibernate queries. For example, consider the following query in HQL that retrieves all com- ments that are rated “bad”: Query q = session.createQuery( "from Comment c where c.rating = auction.model.Rating.BAD" ); Although this query works if you persist your enumeration as a string (the query parser uses the enumeration value as a constant), it doesn’t work if you selected ordinal representation. You have to use a bind parameter and set the rating value for the comparison programmatically: Query q = session.createQuery("from Comment c where c.rating = :rating"); Properties params = new Properties(); params.put("enumClassname", "auction.model.Rating"); q.setParameter("rating", Rating.BAD, Hibernate.custom(StringEnumUserType.class, params) ); The last line in this example uses the static helper method Hibernate.custom() to convert the custom mapping type to a Hibernate Type ; this is a simple way to tell Hibernate about your enumeration mapping and how to deal with the Rating.BAD value. Note that you also have to tell Hibernate about any initializa- tion properties the parameterized type may need. Unfortunately, there is no API in Java Persistence for arbitrary and custom query parameters, so you have to fall back to the Hibernate Session API and cre- ate a Hibernate Query object. We recommend that you become intimately familiar with the Hibernate type system and that you consider the creation of custom mapping types an essential skill—it will be useful in every application you develop with Hibernate or JPA. Summary 239 5.4 Summary In this chapter, you learned how inheritance hierarchies of entities can be mapped to the database with the four basic inheritance mapping strategies: table per concrete class with implicit polymorphism, table per concrete class with unions, table per class hierarchy, and the normalized table per subclass strategy. You’ve seen how these strategies can be mixed for a particular hierarchy and when each strategy is most appropriate. We also elaborated on the Hibernate entity and value type distinction, and how the Hibernate mapping type system works. You used various built-in types and wrote your own custom types by utilizing the Hibernate extension points such as UserType and ParameterizedType . Table 5.5 shows a summary you can use to compare native Hibernate features and Java Persistence. The next chapter introduces collection mappings and discusses how you can han- dle collections of value typed objects (for example, a collection of String s) and collections that contain references to entity instances. Table 5.5 Hibernate and JPA comparison chart for chapter 5 Hibernate Core Java Persistence and EJB 3.0 Supports four inheritance mapping strategies. Mixing of inheritance strategies is possible. Four inheritance mapping strategies are standardized; mixing strategies in one hierarchy isn’t considered portable. Only table per class hierarchy and table per subclass are required for JPA- compliant providers. A persistent supertype can be an abstract class or an interface (with property accessor methods only). A persistent supertype can be an abstract class; mapped inter- faces aren’t considered portable. Provides flexible built-in mapping types and converters for value typed properties. There is automatic detection of mapping types, with standard- ized override for temporal and enum mapping types. Hibernate extension annotation is used for any custom mapping type dec- laration. Powerful extendable type system. The standard requires built-in types for enumerations, LOBs, and many other value types for which you’d have to write or apply a custom mapping type in native Hibernate. 240 Mapping collections and entity associations This chapter covers ■ Basic collection mapping strategies ■ Mapping collections of value types ■ Mapping a parent/children entity relationship Sets, bags, lists, and maps of value types 241 Two important (and sometimes difficult to understand) topics didn’t appear in the previous chapters: the mapping of collections, and the mapping of associa- tions between entity classes. Most developers new to Hibernate are dealing with collections and entity asso- ciations for the first time when they try to map a typical parent/child relationship. But instead of jumping right into the middle, we start this chapter with basic col- lection mapping concepts and simple examples. After that, you’ll be prepared for the first collection in an entity association—although we’ll come back to more complicated entity association mappings in the next chapter. To get the full pic- ture, we recommend you read both chapters. 6.1 Sets, bags, lists, and maps of value types An object of value type has no database identity; it belongs to an entity instance, and its persistent state is embedded in the table row of the owning entity—at least, if an entity has a reference to a single instance of a valuetype. If an entity class has a collection of value types (or a collection of references to value-typed instances), you need an additional table, the so-called collection table. Before you map collections of value types to collection tables, remember that value-typed classes don’t have identifiers or identifier properties. The lifespan of a value-type instance is bounded by the lifespan of the owning entity instance. A value type doesn’t support shared references. Java has a rich collection API, so you can choose the collection interface and implementation that best fits your domain model design. Let’s walk through the most common collection mappings. Suppose that sellers in CaveatEmptor are able to attach images to Item s. An image is accessible only via the containing item; it doesn’t need to support associ- ations from any other entity in your system. The application manages the collec- tion of images through the Item class, adding and removing elements. An image object has no life outside of the collection; it’s dependent on an Item entity. In this case, it isn’t unreasonable to model the image class as a value type. Next. you need to decide what collection to use. 6.1.1 Selecting a collection interface The idiom for a collection property in the Java domain model is always the same: private <<Interface>> images = new <<Implementation>>(); // Getter and setter methods [...]... typical Hibernate application ■ A java. util.SortedSet can be mapped with , and the sort attribute can be set to either a comparator or natural ordering for in-memory sorting Initialize the collection with a java. util.TreeSet instance ■ A java. util.List can be mapped with , preserving the position of each element with an additional index column in the collection table Initialize with a java. util.ArrayList... mapped with , preserving key and value pairs Use a java. util.HashMap to initialize a property Sets, bags, lists, and maps of value types 243 ■ A java. util.SortedMap can be mapped with element, and the sort attribute can be set to either a comparator or natural ordering for in-memory sorting Initialize the collection with a java. util.TreeMap instance ■ Arrays are supported by Hibernate with. .. collections of value types with annotations is different compared with mappings in XML; at the time of writing, it isn’t part of the Java Persistence standard but is available in Hibernate 6.3 Mapping collections with annotations The Hibernate Annotations package supports nonstandard annotations for the mapping of collections that contain value-typed elements, mainly org .hibernate. annotations.CollectionOfElements... important that you use the right combination Hibernate only wraps the collection object you’ve already initialized on declaration of the field (or sometimes replaces it, if it’s not the right one) Without extending Hibernate, you can choose from the following collections: ■ A java. util.Set is mapped with a element Initialize the collection with a java. util.HashSet The order of its elements isn’t... the Java Collections framework doesn’t include a bag implementation However, the java. util.Collection interface has bag semantics, so you only need a matching implementation You have two choices: Sets, bags, lists, and maps of value types 245 ■ Write the collection property with the java. util.Collection interface, and, on declaration, initialize it with an ArrayList of the JDK Map the collection in Hibernate. .. collection in Hibernate with a standard or element Hibernate has a built-in PersistentBag that can deal with lists; however, consistent with the contract of a bag, it ignores the position of elements in the ArrayList In other words, you get a persistent Collection ■ Write the collection property with the java. util.List interface, and, on declaration, initialize it with an ArrayList of the... java. util.ArrayList ■ A java. util.Collection can be mapped with or Java doesn’t have a Bag interface or an implementation; however, java. util Collection allows bag semantics (possible duplicates, no element order is preserved) Hibernate supports persistent bags (it uses lists internally but ignores the index of the elements) Use a java. util.ArrayList to initialize a bag collection ■ A java. util.Map... individual properties of the embeddable component Note that @org .hibernate. annotations.MapKey is a more powerful replacement for @javax .persistence. MapKey, which isn’t very useful (see chapter 7, section 7.2 .4 “Mapping maps”) 6.3.2 Sorted and ordered collections A collection can also be sorted or ordered with Hibernate annotations: @org .hibernate. annotations.CollectionOfElements @JoinTable( name = "ITEM_IMAGE",... with generic collections, you need to specify the element type with the targetElement attribute—in the previous example it’s therefore optional To map a persistent List, add @org .hibernate. annotations.IndexColumn with an optional base for the index (default is zero): @org .hibernate. annotations.CollectionOfElements @JoinTable( name = "ITEM_IMAGE", joinColumns = @JoinColumn(name = "ITEM_ID") ) @org .hibernate. annotations.IndexColumn(... complex collections with XML mapping metadata, and annotations Switching focus, we now consider collections with elements that aren’t value types, but references to other entity instances Many Hibernate users try to map a typical parent/children entity relationship, which involves a collection of entity references 6 .4 Mapping a parent/children relationship From our experience with the Hibernate user community, . right one). Without extending Hibernate, you can choose from the following collections: ■ A java. util.Set is mapped with a <set> element. Initialize the collection with a java. util.HashSet Ini- tialize with a java. util.ArrayList . ■ A java. util.Collection can be mapped with <bag> or <idbag> . Java doesn’t have a Bag interface or an implementation; however, java. util. Collection . types 245 ■ Write the collection property with the java. util.Collection interface, and, on declaration, initialize it with an ArrayList of the JDK. Map the collection in Hibernate with a