走進JavaWeb技術世界13:Hibernate入門經典與註解式開發

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到個人倉庫裏查看html

https://github.com/h2pl/Java-Tutorial前端

喜歡的話麻煩點下Star哈java

文章首發於個人我的博客:python

www.how2playlife.commysql

本文是微信公衆號【Java技術江湖】的《走進JavaWeb技術世界》其中一篇,本文部份內容來源於網絡,爲了把本文主題講得清晰透徹,也整合了不少我認爲不錯的技術博客內容,引用其中了一些比較好的博客文章,若有侵權,請聯繫做者。android

該系列博文會告訴你如何從入門到進階,從servlet到框架,從ssm再到SpringBoot,一步步地學習JavaWeb基礎知識,並上手進行實戰,接着瞭解JavaWeb項目中常常要使用的技術和組件,包括日誌組件、Maven、Junit,等等內容,以便讓你更完整地瞭解整個JavaWeb技術體系,造成本身的知識框架。爲了更好地總結和檢驗你的學習成果,本系列文章也會提供每一個知識點對應的面試題以及參考答案。git

若是對本系列文章有什麼建議,或者是有什麼疑問的話,也能夠關注公衆號【Java技術江湖】聯繫做者,歡迎你參與本系列博文的創做和修訂。程序員

文末贈送8000G的Java架構師學習資料,須要的朋友能夠到文末了解領取方式,資料包括Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源) github

前言

本博文主要講解介紹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表明的是ObjcetR表明的是RelativeM表明的是MappingORM->對象關係映射....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工廠

SessionFactorySessionFactory-->Session的工廠,也能夠說表明了hibernate.cfg.xml這個文件...hibernate.cfg.xml的就有 這麼一個節點

openSession方法建立一個Session對象

getCurrentSession方法建立Session對象或取出Session對象

SessionSession是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註解就應修改成:

@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>();複製代碼

接下來在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();
        }複製代碼
}複製代碼

運行以上方法,一切正常。接着咱們測試級聯刪除操做。見下圖:這裏寫圖片描述

可在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://blog.csdn.net/android_hl/article/details/53228348

微信公衆號

我的公衆號:程序員黃小斜

​黃小斜是 985 碩士,阿里巴巴Java工程師,在自學編程、技術求職、Java學習等方面有豐富經驗和獨到看法,但願幫助到更多想要從事互聯網行業的程序員們。​做者專一於 JAVA 後端技術棧,熱衷於分享程序員乾貨、學習經驗、求職心得,以及自學編程和Java技術棧的相關乾貨。​黃小斜是一個斜槓青年,堅持學習和寫做,相信終身學習的力量,但願和更多的程序員交朋友,一塊兒進步和成長!

原創電子書:關注微信公衆號【程序員黃小斜】後回覆【原創電子書】便可領取我原創的電子書《菜鳥程序員修煉手冊:從技術小白到阿里巴巴Java工程師》這份電子書總結了我2年的Java學習之路,包括學習方法、技術總結、求職經驗和麪試技巧等內容,已經幫助不少的程序員拿到了心儀的offer!

程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取,包括Java、python、C++、大數據、機器學習、前端、移動端等方向的技術資料。

技術公衆號:Java技術江湖

若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人微信公衆號【Java技術江湖】

這是一位阿里 Java 工程師的技術小站。做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!

Java工程師必備學習資源:關注公衆號後回覆」Java「便可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源

個人公衆號


微信公衆號

我的公衆號:程序員黃小斜

​黃小斜是 985 碩士,阿里巴巴Java工程師,在自學編程、技術求職、Java學習等方面有豐富經驗和獨到看法,但願幫助到更多想要從事互聯網行業的程序員們。​做者專一於 JAVA 後端技術棧,熱衷於分享程序員乾貨、學習經驗、求職心得,以及自學編程和Java技術棧的相關乾貨。​黃小斜是一個斜槓青年,堅持學習和寫做,相信終身學習的力量,但願和更多的程序員交朋友,一塊兒進步和成長!

原創電子書:關注微信公衆號【程序員黃小斜】後回覆【原創電子書】便可領取我原創的電子書《菜鳥程序員修煉手冊:從技術小白到阿里巴巴Java工程師》這份電子書總結了我2年的Java學習之路,包括學習方法、技術總結、求職經驗和麪試技巧等內容,已經幫助不少的程序員拿到了心儀的offer!

程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取,包括Java、python、C++、大數據、機器學習、前端、移動端等方向的技術資料。

技術公衆號:Java技術江湖

若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人微信公衆號【Java技術江湖】

這是一位阿里 Java 工程師的技術小站。做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!

Java工程師必備學習資源:關注公衆號後回覆」Java「便可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源

個人公衆號

相關文章
相關標籤/搜索