之前我学习了单向的关联,现在继续学习,双向的关联。这个关联关系的理解还是有点复杂,慢慢的理解懂了就好啦!这个过程不是一蹴而就的。我们需要不断的积累,才可以有所成绩的。

年轻人,不要怕~慢慢来

对啦,有的时候我们可能会采用逆向工程产生实体哦。所以我们要看得懂,会修改,会改变

双向1-N关联

对于1-N的关联,Hibernate 推荐使用双向的关联,而不要让1的端(也就是有Set这个集合的那一个)控制关联的关系。而是使用N的一端控制关联关系。双向的1-N和N-1关联其实就是一回事。两端都需要增加对关联属性的访问,N的一端增加引用关联实体的属性,1的一端增加集合属性,集合元素为关联的实体.

Hibernate 同样对月双向关联的映射提供了两种技术支持:一种是有连接表的,一种是无连接表的。对于大部分来说,对于1-N的双向关联我们采用无连接表的策略即可。

无连接的双向的1-N关联

无连接的双向的1-N关联,N的一端肯定是使用@ManyToOne 我们肯定不会默生的涩,1的一端使用@OneToMany.

底层的数据库在记录这种1-N的关联关系的时候,其实就是我们的外键而已,只需要在N的一端增加一个外键列就好啦。因此在使用@ManyToOne的同时,还需要使用@JointColumn来映射外键列。

对于双向的1-N关联的映射,通常不会让1(set集合)的端控制关系,这样没次都需要更新我们的set集合,很不方便的说。而是该由我们的N的一端控制关联的关系。因此我们使用@OneToMany的时候中应该指明mappedBy (映射由谁控制) mappedBy属性—–一但为@OneToMany,@ManyToMany指定了这个属性,则表明当前的实体不能控制关联的关系。放弃控制关系之后,就不能指明@JoinColumn 或@JointTable 来修饰关联实体的属性啦(这两个实体都是外键和第三张表啊来维护关系)

例子:下面的@OneToMany在关联实体的set集合,指明了mappedBy那么1 的端放弃控制关联关系,其实这样也好。每次都更新set集合好累啊。

这个所谓的维护关联关系,主要是看底层的表中是否有外键,如果有外键的话,肯定的先有外键生成,才可以产生现在的嘛。不能乱了顺序涩。

比如 person这个一的端 现在不维护关联关系了 这么说嘛person的 底层表中没得关于地址的任何消息所以我可以随便的保存person实体。而不需要任何的条件。 但是address就不行了,因为现在表中有了person_id这个外键。,所以必须等到有了person实体的属性,才可以让我的地址产生并且维护他们之间的关系。 后面有例子的。这个只是我自己的看法。以后忘了再来看看当时的心德。

例子~~
Person 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实体
    // 指定mappedBy属性表明该Person实体不控制关联关系
    //这里的person是address中的person
    @OneToMany(targetEntity=Address.class
        , mappedBy="person")
    private Set<Address> addresses
        = new HashSet<>();
...

}

然后Address中只需要增加一个Person person这个属性。需要使用@ManyToOne @JoinColumn 设置外键

继续看例子

@Entity
@Table(name="address_inf")
public class Address
{
    @Id @Column(name="address_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int addressId;
    private String addressDetail;
    // 定义该Address实体关联的Person实体
    @ManyToOne(targetEntity=Person.class)
    // 定义名为person_id外键列,该外键列引用person_inf表的person_id列。
    @JoinColumn(name="person_id" , referencedColumnName="person_id"
        , nullable=false)
    private Person person;

...

}

不知道你是不是觉得好神奇啊,我们的底层的数据库都是一样的都是增加了外键,只是在person中增加了个set然后呢,我们就双向关联了,任意的获得对方的属性啦。开源的力量特别叼

底层数据和我们的1-N的一样的哦~我是觉得很厉害~不知道你认为呢

Session session = HibernateUtil.currentSession();
    Transaction tx = session.beginTransaction();
    // 创建一个瞬态的Person对象
    Person p = new Person();
    p.setName("Test");
    p.setAge(29);

你说现在我可以保存到数据库? Person中的set没有赋值哦。可能你忘记啦,我们表的关系是有address维护的,在address中有个外键咯。所以现在我们的person表和address没有关系。我的person当然可以持久化啦。这个 毫无疑问

// 持久化Person对象(对应于插入主表记录)
    session.save(p);
没问题

创建address啦

Address a = new Address("遵义市海龙坝");

现在可以保存?当然不可以,我们必须有外键呢,外键都没得,所以得设置关联关系。并且我们还必须是个持久化的对象,不能是瞬态的对象,上面的person是个持久态的,所以可以。为啥瞬态的不行呢?因为我们没有设置级联关系,插入的时候不会级联的操作,这样的话,外键表Id不存在,会报错的出现异常。你懂了?我也是慢慢的自己分析懂啦

// 先设置Person和Address之间的关联关系
    a.setPerson(p);
    // 再持久化Address对象(对应于插入从表记录)
    session.persist(a); //a 作为外键必须是持久化对象,不然会报错的
    tx.commit();
    HibernateUtil.closeSession();

你懂了吧?这个理解很重要哦,Hibernate的难点,持久化对象,关联关系。记得大二的时候觉得框架好特别,没得人带我飞,自己看这个Hibernate,看的那个各种晕,现在好辣,终于可以懂啦。不错,说明在进步。

有连接表的双向1-N关联

我们的关注底层数据库以怎么样的形式呈现给我们呢,因为增加第三张表来维护关系。Person和Address表中都没有外键啦!这样子不需要担心保存我们的Person 信息和Address 信息。只是在建立两者之间的关联关系的时候,放在第三张表中,要保证我们的外键有效,什么是有效呢?address.setPonsen(b) 这种情况下要保证adress 和 b都是有效的,数据库中存在的,才会插入第三张表中没得问题。

对于有连接表的1-N,让N端控制关系的话,1的端无需改变,N的端和单向有连接表的类似

    @Id 
    @Column(name="address_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int addressId;
    private String addressDetail;
    // 定义该Address实体关联的Person实体
    @ManyToOne(targetEntity=Person.class)
    // 映射连接表,指定连接表为person_address
    @JoinTable(name="person_address",
        // 指定连接表中address_id列参照当前实体对应数据表的address_id主键列
        joinColumns=@JoinColumn(name="address_id"
            , referencedColumnName="address_id", unique=true),
        // 指定连接表中person_id列参照当前实体的关联实体对应数据表的person_id主键列
        inverseJoinColumns=@JoinColumn(name="person_id"
            , referencedColumnName="person_id")
    )
    private Person person;
    ...
}

person –我们还是不维持关系吧。其实吧,这里谁维持关系都一样。都是插入第三张表中,性能没得影响。person中也可以使用@JoinTableb 表名一样就行啦。不需要指定谁维护,都一样啊!
因为是第三张表中的哈哈。

@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 ,mappedBy="person")
这两种的区别就是把,维护关系不啊~
    /*@OneToMany(targetEntity=Address.class)
    // 映射连接表,指定连接表为person_address
    @JoinTable(name="person_address",
        // 指定连接表中person_id列参照当前实体对应数据表的person_id主键列
        joinColumns=@JoinColumn(name="person_id"
            , referencedColumnName="person_id"),
        // 指定连接表中address_id列参照当前实体的关联实体对应数据表的address_id主键列
        inverseJoinColumns=@JoinColumn(name="address_id"
            , referencedColumnName="address_id", unique=true)
    )*/
    private Set<Address> addresses
        = new HashSet<>();

看上面的两种关系都可以,维护维护关系影响不大的,自己看着办就是啦。因为第三张表啦。对于性能没影响,不是更新,是插入第三张表。两者都可以维护关系

看你理解没理解,不需要维护关系是啥子意思呢?
我们来操作就知道啦

// 创建一个瞬态的Person对象
        Person p = new Person();
        p.setName("Test");
        p.setAge(21);
   // 创建一个瞬态的Address对象
        Address a = new Address("遵义市海龙坝");

现在可以a.setPerson? 答案不可以的,第三张表中有两个外键,作为主键,引用了我们的Person和Address .现在的Person和address对象都不是持久化对象在数据库中不存在。不行吧??

session.save(p);
session.save(a)
a.setPerson(p)
.commit()...
这样子就行啦

下面的也是可以的

a.setPerson(p);
// 持久化Address对象
session.persist(a);
// 持久化Person对象
session.save(p);
tx.commit();

为啥子 可以呢,persist并不是立即转换为insert语句的,save是立即的。所以这样子就好像可以啦~两个外键都存在。

双向的N-N关联

双向的N-N 两端都需要set集合,两端都需要增加对Set集合的属性的访问,这里的Set集合是关联关系的,不要和以前的不是关联关系的搞混了。双向的有太多的可能,所以只能采用连接表来操作啦。来表示实体之间的关系。
双向的N-N关联,两端都要set分别使用@ManyToMany 都要使用@JoinTable 显示的连接在一起,和刚刚一对N不同的是(1-N增加了unique约束)
需要说明的是,哪端想放弃控制关联关系,可以使用mappBy。也就不能使用@joinTable啦。其实吧上面都是说过了的。建立第三张表啦。我们两端都可以随意的控制关联关系。重点在于底层的数据库怎么实现的。理解清楚啦,你就知道现在可以持久化?没有控制关系诶!

来来例子,虽然都是差不多,但是还是来啊~

@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;
    @ManyToMany(targetEntity=Address.class)
    // 映射连接表,指定连接表的表名为person_address
    @JoinTable(name="person_address",
        // 映射连接表中名为person_id的外键列,
        // 该列参照当前实体对应表的person_id主键列
        joinColumns=@JoinColumn(name="person_id"
            , referencedColumnName="person_id"),
        // 映射连接表中名为address_id的外键列,
        // 该列参数当前实体的关联实体对应表的address_id主键列
        inverseJoinColumns=@JoinColumn(name="address_id"
            , referencedColumnName="address_id")
    )
    private Set<Address> addresses
        = new HashSet<>();

一样的继续

@Entity
@Table(name="address_inf")
public class Address
{
    // 标识属性
    @Id @Column(name="address_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int addressId;
    // 定义地址详细信息的成员变量
    private String addressDetail;
    // 定义该Address实体所有关联的Person实体
    @ManyToMany(targetEntity=Person.class)
    // 映射连接表,指定连接表的表名为person_address
    @JoinTable(name="person_address",
        // 映射连接表中名为address_id的外键列,
        // 该列参照当前实体对应表的address_id主键列
        joinColumns=@JoinColumn(name="address_id"
            , referencedColumnName="address_id"),
        // 映射连接表中名为person_id的外键列,
        // 该列参照当前实体对应表的person_id主键列
        inverseJoinColumns=@JoinColumn(name="person_id"
            , referencedColumnName="person_id")
    )
    private Set<Person> persons
        = new HashSet<>();
...
}

两面都没放弃控制权,哈哈。没必要!就是晓得原理啦,随意来啊~~
我就是任性

// 创建一个Person对象
        Person p = new Person();
    // 持久化Person对象(对应于插入主表记录)
        session.save(p);
    // 创建一个瞬态的Address对象
        Address a = new Address("遵义市海龙坝");
        // 先设置Person和Address之间的关联关系
        a.getPersons().add(p);
        // 再持久化Address对象
        session.persist(a);

这上面没错的,我先持久化了person,创建关联关系。在持久化地址。地值在持久化的时候,会先创建插入地址,在插入到关系表中的。
也可以这样写
session.save(a);
a.getPersons().add(p);
这样也是可以的,最后提交的时候就插入到了数据库中。多自己分析哈,自然的就懂了。

双向的1-1关联

基于外键的哦
直接的代码。前面的懂了,这点还不是小意思。你维护不维护实体关系呢?一方维护,一方不维护涩
总有一方要先把实体存到数据库中,然后另一方才有外键存在呢。
有mappedBy的不维护,那么低层的数据表中就不会出现啦,所以,可以自己创建并保存到数据库,不用去理会这个属性。

@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 , mappedBy="person")
    private Address address;
    ...
}

所以下面
Person p=new Person();
session.save(p)没有压力

继续地址,这里没有使用连接表哦。还是有先后顺序的。必须先有person在有address;

@Entity
@Table(name="address_inf")
public class Address
{
    // 标识属性
    @Id @Column(name="address_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int addressId;
    // 定义地址详细信息的成员变量
    private String addressDetail;
    // 定义该Address实体关联的Person实体
    @OneToOne(targetEntity=Person.class)
    // 用于映射person_id外键列,参照person_inf表的person_id列
    // 指定了unique=true表明是1-1关联
    @JoinColumn(name="person_id" , referencedColumnName="person_id"
        , unique=true)
    private Person person;
..
}

地层的数据库中多了个person_id 外键列
Address address=new Address();
address.addPerson(持久化的Person);
session.save(address)
没有问题涩。

下面继续。有链接表的
可以两端都有JoinTable unique熟悉必须的涩,因为是1-1必须具有唯一性另外一段不想控制关系,可以使用mappedBy涩.简单搞定

@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)
    // 映射底层连接表,表名为person_address
    @JoinTable(name="person_address",
        // 映射连接表的外键列,增加unique=true表明是1-1关联
        joinColumns=@JoinColumn(name="person_id"
            , referencedColumnName="person_id" , unique=true),
        // 映射连接表的外键列,增加unique=true表明是1-1关联
        inverseJoinColumns=@JoinColumn(name="address_id"
            , referencedColumnName="address_id", unique=true)
    )
    private Address address;

… 不想贴了