深入解析Java的Hibernate框架中的一对一关联映射
作为一个ORM框架,hibernate肯定也需要满足我们实现表与表之间进行关联的需要。hibernate在关联方法的实现很简单。下面我们先来看看一对一的做法:
不多说了,我们直接上代码:
两个实体类,TUser和TPassport:
publicclassTUserimplementsSerializable{ privatestaticfinallongserialVersionUID=1L; privateintid; privateintage; privateStringname; privateTPassportpassport; //省略Get/Set方法 } publicclassTPassportimplementsSerializable{ privatestaticfinallongserialVersionUID=1L; privateintid; privateStringserial; privateintexpiry; privateTUseruser; //省略Get/Set方法 }
下面我们看一下映射文件有什么不同:
<hibernate-mappingpackage="org.hibernate.tutorial.domain4"> <classname="TUser"table="USER4"> <idname="id"column="id"> <generatorclass="native"/> </id> <propertyname="name"type="java.lang.String"column="name"/> <propertyname="age"type="java.lang.Integer"column="age"/> <one-to-onename="passport"class="TPassport" cascade="all"outer-join="true"/> </class> </hibernate-mapping>
这里我们看到有一个新标签,one-to-one,它表明当前类与所对应的类是一对一的关系,cascade是级联关系,all表明无论什么情况都进行级联,即当对TUser类进行操作时,TPassport也会进行相应的操作,outer-join是指是否使用outerjoin语句。
我们再看另外一个TPassport的映射文件:
<hibernate-mappingpackage="org.hibernate.tutorial.domain4"> <classname="TPassport"table="passport4"> <idname="id"column="id"> <generatorclass="foreign"> <paramname="property">user</param> </generator> </id> <propertyname="serial"type="java.lang.String"column="serial"/> <propertyname="expiry"type="java.lang.Integer"column="expiry"/> <one-to-onename="user"class="TUser"constrained="true"/> </class> </hibernate-mapping>
这里我们重点看到generator的class值,它为foreign表明参照外键,而参照哪一个是由param来进行指定,这里表明参照User类的id。而one-to-one标签中多了一个constrained属性,它是告诉hibernate当前类存在外键约束,即当前类的ID根据TUser的ID进行生成。
下面我们直接上测试类,这次测试类没有用JUnit而是直接Main方法来了:
publicstaticvoidmain(String[]args){ Configurationcfg=newConfiguration().configure(); SessionFactorysessionFactory=cfg.buildSessionFactory(); Sessionsession=sessionFactory.openSession(); session.beginTransaction(); TUseruser=newTUser(); user.setAge(20); user.setName("shunTest"); TPassportpassport=newTPassport(); passport.setExpiry(20); passport.setSerial("123123123"); passport.setUser(user); user.setPassport(passport); session.save(user); session.getTransaction().commit(); }
代码很简单,就不说了。我们主要看这里:
session.save(user);
这里为什么我们只调用一个save呢,原因就在我们的TUser映射文件中的cascade属性,它被设为all,即表明当我们对TUser进行保存,更新,删除等操作时,TPassport也会进行相应的操作,所以这里我们不用写session.save(passport)。我们看到后台:
Hibernate:insertintoUSER4(name,age)values(?,?) Hibernate:insertintopassport4(serial,expiry,id)values(?,?,?)Hibernate: 它打印出两个语句,证明hibernate确定帮我们做了这个工作。
下面我们再来一个测试类,测试查询:
publicstaticvoidmain(String[]args){ Configurationcfg=newConfiguration().configure(); SessionFactorysessionFactory=cfg.buildSessionFactory(); Sessionsession=sessionFactory.openSession(); TUseruser=(TUser)session.load(TUser.class,newInteger(3)); System.out.println(user.getName()+":"+user.getPassport().getSerial()); }这里我们通过查询出TUser类,取到TPassport对象。
我们可以看到hibernate的SQL语句是:
Hibernate: selecttuser0_.idasid0_1_,tuser0_.nameasname0_1_,tuser0_.ageasage0_1_,tpassport1_.idasid1_0_,tpassport1_.serialasserial1_0_,tpassport1_.expiryasexpiry1_0_fromUSER4tuser0_leftouterjoinpassport4tpassport1_ontuser0_.id=tpassport1_.idwheretuser0_.id=?
我们看到语句当中有leftouterjoin,这是因为我们前面在one-to-one当中设了outer-join="true",我们试着把它改成false,看到SQL语句如下:
Hibernate: selecttuser0_.idasid0_0_,tuser0_.nameasname0_0_,tuser0_.ageasage0_0_fromUSER4tuser0_wheretuser0_.id=?Hibernate:
selecttpassport0_.idasid1_0_,tpassport0_.serialasserial1_0_,tpassport0_.expiryasexpiry1_0_frompassport4tpassport0_wheretpassport0_.id=?
现在是分成两条来查了,根据第一条查出的ID再到第二条查出来。
也许很多人会问为什么测试的时候不用TPassport查出TUser呢,其实也可以的,因为它们是一对一的关系,谁查谁都是一样的。
外键关联
现在我们看一下通过外键来进行关联的一对一关联。
还是一贯的直接上例子:我们写了两个实体类,TGroup和TUser
publicclassTGroupimplementsSerializable{ privatestaticfinallongserialVersionUID=1L; privateintid; privateStringname; privateTUseruser; //省略Get/Set方法 } publicclassTUserimplementsSerializable{ privatestaticfinallongserialVersionUID=1L; privateintid; privateintage; privateStringname; privateTGroupgroup; //省略Get/Set方法 }
实体类完了我们就看一下映射文件:
<hibernate-mappingpackage="org.hibernate.tutorial.domain5"> <classname="TUser"table="USER5"> <idname="id"column="id"> <generatorclass="native"/> </id> <propertyname="name"type="java.lang.String"column="name"/> <propertyname="age"type="java.lang.Integer"column="age"/> <many-to-onename="group"class="TGroup"column="group_id"unique="true"/> </class> </hibernate-mapping>
这里我们看到是用many-to-one标签而不是one-to-one,为什么呢?
这里以前用的时候也没多在注意,反正会用就行,但这次看了夏昕的书终于明白了,实际上这种通过外键进行关联方式只是多对一的一种特殊方式而已,我们通过unique="true"限定了它必须只能有一个,即实现了一对一的关联。
接下来我们看一下TGroup的映射文件:
<hibernate-mappingpackage="org.hibernate.tutorial.domain5"> <classname="TGroup"table="group5"> <idname="id"column="id"> <generatorclass="native"/> </id> <propertyname="name"type="java.lang.String"column="name"/> <one-to-onename="user"class="TUser"property-ref="group"/> </class> </hibernate-mapping>
这里,注意,我们又用到了one-to-one,表明当前的实体和TUser是一对一的关系,这里我们不用many-to-one,而是通过one-to-one指定了TUser实体中通过哪个属性来关联当前的类TGroup。这里我们指定了TUser是通过group属性和Tuser进行关联的。property-ref指定了通过哪个属性进行关联。
下面我们看测试类:
publicclassHibernateTest{ publicstaticvoidmain(String[]args){ Configurationcfg=newConfiguration().configure(); SessionFactorysessionFactory=cfg.buildSessionFactory(); Sessionsession=sessionFactory.openSession(); session.beginTransaction(); TGroupgroup=newTGroup(); group.setName("testGroup"); TUseruser=newTUser(); user.setAge(23); user.setName("test"); user.setGroup(group); group.setUser(user); session.save(group); session.save(user); session.getTransaction().commit(); session.close(); } }
注意,这次我们的代码中需要进行两次的保存,因为它们对各自都有相应的对应,只保存一个都不会对另外一个有什么操作。所以我们需要调用两次保存的操作。最后进行提交。
hibernate打印出语句:
Hibernate:insertintogroup5(name)values(?) Hibernate:insertintoUSER5(name,age,group_id)values(?,?,?)
这说明我们正确地存入了两个对象值。
我们写多一个测试类进行查询:
publicstaticvoidmain(String[]args){ Configurationcfg=newConfiguration().configure(); SessionFactorysessionFactory=cfg.buildSessionFactory(); Sessionsession=sessionFactory.openSession(); TUseruser=(TUser)session.load(TUser.class,newInteger(1)); System.out.println("FromUsergetGroup:"+user.getGroup().getName()); TGroupgroup=(TGroup)session.load(TGroup.class,newInteger(1)); System.out.println("FromGroupgetUser:"+group.getUser().getName()); session.close(); }
我们都可以得到正确的结果,这表明我们可以通过两个对象拿出对方的值,达到了我们的目的。
这个例子中用到的TGroup和TUser只是例子而已,实际上现实生活中的user一般都对应多个group。