數(shù)據(jù)庫中的主鍵能夠唯一識(shí)別一條記錄,它可以是一個(gè)字段也可以是多個(gè)字段的組合。主鍵的主要作用是標(biāo)識(shí)表中的一條記錄,還有和其他表中的數(shù)據(jù)進(jìn)行關(guān)聯(lián)。數(shù)據(jù)庫中的主鍵類型必須符合唯一性約束和非空約束。作為附加屬性,主鍵應(yīng)該盡可能簡潔,不要包含過多屬性。根據(jù)這個(gè)原則,主鍵可以分為自然主鍵和代理主鍵。
自然主鍵是數(shù)據(jù)表中有邏輯含義的字段,比如身份證號(hào)來唯一確定一條個(gè)人記錄。也可以通過學(xué)號(hào)和課程號(hào)來唯一確定成績。使用自然主鍵時(shí)如果業(yè)務(wù)發(fā)生變化則對(duì)數(shù)據(jù)庫調(diào)整是極其麻煩的,所以數(shù)據(jù)庫設(shè)計(jì)使用代理主鍵是非常好的選擇。代理主鍵是一個(gè)和業(yè)務(wù)無關(guān)的流水號(hào),一般采用數(shù)據(jù)庫中自動(dòng)增長的機(jī)制自動(dòng)生成。例如Oracle數(shù)據(jù)庫使用序列,MySQL和SQL Server有自動(dòng)增長(auto increment)類型,該字段類型一般為Integer,名稱設(shè)置為ID或XXID。下面來看看Hibernate中的主鍵生成策略,實(shí)驗(yàn)都以O(shè)racle為主,MySQL為輔來說明。
第一種是
increment策略,Hibernate配置如下:
- <id name="id" column="ID" type="java.lang.Integer" length="10">
- <generator class="increment"></generator>
- </id>
<id name="id" column="ID" type="java.lang.Integer" length="10"><generator class="increment"></generator></id>
運(yùn)行結(jié)果如下:
- Hibernate: select max(ID) from USERS
- Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values(?, ?, ?, ?, ?, ?)
Hibernate: select max(ID) from USERSHibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values(?, ?, ?, ?, ?, ?)
可以看出,Hibernate是從數(shù)據(jù)庫中先找出已經(jīng)存在的最大主鍵數(shù)值,然后加1后作為新紀(jì)錄的主鍵再執(zhí)行插入語句。這種策略不適合非獨(dú)享數(shù)據(jù)庫或者分布式的Hibernate應(yīng)用,否則就很難保證主鍵值的唯一了。
第二種是
identity策略,Hibernate中配置如下:
- <id name="id" column="ID" type="java.lang.Integer" length="10">
- <generator class="identity"></generator>
- </id>
<id name="id" column="ID" type="java.lang.Integer" length="10"><generator class="identity"></generator></id>
這種策略在Oracle中無法使用,因?yàn)檫@種策略是針對(duì)數(shù)據(jù)庫中字段自動(dòng)增長類型。改在MySQL中測(cè)試,得到如下結(jié)果:
- Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME) values(?, ?, ?, ?, ?)
Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME) values(?, ?, ?, ?, ?)
多次運(yùn)行程序,在數(shù)據(jù)庫中查看,可以看到主鍵是自動(dòng)進(jìn)行遞增的。
第三種是
sequence策略,Hibernate中配置如下:
- <generator class="sequence">
- <param name="sequence">SEQ_HIBERNATE_USERS</param>
- </generator>
<generator class="sequence"><param name="sequence">SEQ_HIBERNATE_USERS</param></generator>
使用序列策略是,需要現(xiàn)在數(shù)據(jù)庫中創(chuàng)建一條序列,運(yùn)行程序,得到如下結(jié)果:
- Hibernate: select SEQ_HIBERNATE_USERS.nextval from dual
- Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
Hibernate: select SEQ_HIBERNATE_USERS.nextval from dualHibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
可以看出Hibernate先從序列中得到下一個(gè)數(shù)值,然后執(zhí)行插入操作。
第四種是
hilo策略,這是Hibernate采用一種稱為“高/低位”(hi/lo)的算法產(chǎn)生標(biāo)識(shí)符屬性值,該算法采用一個(gè)高位值和一個(gè)低位值進(jìn)行運(yùn)算,結(jié)果作為標(biāo)識(shí)符屬性的值。使用該策略時(shí),需要在數(shù)據(jù)庫中建立一個(gè)表和其中一個(gè)字段,名稱可以自定。其中的字段值作為高位值的來源。在Hibernate中配置如下:
- <generator class="hilo">
- <param name="table">HIBERNATE_KEY</param>
- <param name="column">NEXT_HIVALUE</param>
- </generator>
<generator class="hilo"><param name="table">HIBERNATE_KEY</param><param name="column">NEXT_HIVALUE</param></generator>
可以見名知意,我使用的表名是HIBERNATE_KEY,字段名是NEXT_HIVALUE,然后給該字段設(shè)置一個(gè)值,作為高位。低位Hibernate有自己的管理機(jī)制可不用創(chuàng)建,也可以給出在配置文件中用<param name="max_lo">1</param>的形式給出即可,一般使用情況下使用1更好。該算法是每次按max_lo數(shù)值遞增。Hibernate的執(zhí)行結(jié)果還是一條語句,如下:
- Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
執(zhí)行后高位自動(dòng)加1,然后到數(shù)據(jù)庫中就可以查看生成的主鍵數(shù)值。該策略與底層的數(shù)據(jù)庫無關(guān),只要保證高位降低,那么生成的主鍵值是唯一的,并且可跨數(shù)據(jù)庫使用。
第五種是
seqhilo策略,基于hilo策略,這種方式的標(biāo)識(shí)符屬性生成時(shí)指定一個(gè)序列作為高位值,那么在Hibernate中的配置如下:
- <generator class="seqhilo">
- <param name="sequence">SEQ_HIBERNATE_USERS</param>
- </generator>
<generator class="seqhilo"><param name="sequence">SEQ_HIBERNATE_USERS</param></generator>
執(zhí)行插入后,Hibernate首先取出序列的下一個(gè)值作為高位值,然后計(jì)算一個(gè)ID出來進(jìn)行操作,得到如下結(jié)果:
- Hibernate: select SEQ_HIBERNATE_USERS.nextval from dual
- Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
Hibernate: select SEQ_HIBERNATE_USERS.nextval from dualHibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
第六種是
uuid策略,這種策略是使用128位的UUID算法來生成字符串類型標(biāo)識(shí)符屬性,這種算法使用IP地址,JVM的啟動(dòng)時(shí)間(精確到1/4秒),系統(tǒng)時(shí)間和一個(gè)計(jì)數(shù)器(在當(dāng)前JVM中唯一)這些數(shù)值經(jīng)過計(jì)算得到一個(gè)標(biāo)識(shí)符屬性的值,產(chǎn)生的值是32位長度的字符串,則使用前需要將數(shù)據(jù)庫字段調(diào)整到varchar2(32),持久化類的ID屬性改為String進(jìn)行測(cè)試。Hibernate中配置如下,注意字段信息的修改:
- <id name="id" column="ID" type="java.lang.String" length="32">
- <generator class="uuid">
- </generator>
- lt;/id>
<id name="id" column="ID" type="java.lang.String" length="32"><generator class="uuid"></generator></id>
執(zhí)行后Hibernate的結(jié)果還是一條插入語句,這里不再復(fù)制了,我們看看數(shù)據(jù)庫中的結(jié)果吧,如下圖,就得到了32位的主鍵值了。
在做文件下載時(shí),用這種策略生成的主鍵就可以作為下載鏈接,而不用在人為去制作鏈接,是一個(gè)不錯(cuò)的選擇。
第七種是
guid策略,Hibernate中配置如下:
- <generator class="guid"></generator>
<generator class="guid"></generator>
這個(gè)在Oracle中是用了sys_guid()函數(shù)生成的值,而在MySQL中使用uuid()函數(shù)生成值,這個(gè)值要設(shè)置成varchar2/varchar類型,區(qū)別在于Oracle中是32位長度,而MySQL原生是36位(有4個(gè)-隔開)。運(yùn)行程序得到如下結(jié)果:(Oracle環(huán)境下)
- Hibernate: select rawtohex(sys_guid()) from dual
- Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
Hibernate: select rawtohex(sys_guid()) from dualHibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
(MySQL環(huán)境下)
- Hibernate: select uuid()
- Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
Hibernate: select uuid()Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
第八種是
native策略,native字面意思是“本地的”,那么對(duì)于數(shù)據(jù)庫,Hibernate該如何選擇呢?Hibernate根據(jù)所使用的數(shù)據(jù)庫支持能力從identity,sequence或者h(yuǎn)ilo策略中選擇一種,Hibernate中配置很簡單:
- <generator class="native"></generator>
<generator class="native"></generator>
在Oracle下,Oracle先創(chuàng)建一個(gè)序列,使用默認(rèn)名(數(shù)據(jù)庫名_SEQUENCE),然后執(zhí)行插入操作,而在MySQL下則使用identity策略,使用了自動(dòng)增長的字段。Oracle中測(cè)試結(jié)果如下:
- Hibernate: select hibernate_sequence.nextval from dual
- Hibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
Hibernate: select hibernate_sequence.nextval from dualHibernate: insert into USERS (NAME, PHONE, DEPARTMENT, CITY, HIRE_TIME, ID) values (?, ?, ?, ?, ?, ?)
第九種是
assigned策略,這種方式也是Hibernate中<generator>沒有配置標(biāo)識(shí)符屬性生成策略時(shí)默認(rèn)使用的方式。但使用這種策略時(shí)需要我們自定義標(biāo)識(shí)符屬性的值,也就是我們?nèi)藶樵O(shè)置標(biāo)識(shí)符屬性的值,這就需要在程序中顯式為標(biāo)識(shí)符屬性(ID)賦值。配置方式如下:
- <generator class="assigned"></generator>
<generator class="assigned"></generator>
第十種是
foreign策略,這種方式是通過關(guān)聯(lián)的持久化對(duì)象為當(dāng)前的持久化對(duì)象設(shè)置標(biāo)識(shí)符屬性,當(dāng)他們是一對(duì)一關(guān)聯(lián)時(shí),一個(gè)持久化類的主鍵值可以參考關(guān)聯(lián)持久化類的標(biāo)識(shí)符屬性值。我們做一個(gè)完整的實(shí)例來看。(使用Hibernate為我們自動(dòng)創(chuàng)建表)
hibernate.cfg.xml
- <?xml version='1.0' encoding='UTF-8'?>
- <!DOCTYPE hibernate-configuration PUBLIC
- "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
- <hibernate-configuration>
- <session-factory>
- <property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
- <property name="connection.url">jdbc:oracle:thin:@localhost:1521:orcl</property>
- <property name="hibernate.connection.username">hibernate</property>
- <property name="hibernate.connection.password">hibernate</property>
- <property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
- <property name="current_session_context_class">thread</property>
- <property name="hbm2ddl.auto">create</property>
- <property name="show_sql">true</property>
- <property name="format_sql">false</property>
- <mapping resource="demo/domain/User.hbm.xml" />
- <mapping resource="demo/domain/Profile.hbm.xml" />
- </session-factory>
- </hibernate-configuration>
<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"><hibernate-configuration><session-factory><property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property><property name="connection.url">jdbc:oracle:thin:@localhost:1521:orcl</property><property name="hibernate.connection.username">hibernate</property><property name="hibernate.connection.password">hibernate</property><property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property><property name="current_session_context_class">thread</property><property name="hbm2ddl.auto">create</property><property name="show_sql">true</property><property name="format_sql">false</property><mapping resource="demo/domain/User.hbm.xml" /><mapping resource="demo/domain/Profile.hbm.xml" /></session-factory></hibernate-configuration>
User持久化類和映射文件:
- package demo.domain;
- public class User implements java.io.Serializable {
- private Integer id;
- private String name;
- private String password;
- private Profile profile;
- public User() {
- }
-
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- public Profile getProfile() {
- return profile;
- }
- public void setProfile(Profile profile) {
- this.profile = profile;
- }
- }
package demo.domain;public class User implements java.io.Serializable {private Integer id;private String name;private String password;private Profile profile;public User() {}// get和set方法public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Profile getProfile() {return profile;}public void setProfile(Profile profile) {this.profile = profile;}}
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="demo.domain.User" table="USERS">
- <id name="id" column="ID" type="java.lang.Integer" length="10">
- <generator class="native">
- </generator>
- </id>
- <property name="name" column="NAME" type="java.lang.String"
- length="20" not-null="true" />
- <property name="password" column="PASSWORD" type="java.lang.String"
- length="32" />
- <one-to-one name="profile" class="demo.domain.Profile"
- outer-join="true" cascade="all" />
- </class>
- </hibernate-mapping>
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="demo.domain.User" table="USERS"><id name="id" column="ID" type="java.lang.Integer" length="10"><generator class="native"></generator></id><property name="name" column="NAME" type="java.lang.String"length="20" not-null="true" /><property name="password" column="PASSWORD" type="java.lang.String"length="32" /><one-to-one name="profile" class="demo.domain.Profile"outer-join="true" cascade="all" /></class></hibernate-mapping>
Profile持久化類和映射文件如下:
- package demo.domain;
-
- public class Profile implements java.io.Serializable {
- private Integer id;
- private String info;
- private User user;
- public Profile() {
- }
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getInfo() {
- return info;
- }
- public void setInfo(String info) {
- this.info = info;
- }
- public User getUser() {
- return user;
- }
- public void setUser(User user) {
- this.user = user;
- }
- }
package demo.domain;public class Profile implements java.io.Serializable {private Integer id;private String info;private User user;public Profile() {}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getInfo() {return info;}public void setInfo(String info) {this.info = info;}public User getUser() {return user;}public void setUser(User user) {this.user = user;}}
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="demo.domain.Profile" table="PROFILES">
- <id name="id" column="ID" type="java.lang.Integer" length="10">
- <generator class="foreign">
- <param name="property">user</param>
- </generator>
- </id>
- <property name="info" column="INFO" type="java.lang.String"
- length="200" not-null="true" />
- <one-to-one name="user" class="demo.domain.User"
- constrained="true" />
- </class>
- </hibernate-mapping>
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="demo.domain.Profile" table="PROFILES"><id name="id" column="ID" type="java.lang.Integer" length="10"><generator class="foreign"><param name="property">user</param></generator></id><property name="info" column="INFO" type="java.lang.String"length="200" not-null="true" /><one-to-one name="user" class="demo.domain.User"constrained="true" /></class></hibernate-mapping>
注意二者<one-to-one>配置時(shí)的屬性是不同的,因?yàn)橹鲃?dòng)的一方是User,被動(dòng)的一方是Profile,才會(huì)要求一個(gè)配置cascade,cascade為all表示表示主動(dòng)一方無論執(zhí)行任何操作,其關(guān)聯(lián)對(duì)象執(zhí)行同樣的操作。一個(gè)配置constrained,constrained為true表示約束Profile對(duì)象的標(biāo)識(shí)符屬性必須與User對(duì)象的標(biāo)識(shí)符屬性相同。測(cè)試類如下:
- package demo;
- import org.hibernate.*;
- import org.hibernate.cfg.Configuration;
- import demo.domain.*;
- public class Test {
- public static void main(String[] args) {
- Configuration config = new Configuration();
- config.configure();
- SessionFactory sessionFactory = config.buildSessionFactory();
- Session session = sessionFactory.getCurrentSession();
- Transaction tx = session.beginTransaction();
- User user = new User();
- user.setName("Tom");
- user.setPassword("123456");
- Profile profile = new Profile();
- profile.setInfo("Information");
-
- user.setProfile(profile);
- profile.setUser(user);
- session.save(user);
- tx.commit();
- }
- }
package demo;import org.hibernate.*;import org.hibernate.cfg.Configuration;import demo.domain.*;public class Test {public static void main(String[] args) {Configuration config = new Configuration();config.configure();SessionFactory sessionFactory = config.buildSessionFactory();Session session = sessionFactory.getCurrentSession();Transaction tx = session.beginTransaction();User user = new User();user.setName("Tom");user.setPassword("123456");Profile profile = new Profile();profile.setInfo("Information");//雙向關(guān)聯(lián)user.setProfile(profile);profile.setUser(user);session.save(user);tx.commit();}}
執(zhí)行程序,我們得到如下結(jié)果:
- Hibernate: select hibernate_sequence.nextval from dual
- Hibernate: insert into USERS (NAME, PASSWORD, ID) values (?, ?, ?)
- Hibernate: insert into PROFILES (INFO, ID) values (?, ?)
Hibernate: select hibernate_sequence.nextval from dualHibernate: insert into USERS (NAME, PASSWORD, ID) values (?, ?, ?)Hibernate: insert into PROFILES (INFO, ID) values (?, ?)
我們?yōu)閁ser對(duì)象設(shè)置的標(biāo)識(shí)符屬性生成策略是native,則在Oracle數(shù)據(jù)庫中使用序列生成。為Profie對(duì)象設(shè)置的標(biāo)識(shí)符屬性生成策略是foreign,它就按照主體對(duì)象的標(biāo)識(shí)符屬性獲取。其實(shí)這種一對(duì)一關(guān)系完全可以合并到一個(gè)表中,只是為了說明,分開來演示例子而已。而實(shí)際情況就要具體問題具體分析了,主要在業(yè)務(wù)層面考慮而不是具體的技術(shù)實(shí)現(xiàn)。
最后來說一下標(biāo)識(shí)符屬性生成策略的選擇方式。應(yīng)用不需要分布式時(shí),在數(shù)據(jù)庫支持的sequence,identity,hilo,seqhilo和uuid中選擇比較好。而分布式數(shù)據(jù)庫應(yīng)用中uuid是最佳選擇。若是改造遺留系統(tǒng),那么使用assigned是最合適的了。
歡迎交流,希望對(duì)使用者有用。