前言
本博文主要講解介紹Hibernate框架,ORM的概念和Hibernate入門,相信你們看了就會使用Hibernate了!
什麼是Hibernate框架?
Hibernate是一種ORM框架,全稱爲 Object_Relative DateBase-Mapping,在Java對象與關係數據庫之間建立某種映射,以實現直接存取Java對象!
爲什麼要使用Hibernate?
既然Hibernate是關於Java對象和關係數據庫之間的聯繫的話,也就是我們MVC中的數據持久層->在編寫程序中的DAO層...
首先,我們來回顧一下我們在DAO層寫程序的歷程吧:
在DAO層操作XML,將數據封裝到XML文件上,讀寫XML文件數據實現CRUD
在DAO層使用原生JDBC連接數據庫,實現CRUD
嫌棄JDBC的Connection\Statement\ResultSet等對象太繁瑣,使用對原生JDBC的封裝組件-->DbUtils組件
我們來看看使用DbUtils之後,程序的代碼是怎麼樣的:
public class CategoryDAOImpl implements zhongfucheng.dao.CategoryDao { @Override public void addCategory(Category category) { QueryRunner queryRunner = new QueryRunner(Utils2DB.getDataSource()); String sql = "INSERT INTO category (id, name, description) VALUES(?,?,?)"; try { queryRunner.update(sql, new Object[]{category.getId(), category.getName(), category.getDescription()}); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public Category findCategory(String id) { QueryRunner queryRunner = new QueryRunner(Utils2DB.getDataSource()); String sql = "SELECT * FROM category WHERE id=?"; try { Category category = (Category) queryRunner.query(sql, id, new BeanHandler(Category.class)); return category; } catch (SQLException e) { throw new RuntimeException(e); } } @Override public List<Category> getAllCategory() { QueryRunner queryRunner = new QueryRunner(Utils2DB.getDataSource()); String sql = "SELECT * FROM category"; try { List<Category> categories = (List<Category>) queryRunner.query(sql, new BeanListHandler(Category.class)); return categories; } catch (SQLException e) { throw new RuntimeException(e); } } }
其實使用DbUtils時,DAO層中的代碼編寫是很有規律的。
當插入數據的時候,就將JavaBean對象拆分,拼裝成SQL語句
當查詢數據的時候,用SQL把數據庫表中的列組合,拼裝成JavaBean對象
也就是說:javaBean對象和數據表中的列存在映射關係!如果程序能夠自動生成SQL語句就好了....那麼Hibernate就實現了這個功能!
簡單來說:我們使用Hibernate框架就不用我們寫很多繁瑣的SQL語句,從而簡化我們的開發!
ORM概述
在介紹Hibernate的時候,說了Hibernate是一種ORM的框架。那什麼是ORM呢?ORM是一種思想
O代表的是Objcet
R代表的是Relative
M代表的是Mapping
ORM->對象關係映射....ORM關注是對象與數據庫中的列的關係
Hibernate快速入門
學習一個框架無非就是三個步驟:
引入jar開發包
配置相關的XML文件
熟悉API
引入相關jar包
我們使用的是Hibernate3.6的版本
hibernate3.jar核心 + required 必須引入的(6個) + jpa 目錄 + 數據庫驅動包
編寫對象和對象映射
編寫一個User對象->User.java
public class User { private int id; private String username; private String password; private String cellphone; //各種setter和getter}
編寫對象映射->User.hbm.xml。一般它和JavaBean對象放在同一目錄下
我們是不知道該XML是怎麼寫的,可以搜索一下Hibernate文件夾中後綴爲.hbm.xml
。看看它們是怎麼寫的。然後複製一份過來
<?xml version="1.0"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><!-- This mapping demonstrates content-based discrimination for the table-per-hierarchy mapping strategy, using a formula discriminator.--><hibernate-mapping package="org.hibernate.test.array"> <class name="A" lazy="true" table="aaa"> <id name="id"> <generator class="native"/> </id> <array name="bs" cascade="all" fetch="join"> <key column="a_id"/> <list-index column="idx"/> <one-to-many class="B"/> </array> </class> <class name="B" lazy="true" table="bbb"> <id name="id"> <generator class="native"/> </id> </class></hibernate-mapping>
在上面的模板上修改~下面會具體講解這個配置文件!
<!--在domain包下--><hibernate-mapping package="zhongfucheng.domain"> <!--類名爲User,表名也爲User--> <class name="User" table="user"> <!--主鍵映射,屬性名爲id,列名也爲id--> <id name="id" column="id"> <!--根據底層數據庫主鍵自動增長--> <generator class="native"/> </id> <!--非主鍵映射,屬性和列名一一對應--> <property name="username" column="username"/> <property name="cellphone" column="cellphone"/> <property name="password" column="password"/> </class></hibernate-mapping>
主配置文件
hibernate.cfg.xml
如果使用Intellij Idea生成的Hibernate可以指定生成出主配置文件hibernate.cfg.xml
,它是要放在src目錄下的
如果不是自動生成的,我們可以在Hibernate的hibernate-distribution-3.6.0.Final\project\etc
這個目錄下可以找到
它長得這個樣子:
<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"><hibernate-configuration> <session-factory> <property name="connection.url."/> <property name="connection.driver_class"/> <property name="connection.username"/> <property name="connection.password"/> <!-- DB schema will be updated if needed --> <!-- <property name="hbm2ddl.auto">update</property> --> </session-factory></hibernate-configuration>
通過上面的模板進行修改,後面會有對該配置文件進行講解!
<hibernate-configuration> <!-- 通常,一個session-factory節點代表一個數據庫 --> <session-factory> <!-- 1. 數據庫連接配置 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql:///zhongfucheng</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <!-- 數據庫方法配置, hibernate在運行的時候,會根據不同的方言生成符合當前數據庫語法的sql --> <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property> <!-- 2. 其他相關配置 --> <!-- 2.1 顯示hibernate在運行時候執行的sql語句 --> <property name="hibernate.show_sql">true</property> <!-- 2.2 格式化sql --> <property name="hibernate.format_sql">true</property> <!-- 2.3 自動建表 --> <property name="hibernate.hbm2ddl.auto">create</property> <!--3. 加載所有映射--> <mapping resource="zhongfucheng/domain/User.hbm.xml"/> </session-factory></hibernate-configuration>
測試
package zhongfucheng.domain;import org.hibernate.SessionFactory;import org.hibernate.Transaction;import org.hibernate.cfg.Configuration;import org.hibernate.classic.Session;/** * Created by ozc on 2017/5/6. */public class App { public static void main(String[] args) { //創建對象 User user = new User(); user.setPassword("123"); user.setCellphone("122222"); user.setUsername("nihao"); //獲取加載配置管理類 Configuration configuration = new Configuration(); //不給參數就默認加載hibernate.cfg.xml文件, configuration.configure(); //創建Session工廠對象 SessionFactory factory = configuration.buildSessionFactory(); //得到Session對象 Session session = factory.openSession(); //使用Hibernate操作數據庫,都要開啓事務,得到事務對象 Transaction transaction = session.getTransaction(); //開啓事務 transaction.begin(); //把對象添加到數據庫中 session.save(user); //提交事務 transaction.commit(); //關閉Session session.close(); } }
值得注意的是:JavaBean的主鍵類型只能是int類型,因爲在映射關係中配置是自動增長的,String類型是不能自動增長的。如果是你設置了String類型,又使用了自動增長,那麼就會報出下面的錯誤!
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'zhongfucheng.user' does
執行完程序後,Hibernate就爲我們創建對應的表,並把數據存進了數據庫了
我們看看快速入門案例的代碼用到了什麼對象吧,然後一個一個講解
public static void main(String[] args) { //創建對象 User user = new User(); user.setPassword("123"); user.setCellphone("122222"); user.setUsername("nihao"); //獲取加載配置管理類 Configuration configuration = new Configuration(); //不給參數就默認加載hibernate.cfg.xml文件, configuration.configure(); //創建Session工廠對象 SessionFactory factory = configuration.buildSessionFactory(); //得到Session對象 Session session = factory.openSession(); //使用Hibernate操作數據庫,都要開啓事務,得到事務對象 Transaction transaction = session.getTransaction(); //開啓事務 transaction.begin(); //把對象添加到數據庫中 session.save(user); //提交事務 transaction.commit(); //關閉Session session.close(); }
Configuration
配置管理類:主要管理配置文件的一個類
它擁有一個子類AnnotationConfiguration,也就是說:我們可以使用註解來代替XML配置文件來配置相對應的信息
configure方法
configure()方法用於加載配置文件
加載主配置文件的方法
如果指定參數,那麼加載參數的路徑配置文件
如果不指定參數,默認加載src/目錄下的hibernate.cfg.xml
buildSessionFactory方法
buildSessionFactory()用於創建Session工廠
SessionFactory
SessionFactory-->Session的工廠,也可以說代表了hibernate.cfg.xml這個文件...hibernate.cfg.xml的就有<session-factory>
這麼一個節點
openSession方法
創建一個Session對象
getCurrentSession方法
創建Session對象或取出Session對象
Session
Session是Hibernate最重要的對象,Session維護了一個連接(Connection),只要使用Hibernate操作數據庫,都需要用到Session對象
通常我們在DAO層中都會有以下的方法,Session也爲我們提供了對應的方法來實現!
public interface IEmployeeDao { void save(Employee emp); void update(Employee emp); Employee findById(Serializable id); List<Employee> getAll(); List<Employee> getAll(String employeeName); List<Employee> getAll(int index, int count); void delete(Serializable id); }
更新操作
我們在快速入門中使用到了save(Objcet o)方法,調用了這個方法就把對象保存在數據庫之中了。Session對象還提供着其他的方法來進行對數據庫的更新
session.save(obj); 【保存一個對象】
session.update(obj); 【更新一個對象】
session.saveOrUpdate(obj); 【保存或者更新的方法】
沒有設置主鍵,執行保存;
有設置主鍵,執行更新操作;
如果設置主鍵不存在報錯!
我們來使用一下update()方法吧....既然是更新操作了,那麼肯定需要設置主鍵的,不設置主鍵,數據庫怎麼知道你要更新什麼。將id爲1的記錄修改成如下:
user.setId(1); user.setPassword("qwer"); user.setCellphone("1111"); user.setUsername("zhongfucheng");
主鍵查詢
通過主鍵來查詢數據庫的記錄,從而返回一個JavaBean對象
session.get(javaBean.class, int id); 【傳入對應的class和id就可以查詢】
session.load(javaBean.class, int id); 【支持懶加載】
User重寫toString()來看一下效果:
User user1 = (User) session.get(User.class, 1); System.out.println(user1);
HQL查詢
HQL:hibernate query language 即hibernate提供的面向對象的查詢語言
查詢的是對象以及對象的屬性【它查詢的是對象以及屬性,因此是區分大小寫的!】。
SQL:Struct query language 結構化查詢語言
查詢的是表以及列【不區分大小寫】
HQL是面向對象的查詢語言,可以用來查詢全部的數據!
Query query = session.createQuery("FROM User"); List list = query.list(); System.out.println(list);
當然啦,它也可以傳遞參數進去查詢
Query query = session.createQuery("FROM User WHERE id=?"); //這裏的?號是從0開始的,並不像JDBC從1開始的! query.setParameter(0, user.getId()); List list = query.list(); System.out.println(list);
QBC查詢
QBC查詢: query by criteria 完全面向對象的查詢
從上面的HQL查詢,我們就可以發現:HQL查詢是需要SQL的基礎的,因爲還是要寫少部分的SQL代碼....QBC查詢就是完全的面向對象查詢...但是呢,我們用得比較少
我們來看一下怎麼使用吧:
//創建關於user對象的criteria對象 Criteria criteria = session.createCriteria(User.class); //添加條件 criteria.add(Restrictions.eq("id", 1)); //查詢全部數據 List list = criteria.list(); System.out.println(list);
本地SQL查詢
有的時候,如果SQL是非常複雜的,我們不能靠HQL查詢來實現功能的話,我們就需要使用原生的SQL來進行復雜查詢了!
但是呢,它有一個缺陷:它是不能跨平臺的...因此我們在主配置文件中已經配置了數據庫的“方言“了。
我們來簡單使用一下把:
//將所有的記錄封裝成User對象存進List集合中 SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM user").addEntity(User.class); List list = sqlQuery.list(); System.out.println(list);
beginTransaction方法
開啓事務,返回的是一個事務對象....Hibernate規定所有的數據庫操作都必須在事務環境下進行,否則報錯!
Hibernate註解開發
在Hibernate中我們一般都會使用註解,這樣可以幫助我們大大簡化hbm映射文件的配置。下面我就來爲大家詳細介紹。
PO類註解配置
首先肯定是搭建好Hibernate的開發環境啦,我在此也不過多贅述,讀者自行實踐。接着在src目錄下創建一個cn.itheima.domain包,並在該包下創建一個Book實體類,由於Book實體類中寫有註解配置,所以就不用編寫那個映射配置文件啦!
@Entity // 定義了一個實體 @Table(name="t_book",catalog="hibernateTest") public class Book { @Id // 這表示一個主鍵 // @GeneratedValue 相當於native主鍵生成策略 @GeneratedValue(strategy=GenerationType.IDENTITY) // 相當於identity主鍵生成策略 private Integer id; // 主鍵 @Column(name="c_name", length=30, nullable=true) private String name; @Temporal(TemporalType.TIMESTAMP) // 是用來定義日期類型 private Date publicationDate; // 出版日期 @Type(type="double") // 允許你去指定Hibernate裏面的一些類型 private Double price; // 價格,如果沒有添加註解,也會自動的生成在表中 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 Date getPublicationDate() { return publicationDate; } public void setPublicationDate(Date publicationDate) { this.publicationDate = publicationDate; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
下面我就來詳細說一下Book實體類中的註解。
@Entity
:聲明一個實體。@Table
:來描述類與表之間的對應關係。@Entity // 定義了一個實體 @Table(name="t_book",catalog="hibernateTest") public class Book { ...... }
1
2
3
4
5
@id
:聲明一個主鍵。@GeneratedValue
:用它來聲明一個主鍵生成策略。默認情況是native主鍵生成策略。可以選擇的主鍵生成策略有:AUTO、IDENTITY、SEQUENCE。@Id // 這表示一個主鍵 // @GeneratedValue 相當於native主鍵生成策略 @GeneratedValue(strategy=GenerationType.IDENTITY) // 相當於identity主鍵生成策略 private Integer id; // 主鍵
1
2
3
4
@Column
:定義列。@Column(name="c_name", length=30, nullable=true) private String name;
注意:對於PO類中所有屬性,如果你不寫註解,默認情況下也會在表中生成對應的列,列的名稱就是屬性的名稱,列的類型也即屬性的類型。
1
2
@Temporal
:聲明日期類型。@Temporal(TemporalType.TIMESTAMP) // 是用來定義日期類型 private Date publicationDate; // 出版日期
日期類型可以選擇的有:
TemporalType.DATA:只有年月日。
TemporalType.TIME:只有小時分鐘秒。
TemporalType.TIMESTAMP:有年月日小時分鐘秒。
1
2
@Type
:可允許你去指定Hibernate裏面的一些類型。@Type(type="double") // 允許你去指定Hibernate裏面的一些類型 private Double price; // 價格,如果沒有添加註解,也會自動的生成在表中
1
2
編寫完上述Book實體類之後,千萬不要忘記下面這一步:我們最終需要在hibernate.cfg.xml文件中將我們類中的註解配置引用生效。即需要在Hibernate核心配置文件中添加如下配置:
<mapping class="cn.itheima.domain.Book"/>
1
最後我們在src目錄下創建一個cn.itheima.test包,在該包下編寫一個HibernateAnnotationTest單元測試類,並在該類中編寫一個用於測試PO類的註解開發的方法:
public class HibernateAnnotationTest { // 測試PO的註解開發 @Test public void test1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); Book b = new Book(); b.setName("情書"); b.setPrice(56.78); b.setPublicationDate(new Date()); session.save(b); session.getTransaction().commit(); session.close(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
現在來思考兩個問題:
如果主鍵生成策略我們想使用UUID類型呢?
如何設定類的屬性不在表中映射?
這兩個問題我們一起解決。廢話不多說,直接上例子。在cn.itheima.domain包下再編寫一個Person實體類,同樣使用註解配置。
@Entity @Table(name="t_person", catalog="hibernateTest") public class Person { // 生成UUID的主鍵生成策略 @Id @GenericGenerator(name="myuuid", strategy="uuid") // 聲明一種主鍵生成策略(uuid) @GeneratedValue(generator="myuuid") // 引用uuid主鍵生成策略 private String id; @Type(type="string") // 允許你去指定Hibernate裏面的一些類型 private String name; @Transient private String msg; // 現在這個屬性不想生成在表中 public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
當然不要忘記這一步了:我們最終需要在hibernate.cfg.xml文件中將我們類中的註解配置引用生效,故需要在Hibernate核心配置文件中添加如下配置:
<mapping class="cn.itheima.domain.Person"/>
1
最後在HibernateAnnotationTest單元測試類中編寫如下一個方法:
public class HibernateAnnotationTest { // 測試uuid的主鍵生成策略及不生成表中映射 @Test public void test2() { Session session = HibernateUtils.openSession(); session.beginTransaction(); Person p = new Person(); p.setName("李四"); p.setMsg("這是一個好人"); session.save(p); session.getTransaction().commit(); session.close(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
至此,兩個問題就解決了。
注意:對於我們以上講解的關於屬性配置的註解,我們也可以在其對應的getXxx方法去使用。
Hibernate關聯映射——一對多(多對一)
仍以客戶(Customer)和訂單(Order)爲例來開始我的表演。
在src目錄下創建一個cn.itheima.oneToMany包,並在該包編寫這兩個實體類:
客戶(Customer)類
// 客戶 ---- 一的一方 @Entity @Table(name="t_customer") public class Customer { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; // 主鍵 private String name; // 姓名 // 描述客戶可以有多個訂單 /* * targetEntity="...":相當於<one-to-many class="..."> * mappedBy="...":相當於inverse=true,即放棄關聯關係的維護,不然會生成一箇中間表 */ @OneToMany(targetEntity=Order.class,mappedBy="c") private Set<Order> orders = new HashSet<Order>(); public Set<Order> getOrders() { return orders; } public void setOrders(Set<Order> orders) { this.orders = orders; } 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; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
訂單(Order)類
// 訂單 ---- 多的一方 @Entity @Table(name="t_order") public class Order { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private Double money; private String receiverInfo; // 收貨地址 // 訂單與客戶關聯 @ManyToOne(targetEntity=Customer.class) @JoinColumn(name="c_customer_id") // 指定外鍵列 private Customer c; // 描述訂單屬於某一個客戶 public Customer getC() { return c; } public void setC(Customer c) { this.c = c; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } public String getReceiverInfo() { return receiverInfo; } public void setReceiverInfo(String receiverInfo) { this.receiverInfo = receiverInfo; } }
這兒用到了
@OneToMany
和@ManyToOne
這兩個註解。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
以上兩個實體類編寫好之後,可以很明顯的看出我們不需要寫它們對應的映射配置文件了,是不是很爽呢!接下來,我就要編寫測試程序測試一下了。現在我的需求是保存客戶時,順便保存訂單,對於這種情況我們需要在Customer類中配置cascade操作,即配置cascade="save-update"
,配置的方式有兩種,下面我細細說來:
第一種方式,可以使用JPA提供的註解。
那麼@OneToMany
註解就應修改爲:@OneToMany(targetEntity=Order.class,mappedBy="c",cascade=CascadeType.ALL) private Set<Order> orders = new HashSet<Order>();
1
2
第二種方式,可以使用Hibernate提供的註解。
那麼@OneToMany
註解就應修改爲:
@OneToMany(targetEntity=Order.class,mappedBy="c") @Cascade(CascadeType.SAVE_UPDATE) private Set<Order> orders = new HashSet<Order>();
1
2
3
兩種方式都可以,口味任君選擇,不過我傾向於第二種方式。
接下來在HibernateAnnotationTest單元測試類中編寫如下方法進行測試:
public class HibernateAnnotationTest { // 測試one-to-many註解操作(保存客戶時級聯保存訂單) @Test public void test3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); // 1.創建一個客戶 Customer c = new Customer(); c.setName("葉子"); // 2.創建兩個訂單 Order o1 = new Order(); o1.setMoney(1000d); o1.setReceiverInfo("武漢"); Order o2 = new Order(); o2.setMoney(2000d); o2.setReceiverInfo("天門"); // 3.建立關係 c.getOrders().add(o1); c.getOrders().add(o2); // 4.保存客戶,並級聯保存訂單 session.save(c); session.getTransaction().commit(); session.close(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
這時運行以上方法,會發現雖然客戶表的那條記錄插進去了,但是訂單表就變成這個鬼樣了:
訂單表中沒有關聯客戶的id,這是爲什麼呢?原因是我們在Customer類中配置了mappedBy=”c”
,它代表的是外鍵的維護由Order方來維護,而Customer不維護,這時你在保存客戶時,級聯保存訂單,是可以的,但是不能維護外鍵,所以,我們必須在代碼中添加訂單與客戶之間的關係。所以須將test3方法修改爲:
public class HibernateAnnotationTest { // 測試one-to-many註解操作(保存客戶時級聯保存訂單) @Test public void test3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); // 1.創建一個客戶 Customer c = new Customer(); c.setName("葉子"); // 2.創建兩個訂單 Order o1 = new Order(); o1.setMoney(1000d); o1.setReceiverInfo("武漢"); Order o2 = new Order(); o2.setMoney(2000d); o2.setReceiverInfo("天門"); // 3.建立關係 // 原因:是爲了維護外鍵,不然的話,外鍵就不能正確的生成!!! o1.setC(c); o2.setC(c); // 原因:是爲了進行級聯操作 c.getOrders().add(o1); c.getOrders().add(o2); // 4.保存客戶,並級聯保存訂單 session.save(c); session.getTransaction().commit(); session.close(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
這時再測試,就沒有任何問題啦!
擴展
Hibernate註解@Cascade
中的DELETE_ORPHAN已經過時了,如下:
可使用下面方案來替換過時方案:
Hibernate關聯映射——多對多
以學生與老師爲例開始我的表演,我是使用註解完成這種多對多的配置。使用@ManyToMany
註解來配置多對多,只需要在一端配置中間表,另一端使用mappedBy表示放置外鍵的維護權。
在src目錄下創建一個cn.itheima.manyToMany包,並在該包編寫這兩個實體類:
學生類
@Entity @Table(name="t_student") public class Student { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private String name; @ManyToMany(targetEntity=Teacher.class) // @JoinTable:使用@JoinTable來描述中間表,並描述中間表中外鍵與Student、Teacher的映射關係 // joinColumns:它是用來描述Student與中間表的映射關係 // inverseJoinColumns:它是用來描述Teacher與中間表的映射關係 @JoinTable(name="s_t", joinColumns={@JoinColumn(name="c_student_id",referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="c_teacher_id")}) private Set<Teacher> teachers = new HashSet<Teacher>(); 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 Set<Teacher> getTeachers() { return teachers; } public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
老師類
@Entity @Table(name="t_teacher") public class Teacher { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private String name; @ManyToMany(targetEntity=Student.class, mappedBy="teachers") // 代表由對方來維護外鍵 private Set<Student> students = new HashSet<Student>(); 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 Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
接下來,我就要編寫測試程序測試一下了。 從上面可看出我們將外鍵的維護權利交由Student類來維護,現在我們演示保存學生時,將老師也級聯保存,對於這種情況我們需要在Student類中配置cascade操作,即配置cascade=”save-update”,如下:
@JoinTable(name="s_t", joinColumns={@JoinColumn(name="c_student_id",referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="c_teacher_id")}) @Cascade(CascadeType.SAVE_UPDATE) private Set<Teacher> teachers = new HashSet<Teacher>();
1
2
3
接下來在HibernateAnnotationTest單元測試類中編寫如下方法進行測試:
public class HibernateAnnotationTest { // 測試多對多級聯保存(保存學生時同時保存老師) @Test public void test4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); // 1.創建兩個老師 Teacher t1 = new Teacher(); t1.setName("Tom"); Teacher t2 = new Teacher(); t2.setName("Fox"); // 2.創建兩個學生 Student s1 = new Student(); s1.setName("張丹"); Student s2 = new Student(); s2.setName("葉紫"); // 3.學生關聯老師 s1.getTeachers().add(t1); s1.getTeachers().add(t2); s2.getTeachers().add(t1); s2.getTeachers().add(t2); // 保存學生同時保存老師 session.save(s1); session.save(s2); session.getTransaction().commit(); session.close(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
運行以上方法,一切正常。
接着我們測試級聯刪除操作。見下圖:
可在HibernateAnnotationTest單元測試類中編寫如下方法進行測試:
public class HibernateAnnotationTest { // 測試多對多級聯刪除(前提是建立了雙向的級聯) @Test public void test5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); Student s = session.get(Student.class, 1); session.delete(s); session.getTransaction().commit(); session.close(); } }
一位阿里 Java 工程師的技術小站。作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!(關注公衆號後回覆”Java“即可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送作者原創的Java學習指南、Java程序員面試指南等乾貨資源)