说说 Hibernate 的映射策略
来源:互联网 发布:计算机算法有几个 编辑:程序博客网 时间:2024/06/05 22:24
1 基本属性映射
持久化类属性的 JPA 规则是:
- 持久化类的属性如果是基本类型或者基本类型的包装器,诸如
String, BigInteger, BigDecimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, byte[], Byte[], char[], Character[]
,它们会被自动持久化。 - 如果一个类加了
@Embeddable
注解,表明这个类是属于其他某个类的一部分,它是内嵌的类,我们以后会说到。 - 如果属性类型是
java.io.Serializable
,那么它的值会被存储为序列化后的格式。 - 如果以上条件都不成立,那么 Hibernate 会在启动时抛出异常。
1.1 覆盖基本属性的默认值
如果某个属性不需要被持久化,可以加上 @javax.persistence.Transient
注解或者使用 java 的 transient
关键字。
默认情况下,所有的可持久化属性都是可为 null ,既是可选的。因此可以使用 @Basic
注解把某个属性改为必填,像这样:
@Basic(optional = false)BigDecimal initialPrice;
这样配置以后,Hibernate 会在生成 SQL schema 时,把这个属性设置为非 null。如果在插入或者更新时,这个属性是 null,那么 Hibernate 就会抛出异常。
大多数工程师们更喜欢用 @Column
注解来声明非 null:
@Column(nullable = false)BigDecimal initialPrice;
也就是说@Basic、@Column
以及之前所说的 Bean Validation 的 @NotNull
,它们的功能都一样,配置其中一个后,Hibernate 会对这个属性进行非 null 验证。建议使用 Bean Validation 的 @NotNull
,因为这样就能够在表现层手动验证一个 Item 实例。
关于 Bean Validation 的内容请参见 说说 Hibernate 领域模型与库表结构设计
@Column
也能够指定需要映射的表字段名:
@Column(name = "START_PRICE", nullable = false)BigDecimal initialPrice;
@Column
还有一些属性设定,比如可以设定 catalog、schema 的名字,但是它们在实践中很少会被用到。
1.2 自定义存取属性的方式
可以选择是通过字段来直接存取,还是通过 getter/setter 方法来间接存取。
Hibernate 是依据持久化类的 @Id
注解来判断到底是采取哪种方式的。比如 @Id
放在某个属性上,那么所有的属性都会是通过字段来直接存取的。
JPA 规范中还提供了 @Access
注解,它有两个值,AccessType.FIELD
(通过字段来直接存取) 和 AccessType.PROPERTY
(通过 getter/setter 方法来间接存取)。可以把这个注解放在类上,这样就会应用于所有的属性,也可以放在某个类属性上,对它进行精细控制:
@Entitypublic class Item { @Id @GeneratedValue(generator = Constants.ID_GENERATOR) protected Long id; @Access(AccessType.PROPERTY) @Column(name = "ITEM_NAME")//Mapping are still expected here! protected String name; public String getName() { return name; } public void setName(String name) { this.name = !name.startsWith("AUCTION:") ? "AUCTION:" + name : name; }}
Hibernate 还有一个特性可以会很少用到,这里提一提。noop
级别的存取器。假设数据库表有一个 VALIDATED
字段,表示有效时间,但是没有把它放在领域类模型中(这种情况应该很少发生吧 O(∩_∩)O~)。为了能够对这个字段进行 SQL 查询,必须把它放在 hbm.xml 的 metadata 文件中:
<hibernate-mapping> <class name="Item"> <id name="id"> ... </id> <property name="validated" column="VALIDATED" access="noop"/> </property> </class></hibernate-mapping>
做了这样的映射以后,就能够在查询中使用 validated 咯。注意之前说过,如果使用了 Hibernate 的 hbm.xml 的 metadata 文件进行配置,那么所有在 Item 类上的持久化注解就都失效咯。
如果以上的映射还不能满足要求,那么可以直接自定义一个属性存取器!只要实现一个 org.hibernate.property.PropertyAccessor
接口,然后配置到下面这个注解中:
@org.hibernate.annotations.AttributeAccessor("my.custom.Accessor")
这是 Hibernate 4.3 + 新增的特性。
1.3 使用衍生出来的属性
这种属性的值是执行 SQL 语句后实时计算出来的,我们可以使用 @org.hibernate.annotations.Formula
注解:
@org.hibernate.annotations.Formula( "substr (DESCRIPTION, 1, 12) || '...'")protected String shortDescription;@org.hibernate.annotations.Formula( " (select avg(b.AMOUNT) from BID b where b.ITEM_ID = ID)")protected BigDecimal averageBidAmount;
当每次从数据库中获取 Item 后,这些属性就会被重新计算。Hibernate 会把这些属性放入到 select 查询中作为查询条件的一部分。
1.4 转换某列的值
假设表中有一个字段叫 IMPERIALWEIGHT
,它表示的重量,单位是磅。但是应用系统需要的重要单位是公斤,这就需要加入转换注解进行配置:
@Column(name = "IMPERIALWEIGHT")@org.hibernate.annotations.ColumnTransformer( read ="IMPERIALWEIGHT/ 2.20462", write = "? * 2.20462")protected double metricWeight;
这样配置后,就可以直接应用于 HQL:
List<Item> result = em.createQuery("select i from i where i.metricWeight = :w").setParameter("w",2.0).getResultList();
注意:这种方式生成的 SQL 语句可能无法直接利用数据库配置的索引。
1.5 配置属性的默认值
数据库可以配置某个字段的默认值,当插入新数据时,如果这个字段没有设置新值时,会把这个字段设置为默认值。
一般来说,当数据库自动为这个字段设置为默认值时,Hibernate 框架应该要更新相应的实体类实例才是。这可以通过配置 @org.hibernate.annotations.Generated annotation
注解来实现:
@Temporal(TemporalType.TIMESTAMP)@Column(insertable = false, updatable = false)@org.hibernate.annotations.Generated( org.hibernate.annotations.GenerationTime.ALWAYS)protected Date lastModified;@Column(insertable = false)@org.hibernate.annotations.ColumnDefault("1.00")@org.hibernate.annotations.Generated( org.hibernate.annotations.GenerationTime.INSERT)protected BigDecimal initialPrice;
GenerationTime.ALWAYS
表示每次发生 新增或者更新 SQL 操作后,Hibernate 都会更新实例。在属性上也可以配置 @Column(insertable = false, updatable = false)
,这样这个属性就变成了只读属性了,也就是说这个属性只能通过数据库来生成咯。
@org.hibernate.annotations.ColumnDefault
用于配置默认值,这样 Hibernate 会在导出 schema DDL 的 SQL 时,加上字段默认值。
1.6 临时属性
有的属性,比如 timestamp 属性,会在插入数据后由数据库自动生成,然后它的值就不会再变化了:
@Temporal(TemporalType.TIMESTMAP)@Column(updatable = false)@org.hibernate.annotations.CreationTimestampprotected Date createdOn;//Java 8 APIprotected Instant reviewedOn;
Hibernate 支持 /Java 8 中的 java.time
类包。
@Temporal
中的 TemporalType 的可选选项有 DATE、TIME、TIMESTAMP,表示的是这个属性是以何种时间字段类型存储在数据库中的。
这里如果没有配置 @Temporal
注解,Hibernate 会设置为 TemporalType.TIMESTMAP
。@org.hibernate.annotations.CreationTimestamp
插入时,是由数据库自动生成值,然后 Hibernate 自动刷新。还有一个 @org.hibernate.annotations.UpdateTimestamp
注解与 CreationTimestamp
注解类似,只不过它是检测数据更新时的变化。
1.7 映射枚举类型
假设需要一个拍卖类型:
public enum AuctionType{ HIGHEST_BID, LOWEST_BID, FIEXED_PRICE}
可以这样映射:
@NotNull@Enumerated(EnumType.AuctionType)protected AuctionType auctionType = AuctionType.HIGHEST_BID;
@Enumerated
的默认值是 EnumType.ORDINAL
,这不好用,因为 ORDINAL 是枚举中元素的序列值(从 1 开始),这在设置默认值时,表现的不清晰。所以最好把它配置为 EnumType.AuctionType
。
2 映射嵌套类
假设我们的 User 类里面有两个属性(home、billing)都是 Address 类的类型,即它们都是 User 类的一部分(图中表现的是一种整体与部分的包含关系)。
2.1 数据库 schema
嵌套类 Address 没有自己的 ID,因为它必须属于某个持久化类,比如这里的 User 类。嵌套类的生命周期依赖于它的归属类,如果保存了归属类,那么它的嵌套类也会被保存。
2.2 建立嵌套类
@Embeddable//instead of @Entitypublic class Address { @NotNull//Ignored for DDL generation @Column(nullable = false)//Used for DDL generation protected String street; @NotNull @Column(nullable = false,length = 5) protected String zipcode; @NotNull @Column(nullable = false) protected String city; //No-args constructor protected Address(){ } /** * Convenience constructor * @param street * @param zipcode * @param city */ public Address(String street, String zipcode, String city) { this.street = street; this.zipcode = zipcode; this.city = city; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getZipcode() { return zipcode; } public void setZipcode(String zipcode) { this.zipcode = zipcode; } public String getCity() { return city; } public void setCity(String city) { this.city = city; }}
- 一个嵌套类不会有唯一标识的属性。
- Hibernate 会调用无参的构造函数来创建一个实例。
- 可以加一个带参的构造函数,方便调用。
注意: 在嵌套类中定义的 @NotNull
属性,不会被 Hibernate 用于生成数据库的 schema。它只会被用于在运行时对 Bean 进行验证(Bean Validation)。所以我们要使用 @Column(nullable = false)
对 schema 进行控制。
下面是主类的代码:
@Entity@Table(name = "USERS")public class User implements Serializable { @Id @GeneratedValue(generator = Constants.ID_GENERATOR) protected Long id; public Long getId() { return id; } //The Address is @Embeddable; no annotation is needed here. protected Address homeAddress; public Address getHomeAddress() { return homeAddress; } public void setHomeAddress(Address homeAddress) { this.homeAddress = homeAddress; }}
因为 Address 类是嵌套类,所以 Hibernate 会自动检测。
包含嵌套类的类,它的属性存取策略举例如下:
- 如果嵌套类的 @Entity 上配置了 field 存取机制——@Access(AccessType.FIELD),那么它的所有属性都使用 field 机制。
- 如果嵌套类的 @Entity 上配置了属性 存取机制——@Access(AccessType.PROPERTY),那么它的所有属性都使用属性存取机制。
- 如果主类在属性的 getter/setter 方法上加了 @Access(AccessType.PROPERTY),那么它就会使用属性存取机制。
- 如果把 @Access 配在主类上,那么就会依据配置来决定属性的存取策略。
当一个 User 没有 Address 信息时,会返回 null。
2.3 覆盖嵌套类的默认属性
假设 User 类还有一个 billingAdress 属性,它也是 Address 类型的,这就与之前的 homeAddress 属性发生冲突,所以我们要它进行覆盖处理:
@Embedded@AttributeOverrides({ @AttributeOverride(name="street",column = @Column(name = "BILLING_STREET")), @AttributeOverride(name = "zipcode",column = @Column(name = "BILLING_ZIPCODE",length = 5)), @AttributeOverride(name = "city",column = @Column(name = "BILLING_CITY"))})protected Address billingAddress;public Address getBillingAddress() { return billingAddress;}public void setBillingAddress(Address billingAddress) { this.billingAddress = billingAddress;}
@Embedded 在这里其实是多余的,它可能只在需要映射第三方包的类时,才有用。
@AttributeOverrides 会覆盖嵌套类中的配置,在示例中,我们把嵌套类中的三个属性都映射到了三个不同的字段。
2.4 映射多层嵌套类
假设我们定义了多个层级嵌套类,Address 是 User 的嵌套类,City 又是 Address 的嵌套类:
Address 类:
@Embeddablepublic class Address { @NotNull @Column(nullable = false) protected String street; @NotNull @AttributeOverrides( @AttributeOverride(name = "name", column = @Column(name = "CITY", nullable = false)) ) protected City city; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public City getCity() { return city; } public void setCity(City city) { this.city = city; }}
City 类:
@Embeddablepublic class City { @NotNull @Column(nullable = false, length = 5)//Override VARCHAR(255) protected String zipcode; @NotNull @Column(nullable = false) protected String name; @NotNull @Column(nullable = false) protected String country;}
无论嵌套层级定义了有多深,Hibernate 都会理顺它们之间的关系。
可以在任何层级的类属性上加上 @AttributeOverrides ,覆盖默认的列名映射。这些层级都可以使用点语法,比如像这样:Address#City#name。
2.5 使用转换器实现从 Java 类型到 SQL 类型的映射
2.5.1 内建类型
2.5.1.1 原生与数字类型
这里的名称指的是 Hibernate 定义的名称,它会在后面的自定义类型映射中用到。
Hibernate 会把 ANSI SQL 类型转换为实际配置的数据库方言类型。
NUMERIC 类型包含整数位数和精度,对于一个 BigDecimal 属性,它对应的类型是 NUMERIC(19,2)。可以使用 @Column 对整数位数和精度进行精细控制。
2.5.1.2 字符类型
使用 @Column(length=...)
或者 @Length
对属性进行标注后, Hibernate 会自动为属性选择最合适的字符串类型。比如 MySQL 数据库,如果长度在 65535 内,会选择 VARCHAR 类型,如果长度在 65536 ~ 16777215 之间,会选择 MEDIUMTEXT 类型,更长的属性会选择 LONGTEXT 类型。
2.5.1.3 日期和时间类型
列表中包含了 Java 8 的 java.time API,这是 Hibernate 独有的,它们并没有包含在 JPA 2.1 中。
如果存储了一个 java.util.Date
类型的值,获取时,该值类型会是 java.sql.Date
。这可能会在比较对象是否相等时发生问题。要把时间转换为 Unix 的毫秒时间数,使用这种方式进行比较: aDate.getTime() > bDate.getTime()
特别是在集合中(诸如 HashSet)包含 java.util.Date, java.sql.Date|Time|Timestamp
,元素比较方法的差异。最好的方法就是保持类型的统一一致。
2.5.1.4 二进制类型以及大数据类型
VARBINARY 类型是依赖于实际配置的数据库。
在 JPA 中有一个很方便的 @Lob:
@Entitypublic class Item { @Lob protected byte[] image; @Lob protected String descripton;}
这里会把 byte[] 映射为 BlOB 类型,把 String 映射为 CLOB 类型。可惜的是,这样无法实现懒加载大数据的字段。
所以建议使用 java.sql.Clob
和 java.sql.Blob
类型实现懒加载:
@Entitypublic class Item { @Lob protected java.sql.Blob image; @Lob protected java.sql.Clob descripton;}
甚至可以把大数据类型的字段通过字节流的方式来读取:
Item item = em.find(Item.class, ITEM_ID);//You can stream the bytes directly...InputStream imageDataStream = item.getImageBlob().getBinaryStream();//or materialize them to memory:ByteArrayOutputStream outputStream = new ByteArrayOutputStream();StreamUtils.copy(imageDataStream, outputStream);byte[] imageBytes = outputStream.toByteArray();
Hibernate 还提供了一些便利方法,比如直接从 InputStream 中一次性读取固定字节长度的数据用于持久化操作,这样做就不会消耗内存:
//Need the native Hibernate APISession session = em.unwrap(Session.class);//You need to know the number of bytes you want to read from the stream!Blob blob = session.getLobHelper().createBlob(imageInputStream, byteLength);someItem.setImageBlob(blob);em.persist(someItem);
这里会把字节流的数据存储为 VARBINARY 类型的字段。
2.5.1.5 选择一个类型适配器
可以显式配置一个特殊的适配器注解:
@Entitypublic class Item{ @org.hibernate.annotations.Type(type = "yes_no") protected boolean verified = false;}
这样配置后,会把 boolean 值转换为 Y 或者 N 后再进行存储。
2.5.2 创建一个自定义的 JPA 转换器
在我们的在线拍卖系统中,新增了一个需求,要求系统能够支持多种货币计算的功能。传统的做法是修改表结构,然后再修改相应的代码。另外一种方法是使用一个自定义的 JPA 转换器。
public class MonetaryAmount implements Serializable { protected final BigDecimal value; protected final Currency currency; public MonetaryAmount(BigDecimal value, Currency currency) { this.value = value; this.currency = currency; } public BigDecimal getValue() { return value; } public Currency getCurrency() { return currency; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MonetaryAmount that = (MonetaryAmount) o; if (value != null ? !value.equals(that.value) : that.value != null) return false; return !(currency != null ? !currency.equals(that.currency) : that.currency != null); } @Override public int hashCode() { int result = value != null ? value.hashCode() : 0; result = 31 * result + (currency != null ? currency.hashCode() : 0); return result; } @Override public String toString() { return "MonetaryAmount{" + "value=" + value + ", currency=" + currency + '}'; } public static MonetaryAmount fromString(String s) { String[] split = s.split(" "); return new MonetaryAmount(new BigDecimal(split[0]), Currency.getInstance(split[1])); }}
必须实现 equals() 和 hashCode() 方法,让 MonetaryAmount 的实例可以依据值进行比较。
2.5.2.1 转换基本属性
必须实现一个 toString() 方法,用于存储。fromString() 方法用于把数据从库表中加载后,在还原为 MonetaryAmount。这可以通过实现 AttributeConverter 接口来定义。
@Converter(autoApply = true)//Default for MonetaryAmount propertiespublic class MonetaryAmountConverter implements AttributeConverter<MonetaryAmount, String> { @Override public String convertToDatabaseColumn(MonetaryAmount attribute) { return attribute.toString(); } @Override public MonetaryAmount convertToEntityAttribute(String dbData) { return MonetaryAmount.fromString(dbData); }}
然后我们开始定义 MonetaryAmount 类型的属性:
@Entitypublic class Item { ... @NotNull @Convert(//Optional:autoApply is enabled. converter = MonetaryAmountConverter.class, disableConversion = false ) @Column(name = "PRICE", length = 63) protected MonetaryAmount buyNowPrice;}
@Convert 可以对转换器进行精细控制。
2.5.2.2 转换组件级别的属性
假设我们有一个抽象的 Zipcode 类,它有两个实现,一个是德国的邮政编码(5位数),一个是瑞士的邮政编码(4位数)。
Zipcode:
abstract public class Zipcode { protected String value; public Zipcode(String value) { this.value = value; } public String getValue() { return value; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Zipcode zipcode = (Zipcode) o; return !(value != null ? !value.equals(zipcode.value) : zipcode.value != null); } @Override public int hashCode() { return value != null ? value.hashCode() : 0; }}
GermanZipcode 类没有什么特别的:
public class GermanZipcode extends Zipcode { public GermanZipcode(String value) { super(value); }}
两个实现类的区别,我们放到转换器中去处理:
@Converterpublic class ZipcodeConverter implements AttributeConverter<Zipcode, String> { @Override public String convertToDatabaseColumn(Zipcode attribute) { return attribute.getValue(); } @Override public Zipcode convertToEntityAttribute(String s) { if (s.length() == 5) return new GermanZipcode(s); //If you get to this point,consider cleaning up your database... or create an // InvalidZipCode subclass and return it here. throw new IllegalArgumentException("Unsupported zipcode in database: " + s); }}
接下来,就是配置上面定义的这个转换器啦:
@Entity@Table(name = "USERS")public class User implements Serializable { ... @Convert(//Group multiple attribute conversions with @Converts converter = ZipcodeConverter.class, attributeName = "zipcode"//Or "city.zipcode" for nested embeddables ) protected Address homeAddress;}
这里的 @Convert 注解分为以下几种情况,来配置属性名称:
- 如果是
Map<Address, String>
结构,通过 key.zipcode,来配置属性名。 - 如果是
Map<String, Address>
结构,通过 value.zipcode,来配置属性名。 - 如果是
Map<Zipcode, String>
结构,通过 key,来配置属性名。 - 如果是
Map<String, Zipcode>
结构,通过 value,来配置属性名。
2.5.3 使用 UserTypes 来对 HIbernate 进行扩展
假设这样的场景,我们的 Item#buyNowprice 需要存储美元类型的货币值,而 Item#initialPrice 需要存储欧元。不要怀疑,现实的世界常常就是这么奇葩。但是标准的 JPA 不支持一次映射多个属性。所以我们要扩展。
2.5.3.1 扩展点
Hibernate 提供了以下这些扩展接口:
- UserType:内部使用 JDBC 的 PreparedStatement 和 ResultSet 进行转换。
- CompositeUserType:扩展了 UserType。
- ParameterizedUserType:提供了一些在映射方面的设置。
- DynamicParameterizedType:最强大的接口,可以动态写入映射表和字段。
- EnhancedUserType:用于适配主键级别的属性。
- UserVersionType:用于适配版本属性。
- UserCollectonType:用于适配自定义的集合属性。(很少用到)
2.5.3.2 自定义 UserType
public class MonetaryAmountUserType implements CompositeUserType, DynamicParameterizedType { @Override public String[] getPropertyNames() { return new String[]{"value", "currency"}; } @Override public Type[] getPropertyTypes() { return new Type[]{StandardBasicTypes.BIG_DECIMAL, StandardBasicTypes.CURRENCY}; } @Override public Object getPropertyValue(Object component, int property) throws HibernateException { MonetaryAmount monetaryAmount = (MonetaryAmount) component; if (property == 0) return monetaryAmount.getValue(); else return monetaryAmount.getCurrency(); } @Override public void setPropertyValue(Object component, int property, Object value) throws HibernateException { throw new UnsupportedOperationException("MonetaryAmount is immutable"); } @Override public Class returnedClass() {//Adapts class return MonetaryAmount.class; } @Override public boolean equals(Object x, Object y) throws HibernateException { return x == y || !(x == null || y == null) && x.equals(y); } @Override public int hashCode(Object x) throws HibernateException { return x.hashCode(); } /** * Reads ResultSet * * @param rs * @param names * @param session * @param owner * @return * @throws HibernateException * @throws SQLException */ @Override public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { BigDecimal amount = rs.getBigDecimal(names[0]); if (rs.wasNull()) return null; Currency currency = Currency.getInstance(rs.getString(names[1])); return new MonetaryAmount(amount, currency); } /** * Stores MonetaryAmount * * @param st * @param value * @param index * @param session * @throws HibernateException * @throws SQLException */ @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { if (value == null) { st.setNull(index, StandardBasicTypes.BIG_DECIMAL.sqlType()); st.setNull(index + 1, StandardBasicTypes.CURRENCY.sqlType()); } else { MonetaryAmount amount = (MonetaryAmount) value; MonetaryAmount dbAmount = convert(amount, convertTo);//When saving, convert to // target currency st.setBigDecimal(index, dbAmount.getValue()); st.setString(index + 1, convertTo.getCurrencyCode()); } } protected MonetaryAmount convert(MonetaryAmount amount, Currency toCurrency) { return new MonetaryAmount(amount.getValue().multiply(new BigDecimal(2)), toCurrency); } @Override public Object deepCopy(Object value) throws HibernateException {//Copies value return value; } @Override public boolean isMutable() {//Enables optimizations return false; } /** * Returns Serializable representation * * @param value * @param session * @return * @throws HibernateException */ @Override public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException { return value.toString(); } /** * Creates MonetaryAmount instance * * @param cached * @param session * @param owner * @return * @throws HibernateException */ @Override public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException { return MonetaryAmount.fromString((String) cached); } /** * Returns copy of original * * @param original * @param target * @param session * @param owner * @return * @throws HibernateException */ @Override public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException { return original; } protected Currency convertTo; @Override public void setParameterValues(Properties parameters) { ParameterType parameterType = (ParameterType) parameters.get(PARAMETER_TYPE); //Accesses dynamic parameters String[] column = parameterType.getColumns(); String table = parameterType.getTable(); Annotation[] annotations = parameterType.getAnnotationsMethod(); String convertToParameter = parameters.getProperty("convertTo"); this.convertTo = Currency.getInstance (convertToParameter != null ? convertToParameter : "USD");//Determines target // currency }}
2.5.3.3 使用自定义 UserType
建议放在 package-info.java 中,这样就可以在包内任意使用啦 O(∩_∩)O~
@org.hibernate.annotations.TypeDefs({ @org.hibernate.annotations.TypeDef( name = "monetary_amount_usd", typeClass = MonetaryAmountUserType.class, parameters = {@Parameter(name = "convertTo", value = "USD")} ), @org.hibernate.annotations.TypeDef( name = "monetary_amount_eur", typeClass = MonetaryAmountUserType.class, parameters = {@Parameter(name = "convertTo", value = "EUR")} )}) package net.deniro.hibernate.converter;import org.hibernate.annotations.Parameter;
现在把自定义的类标注在对应的类属性上咯:
@Entitypublic class Item { private String id; @Id public String getId() { return id; } public void setId(String id) { this.id = id; } @NotNull @org.hibernate.annotations.Type( type = "monetary_amount_usd" ) @org.hibernate.annotations.Columns(columns = { @Column(name = "BUYNOWPRICE_AMOUNT"), @Column(name = "BUYNOWPRICE_CURRENCY", length = 3) }) protected MonetaryAmount buyNowPrice; @NotNull @org.hibernate.annotations.Type( type = "monetary_amount_eur" ) @org.hibernate.annotations.Columns(columns = { @Column(name = "BUYNOWPRICE_AMOUNT"), @Column(name = "BUYNOWPRICE_CURRENCY", length = 3) }) protected MonetaryAmount initialPrice;}
因为 JPA 不支持多个 @Column 注解映射到一个属性上,所以我们要使用 @org.hibernate.annotations.Columns
来实现这个功能。注意,这里定义的顺序必须与实际情况相符!
- 说说 Hibernate 的映射策略
- Hibernate继承映射的两种策略
- 有关hibernate实体的映射策略
- Hibernate的3种继承映射策略
- Hibernate继承映射策略
- HIbernate继承映射策略
- Hibernate 主键维护策略和hibernate 常见的映射类型
- 说说 Hibernate 如何映射持久化类
- 解读Hibernate继承映射策略
- 解读Hibernate继承映射策略
- Hibernate中日期类型字段的映射策略
- Hibernate中实体映射时的命名策略(1)
- Hibernate中枚举Enum类型的映射策略
- hibernate继承的三种映射策略( Inheritance mapping)
- Hibernate支持三种基本的继承映射策略:
- Hibernate中实体映射时的命名策略(2)
- Hibernate针对Java基本类型字段的映射策略总结
- Hibernate中枚举Enum类型的映射策略
- BZOJ 1271: [BeiJingWc2008]秦腾与教学评估 二分查找
- 关于hibernate反向生成实体类的问题-没有主键的表映射
- Js网络视频播放器之VideoJs&&ckplayer(直播拉流rtmp、hls)
- 静夜思—何方(续)
- npm常用命令
- 说说 Hibernate 的映射策略
- 输入子系统(三)------调用关系
- Scala安装配置
- hadoop-hdfs学习1
- 开闭原则
- JavaScript 一些使用方法
- maven集成eclipse总结
- JS中的宽高理解
- Ubuntu-14.04.4-Server图解安装