在hibernate中,如果某个Entity的属性使用泛型,必须为其属性映射加上type属性。
public interface Entity<T> { T getId(); } public class BaseEntity<T> implements Entity<T> { protected T id; public T getId() { return id; } } public class Forum extends BaseEntity<Long> { // ... }
在泛型DAO经常会看到这样的代码。
Hibernate映射文件:
<class name="marlon.myforum.domain.model.Forum" table="FORUM"> <id name="id" column="ID" type="long"> <generator class="native" /> </id> </class>
如果不加上type="long",运行时就会错:
org.hibernate.MappingException: identifier mapping has wrong number of columns: Forum type: object at org.hibernate.mapping.RootClass.validate(RootClass.java:194) at org.hibernate.cfg.Configuration.validate(Configuration.java:1102) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1287)
从错误消息里可以看出hibernate认为id的type为"object",这是因为不指定type时,hibernate会自动根据属性的类型来决定,虽然在编译时id的类型为Long,但是由于泛型的Erase机制,编译之后类型信息就丢失了,通过反射得到id的类型为Object,这也就是为什么hibernate报错的原因。其实只要是通过反射来填充属性,都可能存在这种问题,比如在Web框架中,会将请求参数装载到一个bean中。假设如果id="abc",它仍然可以被填充到Forum的id属性中来,因为它的类型是一个Object,但是运行时通过forum.getId()时就会发生ClassCastException,这显然不是所期望的,这时id实际是一个String,现在需要换成Long。为了避免这种问题,对于上面的例子,可以将BaseEntity不声明为泛型,而直接容纳一个类型为Long的id,这样虽然没有将BaseEntity灵活,对ID其它类型的Entity需要直接实现Entity接口,但是却避免了使用泛型BaseEntity的缺点。
public class BaseEntity implements Entity<Long> { protected Long id; public Long getId() { return id; } }
由此可见,用泛型也是需要慎重考虑的,虽然它能够带来编译时类型安全,但是不能忘记了编译后就是一个裸的Object(在没有指定限定符时),这在需要用反射中填充属性时尤其要注意,而现在大多数的Java框架又广泛的运用了此。