JavaPersistenceWithHibernate第二版笔记-第五章-Mapping value types-007UserTypes的用法(@org.hibernate.annotations.Type、@org.hibernate.annotations.TypeDefs、CompositeUserType、DynamicParameterizedType、、、)

一、结构

二、Hibernate支持的UserTypes接口

 UserType —You can transform values by interacting with the plain JDBC PreparedStatement (when storing data) and ResultSet (when loading data).By implementing this interface, you can also control how Hibernate caches and
dirty-checks values. The adapter for MonetaryAmount has to implement this interface.
 CompositeUserType —This extends UserType , providing Hibernate with more details about your adapted class. You can tell Hibernate that the MonetaryAmount component has two properties: amount and currency . You can then reference these properties in queries with dot notation: for example, select avg(i.buyNowPrice.amount) from Item i .
 ParameterizedUserType —This provides settings to your adapter in mappings.You have to implement this interface for the MonetaryAmount conversion,because in some mappings you want to convert the amount to US dollars and in
other mappings to Euros. You only have to write a single adapter and can customize its behavior when mapping a property.
 DynamicParameterizedType —This more powerful settings API gives you access to dynamic information in the adapter, such as the mapped column and table names. You might as well use this instead of ParameterizedUserType ; there is no additional cost or complexity.
 EnhancedUserType —This is an optional interface for adapters of identifier properties and discriminators. Unlike JPA converters, a UserType in Hibernate can be an adapter for any kind of entity property. Because MonetaryAmount won’t be the type of an identifier property or discriminator, you won’t need it.
 UserVersionType —This is an optional interface for adapters of version properties.
 UserCollectionType —This rarely needed interface is used to implement custom collections. You have to implement it to persist a non- JDK collection and preserve additional semantics.

 

三、代码

1.

  1 package org.jpwh.converter;
  2 
  3 import org.hibernate.engine.spi.SessionImplementor;
  4 import org.hibernate.type.StandardBasicTypes;
  5 import org.hibernate.type.Type;
  6 import org.hibernate.usertype.CompositeUserType;
  7 import org.hibernate.usertype.DynamicParameterizedType;
  8 import org.jpwh.model.advanced.MonetaryAmount;
  9 
 10 import java.io.Serializable;
 11 import java.lang.annotation.Annotation;
 12 import java.math.BigDecimal;
 13 import java.sql.PreparedStatement;
 14 import java.sql.ResultSet;
 15 import java.sql.SQLException;
 16 import java.util.Currency;
 17 import java.util.Properties;
 18 
 19 public class MonetaryAmountUserType
 20     implements CompositeUserType, DynamicParameterizedType {
 21 
 22     protected Currency convertTo;
 23 
 24     public void setParameterValues(Properties parameters) {
 25 
 26         /**
 27          * You can access some dynamic parameters here, such as the
 28          * name of the mapped columns, the mapped (entity) table, or even the
 29          * annotations on the field/getter of the mapped property. We won't need
 30          * them in this example though.
 31          */
 32         ParameterType parameterType =
 33             (ParameterType) parameters.get(PARAMETER_TYPE);
 34         String[] columns = parameterType.getColumns();
 35         String table = parameterType.getTable();
 36         Annotation[] annotations = parameterType.getAnnotationsMethod();
 37 
 38         /**
 39          * We only use the <code>convertTo</code> parameter to
 40          * determine the target currency when saving a value into the database.
 41          * If the parameter hasn't been set, we default to US Dollar.
 42          */
 43         String convertToParameter = parameters.getProperty("convertTo");
 44         this.convertTo = Currency.getInstance(
 45             convertToParameter != null ? convertToParameter : "USD"
 46         );
 47     }
 48 
 49     /**
 50      * The method <code>returnedClass</code> adapts the given class, in this case
 51      * <code>MonetaryAmount</code>.
 52      */
 53     public Class returnedClass() {
 54         return MonetaryAmount.class;
 55     }
 56 
 57     /**
 58      * Hibernate can enable some optimizations if it knows
 59      * that <code>MonetaryAmount</code> is immutable.
 60      */
 61     public boolean isMutable() {
 62         return false;
 63     }
 64 
 65     /**
 66      * If Hibernate has to make a copy of the value, it will call
 67      * this method. For simple immutable classes like <code>MonetaryAmount</code>,
 68      * you can return the given instance.
 69      */
 70     public Object deepCopy(Object value) {
 71         return value;
 72     }
 73 
 74     /**
 75      * Hibernate calls <code>disassemble</code> when it stores a value in the global shared second-level
 76      * cache. You need to return a <code>Serializable</code> representation. For <code>MonetaryAmount</code>,
 77      * a <code>String</code> representation is an easy solution. Or, because <code>MonetaryAmount</code> is actually
 78      * <code>Serializable</code>, you could return it directly.
 79      */
 80     public Serializable disassemble(Object value,
 81                                     SessionImplementor session) {
 82         return value.toString();
 83     }
 84 
 85     /**
 86      * Hibernate calls this method when it reads the serialized
 87      * representation from the global shared second-level cache. We create a
 88      * <code>MonetaryAmount</code> instance from the <code>String</code>
 89      * representation. Or, if have stored a serialized <code>MonetaryAmount</code>,
 90      * you could return it directly.
 91      */
 92     public Object assemble(Serializable cached,
 93                            SessionImplementor session, Object owner) {
 94         return MonetaryAmount.fromString((String) cached);
 95     }
 96 
 97     /**
 98      * Called during <code>EntityManager#merge()</code> operations, you
 99      * need to return a copy of the <code>original</code>. Or, if your value type is
100      * immutable, like <code>MonetaryAmount</code>, you can simply return the original.
101      */
102     public Object replace(Object original, Object target,
103                           SessionImplementor session, Object owner) {
104         return original;
105     }
106 
107     /**
108      * Hibernate will use value equality to determine whether the value
109      * was changed, and the database needs to be updated. We rely on the equality
110      * routine we have already written on the <code>MonetaryAmount</code> class.
111      */
112     public boolean equals(Object x, Object y) {
113         return x == y || !(x == null || y == null) && x.equals(y);
114     }
115 
116     public int hashCode(Object x) {
117         return x.hashCode();
118     }
119 
120     /**
121      * Called to read the <code>ResultSet</code>, when a
122      * <code>MonetaryAmount</code> value has to be retrieved from the database.
123      * We take the <code>amount</code> and <code>currency</code> values as given
124      * in the query result, and create a new instance of <code>MonetaryAmount</code>.
125      */
126     public Object nullSafeGet(ResultSet resultSet,
127                               String[] names,
128                               SessionImplementor session,
129                               Object owner) throws SQLException {
130 
131         BigDecimal amount = resultSet.getBigDecimal(names[0]);
132         if (resultSet.wasNull())
133             return null;
134         Currency currency =
135             Currency.getInstance(resultSet.getString(names[1]));
136         return new MonetaryAmount(amount, currency);
137     }
138 
139     /**
140      * Called when a <code>MonetaryAmount</code> value has
141      * to be stored in the database. We convert the value to the target currency,
142      * then set the <code>amount</code> and <code>currency</code> on the
143      * provided <code>PreparedStatement</code>. (Unless the <code>MonetaryAmount</code>
144      * was <code>null</code>, in that case, we call <code>setNull()</code> to
145      * prepare the statement.)
146      */
147     public void nullSafeSet(PreparedStatement statement,
148                             Object value,
149                             int index,
150                             SessionImplementor session) throws SQLException {
151 
152         if (value == null) {
153             statement.setNull(
154                 index,
155                 StandardBasicTypes.BIG_DECIMAL.sqlType());
156             statement.setNull(
157                 index + 1,
158                 StandardBasicTypes.CURRENCY.sqlType());
159         } else {
160             MonetaryAmount amount = (MonetaryAmount) value;
161             // When saving, convert to target currency
162             MonetaryAmount dbAmount = convert(amount, convertTo);
163             statement.setBigDecimal(index, dbAmount.getValue());
164             statement.setString(index + 1, convertTo.getCurrencyCode());
165         }
166     }
167 
168     /**
169      * Here you can implement whatever currency conversion routine
170      * you need. For the sake of the example, we simply double the value so we
171      * can easily test if conversion was successful. You'll have to replace this
172      * code with a real currency converter in a real application. It's not a
173      * method of the Hibernate <code>UserType</code> API.
174      */
175     protected MonetaryAmount convert(MonetaryAmount amount,
176                                      Currency toCurrency) {
177         return new MonetaryAmount(
178             amount.getValue().multiply(new BigDecimal(2)),
179             toCurrency
180         );
181     }
182 
183     public String[] getPropertyNames() {
184         return new String[]{"value", "currency"};
185     }
186 
187     public Type[] getPropertyTypes() {
188         return new Type[]{
189             StandardBasicTypes.BIG_DECIMAL,
190             StandardBasicTypes.CURRENCY
191         };
192     }
193 
194     public Object getPropertyValue(Object component,
195                                    int property) {
196         MonetaryAmount monetaryAmount = (MonetaryAmount) component;
197         if (property == 0)
198             return monetaryAmount.getValue();
199         else
200             return monetaryAmount.getCurrency();
201     }
202 
203     public void setPropertyValue(Object component,
204                                  int property,
205                                  Object value) {
206         throw new UnsupportedOperationException(
207             "MonetaryAmount is immutable"
208         );
209     }
210 
211     // ...
212 }

 

2.

 1 @org.hibernate.annotations.TypeDefs({
 2     @org.hibernate.annotations.TypeDef(
 3         name = "monetary_amount_usd",
 4         typeClass = MonetaryAmountUserType.class,
 5         parameters = {@Parameter(name = "convertTo", value = "USD")}
 6     ),
 7     @org.hibernate.annotations.TypeDef(
 8         name = "monetary_amount_eur",
 9         typeClass = MonetaryAmountUserType.class,
10         parameters = {@Parameter(name = "convertTo", value = "EUR")}
11     )
12 })
13 package org.jpwh.converter;
14 
15 import org.hibernate.annotations.Parameter;

 

3.

 1 package org.jpwh.model.advanced.usertype;
 2 
 3 import org.jpwh.model.advanced.MonetaryAmount;
 4 
 5 import javax.persistence.Column;
 6 import javax.persistence.Entity;
 7 import javax.persistence.GeneratedValue;
 8 import javax.persistence.Id;
 9 import javax.validation.constraints.NotNull;
10 
11 @Entity
12 public class Item {
13 
14     @Id
15     @GeneratedValue(generator = "ID_GENERATOR")
16     protected Long id;
17 
18     @NotNull
19     protected String name;
20 
21     @NotNull
22     @org.hibernate.annotations.Type(
23         type = "monetary_amount_usd"
24     )
25     @org.hibernate.annotations.Columns(columns = {
26         @Column(name = "BUYNOWPRICE_AMOUNT"),
27         @Column(name = "BUYNOWPRICE_CURRENCY", length = 3)
28     })
29     protected MonetaryAmount buyNowPrice;
30 
31     @NotNull
32     @org.hibernate.annotations.Type(
33         type = "monetary_amount_eur"
34     )
35     @org.hibernate.annotations.Columns(columns = {
36         @Column(name = "INITIALPRICE_AMOUNT"),
37         @Column(name = "INITIALPRICE_CURRENCY", length = 3)
38     })
39     protected MonetaryAmount initialPrice;
40 
41     public Long getId() {
42         return id;
43     }
44 
45     public String getName() {
46         return name;
47     }
48 
49     public void setName(String name) {
50         this.name = name;
51     }
52 
53     public MonetaryAmount getBuyNowPrice() {
54         return buyNowPrice;
55     }
56 
57     public void setBuyNowPrice(MonetaryAmount buyNowPrice) {
58         this.buyNowPrice = buyNowPrice;
59     }
60 
61     public MonetaryAmount getInitialPrice() {
62         return initialPrice;
63     }
64 
65     public void setInitialPrice(MonetaryAmount initialPrice) {
66         this.initialPrice = initialPrice;
67     }
68 
69     // ...
70 }

 

4.

 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 }

 

5.

 

posted @ 2016-04-07 18:25  shamgod  阅读(1299)  评论(0编辑  收藏  举报
haha