在完成之前2篇文章内容之后,我们讨论下以下情况。
项目要改版,TUser或者User要修改属性,添加或者删除某些字段,那么数据库已保存的数据还能被反序列化为原有对象,并进行扩展么?
修改User.java
package cn.vicky.po.vo; import java.io.Serializable; /** * * @author Vicky.H */ public class User implements Serializable { private static final long serialVersionUID = 1L; /** 新增加的属性 **/ private String address; private String username; private String password; private boolean sex; private short age; /** 新增加的属性 **/ private int cardnum; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public short getAge() { return age; } public void setAge(short age) { this.age = age; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isSex() { return sex; } public void setSex(boolean sex) { this.sex = sex; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getCardnum() { return cardnum; } public void setCardnum(int cardnum) { this.cardnum = cardnum; } }
修改App.java
package cn.vicky.tmp1; import cn.vicky.po.TUser; import cn.vicky.po.vo.User; import java.io.IOException; import java.sql.SQLException; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; /** * */ public class App { public static void create1(EntityManager em) { User user = new User(); user.setUsername("Vicky"); user.setPassword("123456"); short age = 25; user.setAge(age); user.setSex(true); TUser tUser = new TUser(); tUser.setUser(user); em.persist(tUser); } public static void create2(EntityManager em) { User user = new User(); user.setUsername("Jack"); user.setPassword("654321"); short age = 37; user.setAge(age); user.setSex(true); TUser tUser = new TUser(); tUser.setUser(user); em.persist(tUser); } /** * 通过主键获得TUser对象,并且获得User对象 * @param em */ public static void find1(EntityManager em) { TUser tUser = em.find(TUser.class, 1); User user = tUser.getUser(); System.out.println("user.getUsername() = " + user.getUsername()); System.out.println("user.getPassword() = " + user.getPassword()); System.out.println("user.getAge() = " + user.getAge()); System.out.println("user.isSex() = " + user.isSex()); System.out.println("user.getAddress() = " + user.getAddress()); System.out.println("user.getCardnum() = " + user.getCardnum()); } /** * 直接查询Blob数据并强制转换为User对象 * @param em */ public static void find2(EntityManager em) { User user = (User) em.createQuery("select o.user from t_user o where o.id = 1").getSingleResult(); System.out.println("user.getUsername() = " + user.getUsername()); System.out.println("user.getPassword() = " + user.getPassword()); System.out.println("user.getAge() = " + user.getAge()); System.out.println("user.isSex() = " + user.isSex()); System.out.println("user.getAddress() = " + user.getAddress()); System.out.println("user.getCardnum() = " + user.getCardnum()); } public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException { EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); // create1(em); // create2(em); find1(em); find2(em); em.getTransaction().commit(); em.close(); emf.close(); } }
运行:
Hibernate: select tuser0_.id as id0_0_, tuser0_.user as user0_0_ from t_user tuser0_ where tuser0_.id=?
user.getUsername() = Vicky
user.getPassword() = 123456
user.getAge() = 25
user.isSex() = true
user.getAddress() = null
user.getCardnum() = 0
Hibernate: select tuser0_.user as col_0_0_ from t_user tuser0_ where tuser0_.id=1 limit ?
user.getUsername() = Vicky
user.getPassword() = 123456
user.getAge() = 25
user.isSex() = true
user.getAddress() = null
user.getCardnum() = 0
尝试删除属性后,程序执行依然正确。似乎挺简单的,没有什么值得注意的。
但这里的确有个地方需要注意,那就是:
private static final long serialVersionUID = 1L;
我们尝试删除User.java 的 private static final long serialVersionUID = 1L;
再次运行App.java
Hibernate: select tuser0_.id as id0_0_, tuser0_.user as user0_0_ from t_user tuser0_ where tuser0_.id=?
Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.type.SerializationException: could not deserialize
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614)
at org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:202)
at cn.vicky.tmp1.App.find1(App.java:47)
at cn.vicky.tmp1.App.main(App.java:81)
Caused by: org.hibernate.type.SerializationException: could not deserialize
at org.hibernate.util.SerializationHelper.deserialize(SerializationHelper.java:188)
at org.hibernate.util.SerializationHelper.deserialize(SerializationHelper.java:211)
at org.hibernate.type.SerializableType.fromBytes(SerializableType.java:105)
at org.hibernate.type.SerializableType.get(SerializableType.java:62)
at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:184)
at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:173)
at org.hibernate.type.AbstractType.hydrate(AbstractType.java:105)
at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2124)
at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1404)
at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1332)
at org.hibernate.loader.Loader.getRow(Loader.java:1230)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:603)
at org.hibernate.loader.Loader.doQuery(Loader.java:724)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.loadEntity(Loader.java:1881)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:71)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:65)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3072)
at org.hibernate.event.def.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:434)
at org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:415)
at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:165)
at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:223)
at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:126)
at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:905)
at org.hibernate.impl.SessionImpl.get(SessionImpl.java:842)
at org.hibernate.impl.SessionImpl.get(SessionImpl.java:835)
at org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:182)
... 2 more
Caused by: java.io.InvalidClassException: cn.vicky.po.vo.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 993454148924749307
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at org.hibernate.util.SerializationHelper.deserialize(SerializationHelper.java:181)
... 28 more
错误原因:
serialVersionUID用来表明类的不同版本间的兼容性 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来 的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序 列化,否则就会出现序列化版本不一致的异常。
当实现java.io.Serializable接口的实体(类)没有显式地定义一个名为serialVersionUID,类型为long的变 量时,Java序列化机制会根据编译的class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,只有同一次编译生成的 class才会生成相同的serialVersionUID 。
如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,未作更改的类,就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。
你可以很轻松的在google上查询到详细的解释。