本系列文章将整理到我在GitHub上的《Java面試指南》倉庫,更多精彩内容請到我的倉庫裡檢視
https://github.com/h2pl/Java-Tutorial
喜歡的話麻煩點下Star哈
文章首發于我的個人部落格:
www.how2playlife.com
本文是微信公衆号【Java技術江湖】的《走進JavaWeb技術世界》其中一篇,本文部分内容來源于網絡,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格内容,引用其中了一些比較好的部落格文章,如有侵權,請聯系作者。
該系列博文會告訴你如何從入門到進階,從servlet到架構,從ssm再到SpringBoot,一步步地學習JavaWeb基礎知識,并上手進行實戰,接着了解JavaWeb項目中經常要使用的技術群組件,包括日志元件、Maven、Junit,等等内容,以便讓你更完整地了解整個JavaWeb技術體系,形成自己的知識架構。為了更好地總結和檢驗你的學習成果,本系列文章也會提供每個知識點對應的面試題以及參考答案。
如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公衆号【Java技術江湖】聯系作者,歡迎你參與本系列博文的創作和修訂。
文末贈送8000G的Java架構師學習資料,需要的朋友可以到文末了解領取方式,資料包括Java基礎、進階、項目和架構師等免費學習資料,更有資料庫、分布式、微服務等熱門技術學習視訊,内容豐富,兼顧原理和實踐,另外也将贈送作者原創的Java學習指南、Java程式員面試指南等幹貨資源)
前言
本博文主要講解介紹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的ConnectionStatementResultSet等對象太繁瑣,使用對原生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>
<key column="a_id"/>
<list-index column="idx"/>
<one-to-many class="B"/>
</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>
如果使用Intellij Idea生成的Hibernate可以指定生成出主配置檔案hibernate.cfg.xml,它是要放在src目錄下的
如果不是自動生成的,我們可以在Hibernate的hibernate-distribution-3.6.0.Finalprojectetc這個目錄下可以找到
它長得這個樣子:
<?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的就有這麼一個節點
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;
}
}
下面我就來詳細說一下Book實體類中的注解。
@Entity:聲明一個實體。
@Table:來描述類與表之間的對應關系。
@Entity // 定義了一個實體
@Table(name="t_book",catalog="hibernateTest")
public class Book {
......
}
@id:聲明一個主鍵。
@GeneratedValue:用它來聲明一個主鍵生成政策。預設情況是native主鍵生成政策。可以選擇的主鍵生成政策有:AUTO、IDENTITY、SEQUENCE
@Id // 這表示一個主鍵
// @GeneratedValue 相當于native主鍵生成政策
@GeneratedValue(strategy=GenerationType.IDENTITY) // 相當于identity主鍵生成政策
private Integer id; // 主鍵
@Column:定義列。
@Column(name="c_name", length=30, nullable=true)
private String name;
注意:對于PO類中所有屬性,如果你不寫注解,預設情況下也會在表中生成對應的列,列的名稱就是屬性的名稱,列的類型也即屬性的類型。
@Temporal:聲明日期類型。
@Temporal(TemporalType.TIMESTAMP) // 是用來定義日期類型
private Date publicationDate; // 出版日期
日期類型可以選擇的有:
* TemporalType.DATA:隻有年月日。
* TemporalType.TIME:隻有小時分鐘秒。
* TemporalType.TIMESTAMP:有年月日小時分鐘秒。
@Type:可允許你去指定Hibernate裡面的一些類型。
@Type(type="double") // 允許你去指定Hibernate裡面的一些類型
private Double price; // 價格,如果沒有添加注解,也會自動的生成在表中
最後我們在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();
}
}
現在來思考兩個問題:
如果主鍵生成政策我們想使用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;
}
}
最後在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();
}
}
至此,兩個問題就解決了。
注意:對于我們以上講解的關于屬性配置的注解,我們也可以在其對應的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 >
*/
@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;
}
}
訂單(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
這兩個注解。
以上兩個實體類編寫好之後,可以很明顯的看出我們不需要寫它們對應的映射配置檔案了,是不是很爽呢!接下來,我就要編寫測試程式測試一下了。現在我的需求是儲存客戶時,順便儲存訂單,對于這種情況我們需要在Customer類中配置cascade操作,即配置cascade="save-update",配置的方式有兩種,下面我細細說來:
第一種方式,可以使用JPA提供的注解。
那麼@OneToMany注解就應修改為:
@OneToMany(targetEntity=Order.class,mappedBy="c",cascade=CascadeType.ALL)
private Set<Order> orders = new HashSet<Order>();
第二種方式,可以使用Hibernate提供的注解。
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Cascade(CascadeType.SAVE_UPDATE)
private Set<Order> orders = new HashSet<Order>();
兩種方式都可以,口味任君選擇,不過我傾向于第二種方式。
接下來在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();
}
}
這時運作以上方法,會發現雖然客戶表的那條記錄插進去了,但是訂單表就變成這個鬼樣了:
訂單表中沒有關聯客戶的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();
}
}
這時再測試,就沒有任何問題啦!
擴充
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;
}
}
老師類
@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;
}
}
接下來,我就要編寫測試程式測試一下了。 從上面可看出我們将外鍵的維護權利交由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>();
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();
}
}
運作以上方法,一切正常。
接着我們測試級聯删除操作。見下圖:
這裡寫圖檔描述
可在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();
}
}
參考文章
https://segmentfault.com/a/1190000009707894 https://www.cnblogs.com/hysum/p/7100874.html http://c.biancheng.net/view/939.html https://www.runoob.com/ https://blog.csdn.net/android_hl/article/details/53228348微信公衆号
個人公衆号:程式員黃小斜
微信公衆号【程式員黃小斜】新生代青年聚集地,程式員成長充電站。作者黃小斜,職業是阿裡程式員,身份是斜杠青年,希望和更多的程式員交朋友,一起進步和成長!專注于分享技術、面試、職場等成長幹貨,這一次,我們一起出發。
關注公衆号後回複“2020”領取我這兩年整理的學習資料,涵蓋自學程式設計、求職面試、算法刷題、Java技術學習、計算機基礎和考研等8000G資料合集。
技術公衆号:Java技術江湖
微信公衆号【Java技術江湖】一位阿裡 Java 工程師的技術小站,專注于 Java 相關技術:SSM、SpringBoot、MySQL、分布式、中間件、叢集、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術幹貨和學習經驗,緻力于Java全棧開發!
關注公衆号後回複“PDF”即可領取200+頁的《Java工程師面試指南》強烈推薦,幾乎涵蓋所有Java工程師必知必會的知識點。