一、结构
二、代码
1.
1 package org.jpwh.model.advanced; 2 3 import java.io.Serializable; 4 import java.math.BigDecimal; 5 import java.util.Currency; 6 7 /* 8 This value-typed class should be <code>java.io.Serializable</code>: When Hibernate stores entity 9 instance data in the shared second-level cache (see <a href="#Caching"/>), it <em>disassembles</em> 10 the entity's state. If an entity has a <code>MonetaryAmount</code> property, the serialized 11 representation of the property value will be stored in the second-level cache region. When entity 12 data is retrieved from the cache region, the property value will be deserialized and reassembled. 13 */ 14 public class MonetaryAmount implements Serializable { 15 16 /* 17 The class does not need a special constructor, you can make it immutable, even with 18 <code>final</code> fields, as your code will be the only place an instance is created. 19 */ 20 protected final BigDecimal value; 21 protected final Currency currency; 22 23 public MonetaryAmount(BigDecimal value, Currency currency) { 24 this.value = value; 25 this.currency = currency; 26 } 27 28 public BigDecimal getValue() { 29 return value; 30 } 31 32 public Currency getCurrency() { 33 return currency; 34 } 35 36 /* 37 You should implement the <code>equals()</code> and <code>hashCode()</code> 38 methods, and compare monetary amounts "by value". 39 */ 40 public boolean equals(Object o) { 41 if (this == o) return true; 42 if (!(o instanceof MonetaryAmount)) return false; 43 44 final MonetaryAmount monetaryAmount = (MonetaryAmount) o; 45 46 if (!value.equals(monetaryAmount.value)) return false; 47 if (!currency.equals(monetaryAmount.currency)) return false; 48 49 return true; 50 } 51 52 public int hashCode() { 53 int result; 54 result = value.hashCode(); 55 result = 29 * result + currency.hashCode(); 56 return result; 57 } 58 59 /* 60 You will need a <code>String</code> representation of a monetary 61 amount. Implement the <code>toString()</code> method and a static method to 62 create an instance from a <code>String</code>. 63 */ 64 public String toString() { 65 return getValue() + " " + getCurrency(); 66 } 67 68 public static MonetaryAmount fromString(String s) { 69 String[] split = s.split(" "); 70 return new MonetaryAmount( 71 new BigDecimal(split[0]), 72 Currency.getInstance(split[1]) 73 ); 74 } 75 }
2.
1 package org.jpwh.converter; 2 3 import org.jpwh.model.advanced.MonetaryAmount; 4 5 import javax.persistence.AttributeConverter; 6 import javax.persistence.Converter; 7 8 @Converter(autoApply = true) // Default for MonetaryAmount properties 9 public class MonetaryAmountConverter 10 implements AttributeConverter<MonetaryAmount, String> { 11 12 @Override 13 public String convertToDatabaseColumn(MonetaryAmount monetaryAmount) { 14 return monetaryAmount.toString(); 15 } 16 17 @Override 18 public MonetaryAmount convertToEntityAttribute(String s) { 19 return MonetaryAmount.fromString(s); 20 } 21 }
3.
1 package org.jpwh.model.advanced.converter; 2 3 4 import org.jpwh.converter.MonetaryAmountConverter; 5 import org.jpwh.model.advanced.MonetaryAmount; 6 7 import javax.persistence.Column; 8 import javax.persistence.Convert; 9 import javax.persistence.Entity; 10 import javax.persistence.GeneratedValue; 11 import javax.persistence.Id; 12 import javax.validation.constraints.NotNull; 13 import java.util.Date; 14 15 @Entity 16 public class Item { 17 18 @Id 19 @GeneratedValue(generator = "ID_GENERATOR") 20 protected Long id; 21 22 @NotNull 23 protected String name; 24 25 @NotNull 26 @Convert( // Optional, autoApply is enabled 27 converter = MonetaryAmountConverter.class, 28 disableConversion = false) 29 @Column(name = "PRICE", length = 63) 30 protected MonetaryAmount buyNowPrice; 31 32 @NotNull 33 protected Date createdOn = new Date(); 34 35 public Long getId() { 36 return id; 37 } 38 39 public String getName() { 40 return name; 41 } 42 43 public void setName(String name) { 44 this.name = name; 45 } 46 47 public MonetaryAmount getBuyNowPrice() { 48 return buyNowPrice; 49 } 50 51 public void setBuyNowPrice(MonetaryAmount buyNowPrice) { 52 this.buyNowPrice = buyNowPrice; 53 } 54 55 // ... 56 }
4.
1 package org.jpwh.model.advanced.converter; 2 3 abstract public class Zipcode { 4 5 protected String value; 6 7 public Zipcode(String value) { 8 this.value = value; 9 } 10 11 public String getValue() { 12 return value; 13 } 14 15 @Override 16 public boolean equals(Object o) { 17 if (this == o) return true; 18 if (o == null || getClass() != o.getClass()) return false; 19 Zipcode zipcode = (Zipcode) o; 20 return value.equals(zipcode.value); 21 } 22 23 @Override 24 public int hashCode() { 25 return value.hashCode(); 26 } 27 }
5.
1 package org.jpwh.converter; 2 3 import org.jpwh.model.advanced.converter.GermanZipcode; 4 import org.jpwh.model.advanced.converter.SwissZipcode; 5 import org.jpwh.model.advanced.converter.Zipcode; 6 7 import javax.persistence.AttributeConverter; 8 import javax.persistence.Converter; 9 10 @Converter 11 public class ZipcodeConverter 12 implements AttributeConverter<Zipcode, String> { 13 14 @Override 15 public String convertToDatabaseColumn(Zipcode attribute) { 16 return attribute.getValue(); 17 } 18 19 @Override 20 public Zipcode convertToEntityAttribute(String s) { 21 if (s.length() == 5) 22 return new GermanZipcode(s); 23 else if (s.length() == 4) 24 return new SwissZipcode(s); 25 26 // If you get to this point, you should consider 27 // cleaning up your database... or you can create 28 // an InvalidZipCode subclass and return it here. 29 30 throw new IllegalArgumentException( 31 "Unsupported zipcode in database: " + s 32 ); 33 } 34 }
6.
1 package org.jpwh.model.advanced.converter; 2 3 public class GermanZipcode extends Zipcode { 4 5 public GermanZipcode(String value) { 6 super(value); 7 } 8 }
7.
1 package org.jpwh.model.advanced.converter; 2 3 public class SwissZipcode extends Zipcode { 4 5 public SwissZipcode(String value) { 6 super(value); 7 } 8 }
8.
1 package org.jpwh.model.advanced.converter; 2 3 import org.jpwh.converter.ZipcodeConverter; 4 import org.jpwh.model.Constants; 5 6 import javax.persistence.Convert; 7 import javax.persistence.Entity; 8 import javax.persistence.GeneratedValue; 9 import javax.persistence.Id; 10 import javax.persistence.Table; 11 import javax.validation.constraints.NotNull; 12 import java.io.Serializable; 13 14 @Entity 15 @Table(name = "USERS") 16 public class User implements Serializable { 17 18 @Id 19 @GeneratedValue(generator = Constants.ID_GENERATOR) 20 protected Long id; 21 22 @NotNull 23 protected String username; 24 25 // Group multiple attribute conversions with @Converts 26 @Convert( 27 converter = ZipcodeConverter.class, 28 attributeName = "zipcode" // Or "city.zipcode" for nested embeddables 29 ) 30 protected Address homeAddress; 31 32 public Long getId() { 33 return id; 34 } 35 public String getUsername() { 36 return username; 37 } 38 39 public void setUsername(String username) { 40 this.username = username; 41 } 42 43 public Address getHomeAddress() { 44 return homeAddress; 45 } 46 47 public void setHomeAddress(Address homeAddress) { 48 this.homeAddress = homeAddress; 49 } 50 51 // ... 52 }
9.
1 package org.jpwh.model.advanced.converter; 2 3 import javax.persistence.Column; 4 import javax.persistence.Embeddable; 5 import javax.validation.constraints.NotNull; 6 7 @Embeddable 8 public class Address { 9 10 @NotNull 11 @Column(nullable = false) 12 protected String street; 13 14 @NotNull 15 @Column(nullable = false, length = 5) 16 protected Zipcode zipcode; 17 18 @NotNull 19 @Column(nullable = false) 20 protected String city; 21 22 protected Address() { 23 } 24 25 public Address(String street, Zipcode zipcode, String city) { 26 this.street = street; 27 this.zipcode = zipcode; 28 this.city = city; 29 } 30 31 public String getStreet() { 32 return street; 33 } 34 35 public void setStreet(String street) { 36 this.street = street; 37 } 38 39 public Zipcode getZipcode() { 40 return zipcode; 41 } 42 43 public void setZipcode(Zipcode zipcode) { 44 this.zipcode = zipcode; 45 } 46 47 public String getCity() { 48 return city; 49 } 50 51 public void setCity(String city) { 52 this.city = city; 53 } 54 }
也可以转换集合,If several @Convert annotations are required on a single embedded property, to convert several attributes of the Address , for example, you can group them within an @Converts annotation. You can also apply converters to values of collections and maps, if their values and/or keys are of basic or embeddable type. For example, you can add the @Convert annotation on a persistent Set<Zipcode> . We’ll show you how
to map persistent collections later, with @ElementCollection , in chapter 7.
For persistent maps, the attributeName option of the @Convert annotation has some special syntax:
On a persistent Map<Address, String> , you can apply a converter for the zipcode property of each map key with the attribute name key.zipcode .
On a persistent Map<String, Address> , you can apply a converter for the zipcode property of each map value with the attribute name value.zipcode .
On a persistent Map<Zipcode, String> , you can apply a converter for the key of each map entry with the attribute name key .
On a persistent Map<String, Zipcode> , you can apply a converter for the value of each map entry by not setting any attributeName .
As before, the attribute name can be a dot-separated path if your embeddable classes are nested; you can write key.city.zipcode to reference the zipcode property of the City class, in a composition with the Address class.
Some limitations of the JPA converters are as follows:
You can’t apply them to identifier or version properties of an entity.
You shouldn’t apply a converter on a property mapped with @Enumerated or @Temporal , because these annotations already declare what kind of conversion has to occur. If you want to apply a custom converter for enums or date/time properties, don’t annotate them with @Enumerated or @Temporal .
You can apply a converter to a property mapping in an hbm.xml file, but you have to prefix the name: type="converter:qualified.ConverterName" .