一、表與表之間關係分析
一對多建表原則:在多的一方創建外鍵指向一的一方的主鍵;
多對多建表原則:創建一箇中間表,中間表至少有兩個字段分別作爲外鍵指向多對多雙方的主鍵;
一對一建表原則:(瞭解)有兩種對應方式,即唯一外鍵對應、主鍵對應。唯一外鍵對應方式中,可將其中一張表看成一對多中的n = 1,然後再該表中添加唯一外鍵;主鍵對應方式中,直接將兩張表的主鍵進行一一對應即可。
具體可參考:SQL多表操作
二、Hibernate中一對多關係
1.建立工程,引入jar包
具體參考:Hibernate項目創建
2.創建表
創建客戶表:
CREATE TABLE `cst_customer` (
`cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客戶編號(主鍵)',
`cust_name` varchar(32) NOT NULL COMMENT '客戶名稱(公司名稱)',
`cust_source` varchar(32) DEFAULT NULL COMMENT '客戶信息來源',
`cust_industry` varchar(32) DEFAULT NULL COMMENT '客戶所屬行業',
`cust_level` varchar(32) DEFAULT NULL COMMENT '客戶級別',
`cust_phone` varchar(64) DEFAULT NULL COMMENT '固定電話',
`cust_mobile` varchar(16) DEFAULT NULL COMMENT '移動電話',
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
創建聯繫人表:
CREATE TABLE `cst_linkman` (
`lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '聯繫人編號(主鍵)',
`lkm_name` varchar(16) DEFAULT NULL COMMENT '聯繫人姓名',
`lkm_cust_id` bigint(32) NOT NULL COMMENT '客戶id',
`lkm_gender` char(1) DEFAULT NULL COMMENT '聯繫人性別',
`lkm_phone` varchar(16) DEFAULT NULL COMMENT '聯繫人辦公電話',
`lkm_mobile` varchar(16) DEFAULT NULL COMMENT '聯繫人手機',
`lkm_email` varchar(64) DEFAULT NULL COMMENT '聯繫人郵箱',
`lkm_qq` varchar(16) DEFAULT NULL COMMENT '聯繫人qq',
`lkm_position` varchar(16) DEFAULT NULL COMMENT '聯繫人職位',
`lkm_memo` varchar(512) DEFAULT NULL COMMENT '聯繫人備註',
PRIMARY KEY (`lkm_id`),
KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
兩個表之間的關係:一對多的關係, 聯繫人表中的外鍵lkm_cust_id指向客戶表中的主鍵cust_id
3.創建實體類(客戶與聯繫人)
通過ORM(對象關係映射)的方式,將對象與數據庫中的表建立聯繫,從而也可以建立客戶類與聯繫人類之間的關係。這裏以一對多的方式舉例:
客戶實體類:Customer(這裏簡寫,省略get和set方法)
public class Customer { //一的一方
private Long cust_id;
private String cust_name;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_phone;
private String cust_mobile;
/*這裏放置多的一方的集合,從而使得客戶與聯繫人雙向關聯,Hibernate中使用的集合爲Set*/
private Set<Linkman> linkmans = new HashSet<Linkman>();
}
聯繫人實體類:Linkman(這裏簡寫,省略get和set方法)
注意:這裏聯繫人表爲多的一方,存在外鍵,但是這裏的實體類中使用Customer類(一的一方)的對象來代替外鍵。
public class Linkman { //多的一方
private Long lkm_id;
private String name;
private String lkm_gender;
private String lkm_phone;
private String lkm_mobile;
private String lkm_email;
private String lkm_qq;
private String lkm_position;
private String lkm_memo;
/*注意:這裏的外鍵使用Customer類(一的一方)的對象來代替*/
private Customer customer;
}
4.創建工具類
public class HibernateUtils {
public static final Configuration cfg;
public static final SessionFactory sf;
static{
cfg = new Configuration().configure();
sf = cfg.buildSessionFactory();
}
public static Session openSession(){
return sf.openSession();
}
public static Session getCurrentSession() {
return sf.getCurrentSession();
}
}
5.創建配置文件
映射配置文件:
Customer.hbn.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name = "start_2.Customer" table = "cst_customer">
<!-- 建立OID與主鍵映射 -->
<id name = "cust_id" column = "cust_id">
<generator class="native"/>
</id>
<!-- 建立普通屬性與數據庫表字段的映射 -->
<property name="cust_name" column = "cust_name"/>
<property name="cust_source" column = "cust_source"/>
<property name="cust_industry" column = "cust_industry"/>
<property name="cust_level" column = "cust_level"/>
<property name="cust_phone" column = "cust_phone"/>
<property name="cust_mobile" column = "cust_mobile"/>
<!-- 配置一對多的映射:放置多的一方的集合,set標籤中name放置多的一方的對象集合的屬性名稱 -->
<set name="linkmans">
<!-- key標籤,column中放的是多的一方的外鍵名稱 -->
<key column = "lkm_cust_id"/>
<!-- one-to-many 標籤,class中放的是多的一方的類的全路徑(即包含包名的路徑) -->
<one-to-many class = "start_2.Linkman"/>
</set>
</class>
</hibernate-mapping>
Link.hbn.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name = "start_2.Linkman" table = "cst_linkman">
<!-- 建立OID與主鍵映射 -->
<id name = "lkm_id" column = "lkm_id">
<generator class="native"/>
</id>
<!-- 建立普通屬性與數據庫表字段的映射 -->
<property name="lkm_name"/>
<property name="lkm_gender"/>
<property name="lkm_phone"/>
<property name="lkm_mobile"/>
<property name="lkm_email"/>
<property name="lkm_qq"/>
<property name="lkm_position"/>
<property name="lkm_memo"/>
<!-- 配置多對一的映射:放置的是一的一方的對象 -->
<!-- many-to-one標籤:
name : 一的一方的對象的屬性名稱;
class :一的一方的類的全路徑;
column :在多的一方的表的外鍵名稱;
-->
<many-to-one name="customer" class = "start_2.Customer" column = "lkm_cust_id"/>
</class>
</hibernate-mapping>
核心配置文件:
hibernate.cfg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 連接數據庫的基本參數 -->
<property name = "hibernate.connection.driver_class" >com.mysql.jdbc.Driver</property>
<property name = "hibernate.connection.url" >jdbc:mysql:///hibernate_start2</property>
<property name = "hibernate.connection.username" >root</property>
<property name = "hibernate.connection.password" >2e5y8hxf</property>
<!-- hibernate方言設置 -->
<property name = "hibernate.dialect" >org.hibernate.dialect.MySQLDialect</property>
<!-- 可選配置 -->
<!-- 打印SQL -->
<property name = "hibernate.show_sql">true</property>
<!-- 格式化SQL -->
<property name = "hibernate.format_sql">true</property>
<!-- 自動創建表 -->
<property name = "hibernate.hbm2ddl.auto">update</property>
<!-- 配置當前線程綁定的 Session -->
<property name="hibernate.current_session_context_class">thread</property>
<mapping resource = "start_2/Customer.hbn.xml"/>
<mapping resource = "start_2/Linkman.hbn.xml"/>
</session-factory>
</hibernate-configuration>
6.編寫測試類
public class TestDemo {
@Test
public void demo1() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//創建兩個客戶
Customer customer1 = new Customer();
customer1.setCust_name("張三丰");
Customer customer2 = new Customer();
customer2.setCust_name("張無忌");
//創建三個聯繫人
Linkman linkman1 = new Linkman();
linkman1.setLkm_name("郭襄");
Linkman linkman2 = new Linkman();
linkman2.setLkm_name("趙敏");
Linkman linkman3 = new Linkman();
linkman3.setLkm_name("周芷若");
//設置關係
linkman1.setCustomer(customer1);
linkman2.setCustomer(customer1);
linkman3.setCustomer(customer2);
customer1.getLinkmans().add(linkman1);
customer1.getLinkmans().add(linkman2);
customer2.getLinkmans().add(linkman3);
//保存數據
session.save(customer1);
session.save(customer2);
session.save(linkman1);
session.save(linkman2);
session.save(linkman3);
tx.commit();
}
}
測試結果:
7.一對多中的級聯保存或更新
問題由來:當兩張表(A和B)建立起相互關聯的關係時,若此時只保存表A中的對象customer,則customer會成爲持久態對象,這時對象linkman未保存到表B中,成爲瞬時態對象。由於持久態對象不能與瞬時態對象進行關聯。
級聯:指操作(比如保存操作)一個對象時,會同時操作其關聯的對象。
級聯的方向性:
操作一的一方的時候,看看其是否操作到多的一方;
操作多的一方的時候,看看其是否操作到一的一方;
舉例:
操作一的一方,級聯到多的一方:在Customer.hbn.xml中加上cascade = “save-update”
操作多的一方,級聯到一的一方:在Linkman.hbn.xml中加上cascade = “save-update”
測試類代碼:
@Test
public void demo2() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("令狐沖1");
Linkman linkman = new Linkman();
linkman.setLkm_name("任盈盈1");
linkman.setCustomer(customer);
customer.getLinkmans().add(linkman);
//session.save(customer);
session.save(linkman);
tx.commit();
}
8.對象導航測試
概述:對象的導航即對象之間的關聯關係和順序,下面舉例說明;
舉例:
@Test
public void demo3() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("張無忌");
Linkman linkman1 = new Linkman();
linkman1.setLkm_name("趙敏");
Linkman linkman2 = new Linkman();
linkman2.setLkm_name("周芷若");
Linkman linkman3 = new Linkman();
linkman3.setLkm_name("小昭");
linkman1.setCustomer(customer);
customer.getLinkmans().add(linkman2);
customer.getLinkmans().add(linkman3);
//session.save(linkman1); //發送4條insert語句
//session.save(customer); //發送3條insert語句
session.save(linkman2); //發送1條insert語句
tx.commit();
}
測試結果:
session.save(linkman1); //發送4條insert語句
分析:這時,linkman1被保存,同時linkman1關聯的customer也被保存,之後與customer關聯的linkman2和linkman3也被保存,所以一共執行了4條insert語句。
session.save(customer); //發送3條insert語句
分析:這時保存customer,同時存了customer關聯的linkman2和linkman3,這裏的linkman1與customer是單向關聯,所以不能通過customer來級聯linkman1。
session.save(linkman2); //發送1條insert語句
分析:這裏linkman2與customer是從customer到linkman2的單向關聯,所以不能從linkman2級聯出customer,所以只有linkman2一條insert語句。
9.一對多中的級聯刪除
概述:在JDBC中,無法刪除一對多關聯中“一”一方的表數據,必須先刪除“多”的一方中的表數據,才能解除關聯,才能夠刪除“一”的一方的表數據。在Hibernate中,默認情況下,可以直接刪除存在關聯的“一”的一方的表數據,其實現過程是先在“多”的一方將外鍵置空,然後刪除“一”的一方的相關數據;
級聯刪除:在Hibernate中不使用默認的情況,可以直接刪除“一”的一方的數據,同時級聯刪除“多”的一方的相關聯數據(比如:刪除了一個客戶信息,同時刪除了這個客戶關聯的所有訂單信息)。另外,也可以反過來,刪除訂單信息,同時也刪除了客戶信息,但是不常用,這裏忽略!
舉例:
@Test
public void demo4() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
Customer customer = session.get(Customer.class, 1l);
session.delete(customer);
tx.commit();
}
測試結果:
刪除前:
刪除後:
10.inverse配置
雙向關係維護產生的多餘的SQL語句:由於“一”的一方和“多”的一方都能夠維護“多”的一方表中的外鍵,所以會產生髮送兩次SQL語句去更新“多”的一方表中的外鍵;
發送兩次更新外鍵代碼:
@Test
public void demo5() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
Customer customer = session.get(Customer.class, 2l);
Linkman linkman = session.get(Linkman.class, 1l);
linkman.setCustomer(customer);
customer.getLinkmans().add(linkman);
tx.commit();
}
這裏產生兩條update外鍵lkm_cust_id的語句:
使用inverse配置:inverse默認爲false,爲擁有外鍵維護權;
代碼:
@Test
public void demo5() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
Customer customer = session.get(Customer.class, 2l);
Linkman linkman = session.get(Linkman.class, 1l);
linkman.setCustomer(customer); //linkman可以維護外鍵,也就是說可以修改
customer.getLinkmans().add(linkman); //這裏customer配置文件中放棄外鍵維護權,所以不能修改外鍵
tx.commit();
}
這時,只發送一條update修改外鍵的語句:
cascade與inverse的區別:cascade是級聯操作,可以在操作一個表的同時操作另外一張表;inverse是放棄外鍵維護權,若設置爲true,則該配置文件相應的對象將失去外鍵維護權,即通過該對象修改不了外鍵。
三、Hibernate中多對多關係
1.建立工程,引入jar包
2.創建表
由於是多對多的關係,所以需要創建三個表:用戶表、角色表、中間表
用戶表:
CREATE TABLE `sys_user` (
`user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用戶id',
`user_code` varchar(32) NOT NULL COMMENT '用戶賬號',
`user_name` varchar(64) NOT NULL COMMENT '用戶名稱',
`user_password` varchar(32) NOT NULL COMMENT '用戶密碼',
`user_state` char(1) NOT NULL COMMENT '1:正常,0:暫停',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
角色表:
CREATE TABLE `sys_role` (
`role_id` bigint(32) NOT NULL AUTO_INCREMENT,
`role_name` varchar(32) NOT NULL COMMENT '角色名稱',
`role_memo` varchar(128) DEFAULT NULL COMMENT '備註',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
中間表:
CREATE TABLE `sys_user_role` (
`role_id` bigint(32) NOT NULL COMMENT '角色id',
`user_id` bigint(32) NOT NULL COMMENT '用戶id',
PRIMARY KEY (`role_id`,`user_id`),
KEY `FK_user_role_user_id` (`user_id`),
CONSTRAINT `FK_user_role_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_user_role_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.創建實體類
要創建的實體類包含兩個:User.java 和Role.java (這裏省略get和set方法),在多對多中,要表示兩個類之間的關係,需要在類中放置對方的集合。
User.java
public class User {
private Long user_id;
private String user_code;
private String user_name;
private String user_password;
private String user_state;
private Set<Role> roles = new HashSet<Role>();
}
Role.java
public class Role {
private Long role_id;
private String role_name;
private String role_memo;
private Set<User> users = new HashSet<User>();
}
4.創建映射配置文件
映射配置文件包括:User類映射配置文件(User.hbn.xml)和Role類映射配置文件(Role.hbn.xml)。
User.hbn.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name = "start_3.User" table = "sys_user">
<!-- 建立OID與主鍵映射 -->
<id name = "user_id" column = "user_id">
<generator class="native"/>
</id>
<!-- 建立普通屬性與數據庫表字段的映射 -->
<property name="user_code" column = "user_code"/>
<property name="user_name" column = "user_name"/>
<property name="user_password" column = "user_password"/>
<property name="user_state" column = "user_state"/>
<!-- 配置與User的多對多的關係映射 -->
<!-- Set標籤:
*name :相關的對方的集合的屬性名稱
*table :多對多的關係需要使用中間表,這裏放的是中間表的名稱
Key標籤:
*column :當前的對象對應的中間表中的外鍵名稱
many-to-many標籤:
*class :對方類的全路徑
*column :對方的對象在中間表的外鍵名稱
-->
<set name="roles" table = "sys_user_role">
<key column = "user_id"/>
<many-to-many class = "start_3.Role" column = "role_id"/>
</set>
</class>
</hibernate-mapping>
Role.hbn.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name = "start_3.Role" table = "sys_role">
<!-- 建立OID與主鍵映射 -->
<id name = "role_id" column = "role_id">
<generator class="native"/>
</id>
<!-- 建立普通屬性與數據庫表字段的映射 -->
<property name="role_name" column = "role_name"/>
<property name="role_memo" column = "role_memo"/>
<!-- 配置與User的多對多的關係映射 -->
<!-- Set標籤:
*name :相關的對方的集合的屬性名稱
*table :多對多的關係需要使用中間表,這裏放的是中間表的名稱
Key標籤:
*column :當前的對象對應的中間表中的外鍵名稱
many-to-many標籤:
*class :對方類的全路徑
*column :對方的對象在中間表的外鍵名稱
-->
<set name="users" table = "sys_user_role" inverse = "true">
<key column = "role_id"/>
<many-to-many class = "start_3.User" column = "user_id"/>
</set>
</class>
</hibernate-mapping>
注意:在多對多中,對於雙向關聯,只能有一方能夠維護中間表中的外鍵,如果雙方都能維護中間表中的外鍵,則會出現中間表中的主鍵重複的情況(中間表中的主鍵可以認爲是由兩個元素共同構成,即user_id和role_id)。一般而已,通常是被動方放棄外鍵維護權利,另外,若不採用雙向關聯,則雙方都可以有外鍵維護權利。
5.創建測試類
public class TestDemo1 {
@Test
public void demo1() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//創建2個用戶
User user1 = new User();
user1.setUser_name("陳赫");
User user2 = new User();
user2.setUser_name("鄧超");
//創建3個角色
Role role1 = new Role();
role1.setRole_name("美國隊長");
Role role2 = new Role();
role2.setRole_name("鋼鐵俠");
Role role3 = new Role();
role3.setRole_name("蜻蜓隊長");
//這裏採用雙向關聯
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
role1.getUsers().add(user1);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
role3.getUsers().add(user2);
//保存數據
session.save(user1);
session.save(user2);
session.save(role1);
session.save(role2);
session.save(role3);
tx.commit();
}
}
測試結果:
6.多對多關係的級聯保存
舉例:
配置文件設置:在User.hbn.xml配置文件中設置
代碼:
@Test
public void demo2() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//創建2個用戶
User user1 = new User();
user1.setUser_name("陳赫");
//創建3個角色
Role role1 = new Role();
role1.setRole_name("美國隊長");
//這裏採用雙向關聯
user1.getRoles().add(role1);
//role1.getUsers().add(user1); //因爲role1無外鍵維護權利,所以這行代碼冗餘
session.save(user1);
tx.commit();
}
分析:這裏的role1爲被動方,設置放棄外鍵維護權利,所以只能保存user1來級聯保存role1,而不能通過保存role1來級聯保存user1。
測試結果:級聯保存成功。
7.多對多關係的級聯刪除
舉例:
配置文件:在User.hbn.xml中配置
代碼:
@Test
public void demo3() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//獲取要刪除的對象數據
User user1 = session.get(User.class, 1l);
session.delete(user1);
tx.commit();
}
刪除前:
刪除後:
注意事項:級聯刪除時,比如刪除用戶id爲1的用戶,同時刪除用戶1級聯的角色1和2,若角色1或2也與其他用戶相關聯,則刪除不了,報錯。
8.多對多關係中的其他操作
概述:在多對多關係中,這裏的其他操作包含:給用戶選擇角色、給用戶改選角色、給用戶刪除角色,這裏的操作主要是對集合的操作。
給用戶選擇角色:
@Test
public void demo4() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
User user1 = session.get(User.class, 1l);
Role role3 = session.get(Role.class, 3l);
user1.getRoles().add(role3);
tx.commit();
}
給用戶改選角色:
@Test
public void demo5() { //用戶1選擇的角色3,改選成角色2
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
User user1 = session.get(User.class, 1l);
Role role2 = session.get(Role.class, 2l);
Role role3 = session.get(Role.class, 3l);
user1.getRoles().remove(role3);
user1.getRoles().add(role2);
tx.commit();
}
給用戶刪除角色:
@Test
public void demo5() { //用戶1刪除角色2
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
User user1 = session.get(User.class, 1l);
Role role2 = session.get(Role.class, 2l);
user1.getRoles().remove(role2);
tx.commit();
}