1. 程式人生 > >學習筆記——spring boot(10)

學習筆記——spring boot(10)

資料庫關聯

在進行資料庫操作的時候,總會遇到不同表之間的關聯,就好比如我在Spring Security許可權管理這篇部落格中遇到的使用者user與許可權authority之間關係的關聯繫結,這類表與表之間的繫結是十分重要的,所以這次就需要介紹一下hibernate關聯關係註解,當然部落格“碼農小汪——Hibernate學習”是轉載的,感謝原博主汪小哥的支援:

現實的世界中確實很少有孤立純在的東西,大多都是兩者之間純在某種關係的。有單向關係 ,如只能通過老師訪問學生,或者學生訪問老師 ;或者雙向關係: 兩個都可以相互的訪問。下面的關係有:

單向關係 
1-1 
1-N 
N-1 
N-N 
雙向關係 
1-1 
1-N 
N-N

單向N_1關聯是最多的,多個人住同一個地址,我們只需要從人這一端知道他的住址就行了,沒必要知道住同一地址的使用者。 為了讓我們兩個持久化實體關聯,程式在N的一端增加持久化實體的一個屬性,該屬性指向引用1的那一端的關聯實體。 對於N-1(不管是單向關聯還是雙向關聯),都只需要在N的一端使用@ManyToOne修飾關聯實體的屬性 ,而下面的一些屬性,可以通過表來記憶:

屬性 說明
Cascade 級聯操作策略CascadeType.ALL….
Fetch 抓取關聯實體的策略,之前說過FechType.Lazy 延遲和立即
targetEntity 該屬性的關聯實體的類名,在預設情況下通過反射獲得類名

預設情況下我們的targetEntity無需指定這個屬性的。但在一些特殊的情況下,例如使用@[email protected] 修飾 1-N N-N關聯的時候,關聯實體集合不帶泛型資訊就必須指定了

無連線的N-1


無連線也就是不需要第三張表,維護我們的關聯關係。對於N_1關聯關係,我們只需要在N的一端增加一列外來鍵即可。讓外來鍵的值記錄到該屬性的實體即可。Hibernate使用@JoinColumn來修飾代表關聯實體的屬性,用於對映底層的外來鍵列。這種就不使用連線表了

好啦,我們看一下例子(多個人對應同一個地址):

@Entity
@Table(name="person_inf")
public class Person
{
    @Id @Column(name="person_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private int age;
    // 定義該Person實體關聯的Address實體
    @ManyToOne(targetEntity=Address.class)
    // 對映外來鍵列,指定外來鍵列的列名為address_id、不允許為空
    @JoinColumn(name="address_id" , nullable=false)
    @Cascade(CascadeType.ALL)
    private Address address;
    ........
}

他們建立的表什麼樣子呢? 
person_inf 
person_id age name address_id 
address_inf 
address_id addressDetail

如何操作物件呢,因為address_id 是一個外來鍵。如果我們儲存一個Person物件在我們的表中的話,我們應該首先有一個Address 物件吧。可以是存在資料庫中的,也可以才瞬態物件。我們在插入的時候級聯操作,會先插入address_inf這個表中,然後才可以使用外接啦。不然會報錯的,因為沒有存在的外來鍵,肯定不行啦~

    Session session = HibernateUtil.currentSession();
    Transaction tx = session.beginTransaction();
    // 建立一個Person物件
    Person p = new Person();
    // 建立一個瞬態的Address物件
    Address a = new Address("廣州天河");          
    p.setName("Test");
    p.setAge(21);
    // 設定Person和Address之間的關聯關係
    p.setAddress(a);
    // 持久化Person物件
    session.persist(p);//級聯的插入操作哦~,地址一定先於person插入資料表中,這裡必須設定級聯操作,不然要報錯
    // 建立一個瞬態的Address物件
    Address a2 = new Address("上海虹口");        
    // 修改持久化狀態的Person物件
    p.setAddress(a2);                            
    tx.commit();
    HibernateUtil.closeSession();

有連線的N-1


我們的關聯關係的維護讓第三張表來維護啦。對於大部分的N_1單向關係,只要基於外來鍵的關聯關係維護已經夠了。 
如果有需要使用連線表來維護關聯關係,程式可以使用連線表顯示的維護這種關係,所謂連線表就是建立第三張表格來維護我們的關係就行了。使用@JoinTable

下面的是java給的例子,除了這些屬性外,我們還可以指定targetEntity。指定關聯的實體是哪個!也就是生成表的是哪個表所對應的實體。

 @JoinTable(
        name="CUST_PHONE",
        //相對於當前實體的外來鍵,的列名。 參照的列名,也就是資料表中的名字
        joinColumns=
            @JoinColumn(name="CUST_ID", referencedColumnName="ID"),
        //這個也是個外來鍵,只是不說當前實體裡面的屬性。
        inverseJoinColumns=
            @JoinColumn(name="PHONE_ID", referencedColumnName="ID")
    )


我們來看個例子就知道啦

@Entity
@Table(name="person_inf")
public class Person
{
    // 標識屬性
    @Id @Column(name="person_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private int age;
    // 定義該Person實體關聯的Address實體
    @ManyToOne(targetEntity=Address.class)
    // 顯式使用@JoinTable對映連線表
    @JoinTable(name="person_address", // 指定連線表的表名為person_address
        // 指定連線表中person_id外來鍵列,參照到當前實體對應表的主鍵列
        [email protected](name="person_id"
            , referencedColumnName="person_id", unique=true),
        // 指定連線表中address_id外來鍵列,參照到當前實體的關聯實體對應表的主鍵列
        [email protected](name="address_id"
            , referencedColumnName="address_id")
    )
    private Address address;
    .....
}
@Entity
@Table(name="address_inf")
public class Address
{
    // 標識屬性
    @Id @Column(name="address_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int addressId;
    // 定義地址詳細資訊的成員變數
    private String addressDetail;
}


這裡產生的表的話,以前的person的表不會改變address也不會變 
只是增加一個外表 
person_address ——table 
address_id person_id 
使用連線表維護關係

單向1-1關聯


看上去 1-1 和N-1 差不多啊,都需要在持久化實體中增加代表關聯實體的成員變數,從程式碼上看沒得什麼區別。因為N的一端和1的一端都可以直接的訪問關聯的實體。而無論單向的還是雙向的1-1關聯,都需要使用@OneToOne修飾關聯實體的屬性 。
有下面的級聯操作,抓取屬性。optional 關聯關係是否可選。目標關聯實體的類名targetEntity。還有個重要的 mappedBy:該屬性合法的屬性值為關聯實體的屬性名,該屬性指定關聯實體中的哪個屬性可引用當前的實體

例子:基於外來鍵的單向 1-1 關聯,無連線表:

@Entity
@Table(name="person_inf")
public class Person
{
    // 標識屬性
    @Id @Column(name="person_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private int age;
    // 定義該Person實體關聯的Address實體
    @OneToOne(targetEntity=Address.class)
    // 對映名為address_id的外來鍵列,參照關聯實體對應表的addres_id主鍵列
    @JoinColumn(name="address_id"
        , referencedColumnName="address_id" , unique=true)
    private Address address;
    //地址肯定是獨一無二的嘛,對不對!增加unique約束~
    .....
}


有連線表的也是差不多,理解就行了

@Entity
@Table(name="person_inf")
public class Person
{
    // 標識屬性
    @Id @Column(name="person_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private int age;
    // 定義該Person實體關聯的Address實體
    @OneToOne(targetEntity=Address.class)
    @JoinTable(name="person_address",
        [email protected](name="person_id"
            , referencedColumnName="person_id" , unique=true),
        [email protected](name="address_id"
            , referencedColumnName="address_id", unique=true)
    )
    private Address address;
    ......
}

單向的1-N


1的一端要訪問N的一端,肯定的加個集合和前面的集合很相似,但是現在的集合裡的元素是關聯的實體啦。對於單向的1—N關聯關係。只需要在1的一端加入set型別的成員變數,記錄所有的關聯的實體。就行了。具體怎麼操作,我們慢慢的說來。一個個字的打還是可以的。增加自己的理解 

@OneToMany 
級聯,抓取,目標實體,mappedBy

無連線表的單向1-N(1個人有多個住處):

@Entity
@Table(name="person_inf")
public class Person
{
    @Id @Column(name="person_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private int age;
    // 定義該Person實體所有關聯的Address實體,沒有指定cascade屬性
    @OneToMany(targetEntity=Address.class)
    // 對映外來鍵列,此處對映的外來鍵列將會新增到關聯實體對應的資料表中,為啥呢?
    @JoinColumn(name="person_id" , referencedColumnName="person_id")
    private Set<Address> addresses
        = new HashSet<>();
   ........
 }


這裡的外來鍵列不會增加到當前實體對應的資料表中,而是增加到,關聯實體Address對應的資料表中,有點特殊!為什麼。之前我們使用set集合的時候都必須在增加一個表記得?只不過這裡增加到了關聯實體裡面去了。而不是在增加一張表~~
N的端不維護關係,沒得變化~

有連線的1-N

@Entity
@Table(name="person_inf")
public class Person
{
    // 標識屬性
    @Id @Column(name="person_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private int age;
    // 定義該Person實體所有關聯的Address實體
    @OneToMany(targetEntity=Address.class)
    // 對映連線表為person_address
    @JoinTable(name="person_address",
        // 定義連線表中名為person_id的外來鍵列,該外來鍵列參照當前實體對應表的主鍵列
        [email protected](name="person_id"
            , referencedColumnName="person_id"),
        // 定義連線表中名為address_id的外來鍵列,
        // 該外來鍵列參照當前實體的關聯實體對應表的主鍵列
        [email protected](name="address_id"
            , referencedColumnName="address_id", unique=true)
    )
    private Set<Address> addresses
        = new HashSet<>();
    ...
}


這裡是1對N 1個人有多個住的地方,但是每個都是不一樣的,所以要增加unique約束 
因為採用連線表了,我們的Person表中沒有存在維護連線關係,可以任意的持久化操作。 
1個person 2個adress 要插入5次操作哦 
自己想想為什麼?

單向的N-N也是需要的


@ManyToMany 
N-N關係必須需要連線表啦,和有連線的1-N相似,但是要把unique約束去掉啦~ 
直接修改啦,上面那個~

@Entity
@Table(name="person_inf")
public class Person
{
    // 標識屬性
    @Id @Column(name="person_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private int age;
    // 定義該Person實體所有關聯的Address實體
    @ManyToMany(targetEntity=Address.class)
    // 對映連線表為person_address
    @JoinTable(name="person_address",
        // 定義連線表中名為person_id的外來鍵列,該外來鍵列參照當前實體對應表的主鍵列
        [email protected](name="person_id"
            , referencedColumnName="person_id"),
        // 定義連線表中名為address_id的外來鍵列,
        // 該外來鍵列參照當前實體的關聯實體對應表的主鍵列
        [email protected](name="address_id"
            , referencedColumnName="address_id")
    )
    private Set<Address> addresses
        = new HashSet<>();
...
}