程序员人生 网站导航

Hibernate之实体关系映射

栏目:php教程时间:2015-04-09 08:29:01

延迟加载与即时加载

例如Person类和Email类是1对多关系,如果设为即时加载,当加载Person时,会自动加载Email,如果设置为延迟加载,当第1次调用person.getEmails()时才会履行SQL语句加载Email
注解配置时,@OnetToMany(Fetch = FetchType.EAGER)为即时加载,Fetch = FetchType.LAZY为延迟加载
延迟加载和即时加载的策略适用于所有1对多、多对1、多对多等所有的实体关系

1般来讲,延迟加载要比即时加载节省资源,但是如果处理不当,延迟加载容易抛出LazyInitializationException异常,解决当方法有两种,1种是在Session关闭之前调用1下,person.getEmails()方法,逼迫Hibernate加载数据,这是最经常使用的方式,还有1种是延迟Session的范围,使Session关闭前完成所有的业务逻辑,在web程序中有些工具也能延长Session的范围,例如Spring的OpenSessionInViewFilter,能把Session扩大到所有的web资源,包括Servlet层、JSP层、DAO层、Service层


单边1对多

1方有集合属性,包括多个多方,而多方没有1方的援用
import javax.persistence.*; //“1"方 @Entity @Table(name="tb_person") public class Person{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; @OneToMany(fetch = FetchType.EAGER,targetEntity = Email.class,cascade={ CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH }) @JoinColumns(value= {@JoinColumn(name="person_id",referencedColumnName="id")}) @OrderBy(value = “email desc") private List<Email> emails = new ArrayList<Email>(); //在email对应的表中产生1个person_id字段,援用person标的id // referencedColumnName指的是实体类的关联列,默许为主键,可省略 //setter、getter略 } //“多"方 @Entity @Table(name="tb_email") public class Email{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String email; //setter、getter略 }


<hibernate-mapping package="com.clf.hibernate.bean"> <class name="Person" table="tb_person"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> <bag name="emails" cascade="all" lazy="false" where="email like %@%" order-by="email"> <key column="email_id"></key> <one-to-many class="com.clf.hibernate.bean.Email" /> </bag> </class> </ hibernate-mapping > <hibernate-mapping package="com.clf.hibernate.bean"> <class name="Email" table="tb_eamil"> <id name="id" column="id"> <generator class="native"> </id> <property name="email"/> </class> </ hibernate-mapping >



如果集合属性使用的是Set而非List,则XML配置时需要使用<set>标签而不是<bag>标签,而且不能使用order-by属性
JPA(Java Persistence API)要求实体类必须为POJO,而不能为String等基本类型,换句话说,电子邮件必须封装为Email对象,即便它只有1个String属性
而XML配置允许直接使用String作为实体类,而不需要封装成1个Email对象
配置改动以下
<bag name="emails" cascade="all" lazy="false" where="email like %@%" order-by="email"> <key column="email_id"></key> <element type="String" column="email" /> </bag>


单边多对1

//“1"方 @Entity @Table(name = "tb_table") public class Type{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(unique = true) private String name; //setter、getter方法略 } //“多"方 @Entity @Table(name = <span style="font-family: Arial, Helvetica, sans-serif;">"</span><span style="font-family: Arial, Helvetica, sans-serif;">tb_article")</span> public class Article{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @ManyToOne(cascade = {CascadeType.PERSIST},fetch = FetchType.EAGER) @JoinColumn(name = "type_id") Private Type type; //在article表总产生1个type_id字段,援用type表中的id字段 @Column(columnDefinition = "text") private String content; private String name; //setter、getter方法略 }


<class name="Type" table="tb_type"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> </class> <class name="Article" table="tb_article"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> <property name="content"/> <many-to-one name = "type" column = "type_id" cascade = "persist" lazy="false" not-found="ignore"> </ many-to-one > </class>


双边多对1、1对多

//“1"方 @Entity @Table(name = "tb_class") public class Clazz{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; //使用反向配置(@ManyToOne不存在该属性),双边关系中,控制权1般交给多方,定义在被具有方,指向具有方,具有方能够级联地保护被具有方的关系。因此这里没有配置数据库的外键,而只配置了1个mappedBy属性,告知Hibernate,配置信息要到Student类的“clazz"属性中找 @OneToMany(mappedBy = "clazz") private List<Student> students; //setter、getter方法略 } //“多"方 @Entity @Table(name = "tb_student") public class Student{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @ManyToOne(cascade = {CascadeType.PERSIST},fetch = FetchType.EAGER) @JoinColumn(name = "class_id") Private Clazz clazz; //在student表总产生1个class_id字段,援用class表中的id字段 private String sex; private String name; //setter、getter方法略 }


<class name="Clazz" table="tb_class"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> <bag name="students" inverse="true" cascade="all"> <key column="class_id"/> <one-to-many class="com.clf.hibernate.bean.Student"/> </bag> </class> <class name="Student" table="tb_student"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> <property name="sex"/> <many-to-one name = "clazz" column = "class_id" cascade = "all" > </ many-to-one > </class>


单边1对1

有两种策略可以实现1对1的关联映照
主键关联:即让两个对象具有相同的主键值,以表明它们之间的逐一对应的关系;数据库表不会有额外的字段来保护它们之间的关系,仅通过表的主键来关联。
唯1外键关联:外键关联,本来是用于多对1的配置,但是如果加上唯1的限制以后,也能够用来表示1对1关联关系。
唯1外键关联策略
@Entity public class IdCard { private int id; private String cardNo; @Id @GeneratedValue public int getId() {return id;} //setter、getter略 } @Entity public class Person { private IdCard idCard;//援用IdCard对象 private String name; @Id @GeneratedValue private int id; @OneToOne @JoinColumn(name="idCard") private IdCard idCard; //援用IdCard对象 private String name; //setter、getter略 }


<class name="com.clf.hibernate.IdCard" table="t_idcard"> <id name="id" column="id"> <generator class="native"/> </id> <property name="cardNo"/> </class> <class name="com.clf.hibernate.Person" table="t_person"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name"/> <!--在多的1端(当前Person1端),加入1个外键(当前为idCard)指向1的1端(当前IdCard),但多对1关联映照字段是可以重复的,所以需要加入1个唯1条件unique="true",这样就能够此字段唯1了。--> <many-to-one name="idCard" unique="true"/> </class>


主键关联策略
IdCart正常注解

@Entity public class Person { private IdCard idCard;//援用IdCard对象 private String name; @Id @GeneratedValue private int id; @OneToOne @PrimaryKeyJoinColumn //注解主键关联映照 private IdCard idCard; //援用IdCard对象 private String name; //setter、getter略 }



<class name="com.clf.hibernate.IdCard" table="t_idcard"> <id name="id" column="id"> <generator class="native"/> </id> <property name="cardNo"/> </class> <class name="com.clf.hibernate.Person" table="t_person"> <id name="id" column="id"> <!--主键不是自己生成的,而是作为1个外键,所以使用foreign生成策略。foreign:使用另外1个相干联的对象的标识符,通常和<one-to-one>联合起来使用。再使用元素<param>的属性值指定相干联对象(这里Person相干联的对象为idCard,则标识符为idCard的id)为了能够在加载person数据同时加载IdCard数据,所以需要使用1个标签<one-to-one>来设置这个功能。 --> <generator class="foreign"> <!-- 元素<param>属性name的值是固定为property --> <param name="property">idCard</param> </generator> </id> <property name="name"/> <!--表示如何加载它的援用对象(这里援用对象就指idCard这里的name值是idCard),同时也说是1对1的关系。 默许方式是根据主键加载(把person中的主键取出再到IdCard中来取相干IdCard数据。) 我们也说过此主键也作为1个外键援用 了IdCard,所以需要加1个数据库限制(外键束缚)constrained="true" --> <one-to-one name="idCard" constrained="true"/> </class>


联合主键关联(Annotation方式)
实现上联合主键的原理同唯1外键关联-单向1样,只是使用的是@JoinColumns,而不是@JoinColumn,实体类注解以下:
@OneToOne @JoinColumns( { @JoinColumn(name="personId", referencedColumnName="id"), @JoinColumn(name="personName", referencedColumnName="name") } ) private Person person;


   注意:@JoinColumns注解联合主键1对1联系,然后再使用@JoinColumn来注解当前表中的外键字段名,并指定关联哪一个字段,使用referencedColumnName指定哪一个字段的名称


双边1对1

凡是双向关联,必设mappedBy
唯1外键关联策略
@Entity public class IdCard { private String cardNo; @Id @GeneratedValue private int id; @OneToOne(mappedBy="idCard") private Person person; //setter、getter略 } @Entity public class Person { private IdCard idCard;//援用IdCard对象 private String name; @Id @GeneratedValue private int id; @OneToOne @JoinColumn(name="idCard") //为idCard对象指定外键 private IdCard idCard; //援用IdCard对象 private String name; //setter、getter略 }



<class name="com.clf.hibernate.IdCard" table="t_idcard"> <id name="id" column="id"> <generator class="native"/> </id> <property name="cardNo"/> <one-to-one name="person" property-ref="idCard"/> </class> <!-- 1对1 唯1外键 关联映照 双向 需要在另外一端(当前IdCard),添加<one-to-one>标签,唆使hibernate如何加载其关联对象(或援用对象),默许根据主键加载(加载person),外键关联映照中,由于两个实体采取的是person的外键来保护的关系,所以不能指定主键加载person,而要根据person的外键加载,所以采取以下映照方式: <one-to-one>标签:告知hibernate如何加载其关联对象 property-ref属性:是根据哪一个字段进行比较加载数据 --> <class name="com.clf.hibernate.Person" table="t_person"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name"/> <!--在多的1端(当前Person1端),加入1个外键(当前为idCard)指向1的1端(当前IdCard),但多对1关联映照字段是可以重复的,所以需要加入1个唯1条件unique="true",这样就能够此字段唯1了。--> <many-to-one name="idCard" unique="true"/> </class>


主键关联策略
@Entity public class IdCard { private String cardNo; @Id @GeneratedValue private int id; @OneToOne(mappedBy="idCard") private Person person; //setter、getter略 } @Entity public class Person { private IdCard idCard;//援用IdCard对象 private String name; @Id @GeneratedValue private int id; @OneToOne @PrimaryKeyJoinColumn //注解主键关联映照 private IdCard idCard; //援用IdCard对象 private String name; //setter、getter略 }

<class name="com.clf.hibernate.IdCard" table="t_idcard"> <id name="id" column="id"> <generator class="native"/> </id> <property name="cardNo"/> <one-to-one name="person"/> </class> <class name="com.clf.hibernate.Person" table="t_person"> <id name="id" column="id"> <generator class="foreign"> <param name="property">idCard</param> </generator> </id> <property name="name"/> <one-to-one name="idCard" constrained="true"/> </class>


单边多对多

@Entity public class User { private int id; private String name; private Set<User> roles = new HashSet<User>();// Role对象的集合 @Id @GeneratedValue public int getId() {return id;} @ManyToMany @JoinTable(name="u_r",//使用@JoinTable标签的name属性注解第3方表名称 joinColumns={@JoinColumn(name="userId")}, //使用joinColumns属性来注解当前实体类在第3方表中的字段名称并指向该对象 inverseJoinColumns={@JoinColumn(name="roleId")} //使用inverseJoinColumns属性来注解当前实体类持有援用对象在第3方表中的字段名称并指向被援用对象表 ) public Set<User> getRoles() {return roles; }



Role实体正常注解

Role映照文件:
<class name="com.clf.hibernate.Role" table="t_role"> <id name="id"> <generator class="native"/> </id> <property name="name" column="name"/> </class> User映照文件: <class name="com.clf.hibernate.User" table="t_user"> <id name="id"column="id"> <generator class="native"/> </id> <property name="name"/> <!--使用<set>标签映照集合(set),标签中的name值为对象属性名(集合roles),而使用table属性是用于生成第3方表名称,例:table="t_user_role",但是第3方面中的字段是自动加入的,作为外键分别指向其它表。 所以表<key>标签设置,例:<key column="userid"/>,意思是:在第3方表(t_user_role)中加入1个外键并且指向当前的映照实体类所对应的表(t_user).使用<many-to-many>来指定此映照集合所对象的类(实例类),并且使用column属性加入1个外键指向Role实体类所对应的表(t_role) --> <set name="roles" table="t_user_role"> <key column="userid"/> <many-to-many class="com.clf.hibernate.Role" column="roleid"/> </set> </class>


双边多对多

User实体类的注解与单边多对多1样

Role实体类注解也非常的简单:使用@ManyToMany注解,并使用mappedBy属性指定援用对象持有自己的的属性名
@Entity public class Role { private int id; private String name; private Set<User> users = new HashSet<User>(); @Id @GeneratedValue public int getId() {return id; } @ManyToMany(mappedBy="roles") public Set<User> getUsers() {return users;} public voidsetUsers(Set<User> users) {this.users = users; }


<class name="com.clf.hibernate.User" table="t_user"> <id name="id"column="id"> <generator class="native"/> </id> <property name="name"/> <set name="roles" table="t_user_role"> <key column="userid"/> <many-to-many class="com.clf.hibernate.Role" column="roleid"/> </set> </class> <class name="com.clf.hibernate.Role" table="t_role"> <id name="id"> <generator class="native"/> </id> <property name="name" column="name"/> <set name="users" table="t_user_role"order-by="userid"> <key column="roleid"/> <many-to-many class="com.clf.hibernate.User" column="userid"/> </set> </class>


component(组件)关联映照

例如有以下两个实体类:用户与员工,二者存在很多相同的字段,但是二者有不可以是同1个类,这样在实体类中每次都要输入很多信息,现在把其中共有部份抽取出来成为1个类,然后在用户、员工对象中援用就能够
值对象没有标识,而实体对象具有标识,值对象属于某1个实体,使用它重复使用率提升,而且更清析。
以上关系的映照称为component(组件)关联映照
在hibernate中,component是某个实体的逻辑组成部份,它与实体的根本区分是没有oid,component可以成为是值对象(DDD)。
采取component映照的好处:它实现了对象模型的细粒度划分,层次会更加分明,复用率会更高。
<class name="com.clf.hibernate.User" table="t_user"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name" column="name"/> <component name="contact"> <property name="email"/> <property name="address"/> <property name="zipCode"/> <property name="contactTel"/> </component> </class>


@Entity public class User { private int id; private String name; private Contact contact;//值对象的援用 @Id @GeneratedValue public int getId() { return id;} @Embedded//用于注解组件映照,表示嵌入对象的映照 public Contact getContact() {return contact;} public void setContact(Contactcontact) {this.contact = contact;}


Contact类是值对象,不是实体对象,是属于实体类的某1部份,因此没有映照文件

Contact值对象:
public class Contact { private String email; private String address; private String zipCode; private String contactTel; //setter、getter略 }


继承关联映照

继承映照:就是把类的继承关系映照到数据库里(首先正确的存储,再正确的加载数据)
继承关联映照的分类:
单表继承:每棵类继承树使用1个表(table per class hierarchy)
具体表继承:每一个子类1个表(table per subclass)
类表继承:每一个具体类1个表(table per concrete class)(有1些限制)

下面用1个实例来讲明3种继承关系的用法和区分:

动物Animal有3个基本属性,然后有1个Pig继承了它并扩大了1个属性,还有1个Brid也继承了并且扩大了1个属性


 
Animal实体类:
public class Animal { private int id; private String name; private boolean sex; public int getId() {return id; } public void setId(int id) { this.id = id;} public String getName() {return name;} public void setName(Stringname) {this.name = name;} public boolean isSex() {return sex;} public void setSex(boolean sex) {this.sex = sex;} }


Pig实体类:
public class Pig extends Animal { private int weight; public int getWeight() {return weight;} public void setWeight(int weight) {this.weight = weight;} }


Bird实体类:
public class Bird extends Animal { private int height; public int getHeight() {return height;} public void setHeight(int height) {this.height = height;} }


单表继承
把所有的属性都要存储表中,目前最少需要5个字段,另外需要加入1个标识字段(表示哪一个具体的子类)
 
其中:
           ①、id:表主键
           ②、name:动物的姓名,所有的动物都有
           ③、sex:动物的性别,所有的动物都有
           ④、weight:只有猪才有
           ⑤、height:只有鸟才有
           ⑥、type:表示动物的类型;P表示猪;B表示鸟
<class name="Animal" table="t_animal" lazy="false"> <id name="id"> <generator class="native"/> </id> <discriminator column="type" type="string"/> <property name="name"/> <property name="sex"/> <subclass name="Pig" discriminator-value="P"> <property name="weight"/> </subclass> <subclass name="Bird" discriminator-value="B"> <property name="height"/> </subclass> </class>


父类用普通的<class>标签定义
在父类中定义1个discriminator,即指定这个辨别的字段的名称和类型,如:<discriminator column="XXX" type="string"/>
子类使用<subclass>标签定义,在定义subclass的时候,需要注意以下几点:
Subclass标签的name属性是子类的全路径名
在Subclass标签中,用discriminator-value属性来标明本子类的discriminator字段(用来辨别不同类的字段)  的值Subclass标签,既可以被class标签所包括(这类包括关系正是表明了类之间的继承关系),也能够与class标  签平行。 当subclass标签的定义与class标签平行的时候,需要在subclass标签中,添加extends属性,里面的值  是父类的全路径名称。子类的其它属性,像普通类1样,定义在subclass标签的内部。

annotation注解
父类中注解以下:
使用@Inheritance注解为继承映照,再使用strategy属性来指定继承映照的方式
strategy有3个值:InheritanceType.SINGLE_TABLE           单表继承
                  InheritanceType.TABLE_PER_CLASS       类表继承
                  InheritanceType.JOINED                具体表继承
再使用@DiscriminatorColumn注意标识字段的字段名,及字段类型
在类中使用@DiscriminatorValue来注解标识字段的值
@Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn( name="discriminator", discriminatorType=DiscriminatorType.STRING) @DiscriminatorValue("type") public class Animal {……}


继承类中注解以下:

    只需要使用@DiscriminatorValue来注解标识字段的值


具体表继承
每一个类映照成1个表(table per subclass),所以Animal也映照成1张表(t_animal),表中字段为实体类属性
而pig子类也需要映照成1张表(t_pid),但为了与父类联系需要加入1个外键(pidid)指向父类映照成的表(t_animal),字段为子类的扩大属性。Bird子类一样也映照成1张表(t_bird),也加入1个外键(birdid)指向父类映照成的表(t_animal),字段为子类的扩大属性。
<class name="com.clf.hibernate.Animal" table="t_animal"> <id name="id"column="id"><!-- 映照主键 --> <generator class="native"/> </id> <property name="name"/><!-- 映照普通属性 --> <property name="sex"/> <!--joined-subclass标签:每一个类映照成1个表 --> <joined-subclass name="com.clf.hibernate.Pig" table="t_pig"> <!-- <key>标签:会在相应的表(当前映照的表)里,加入1个外键 , 参照指向当前类的父类(当前Class标签对象的表(t_animal))--> <key column="pigid"/> <property name="weight"/> </joined-subclass> <joined-subclass name="com.clf.hibernate.Bird" table="t_bird"> <key column="birdid"/> <property name="height"/> </joined-subclass> </class>


这类策略是使用joined-subclass标签来定义子类的。父类、子类,每一个类都对应1张数据库表。
在父类对应的数据库表中,实际上会存储所有的记录,包括父类和子类的记录;在子类对应的数据库表中,这个表只定义了子类中所独有的属性映照的字段。子类与父类,通过相同的主键值来关联。实现这类策略的时候,有以下步骤:
父类用普通的<class>标签定义便可
父类不再需要定义discriminator字段
子类用<joined-subclass>标签定义,在定义joined-subclass的时候,需要注意以下几点:
Joined-subclass标签的name属性是子类的全路径名
Joined-subclass标签需要包括1个key标签,这个标签指定了子类和父类之间是通过哪一个字段来关联的。如:<key column="PARENT_KEY_ID"/>,这里的column,实际上就是父类的主键对应的映照字段名称。Joined-subclass标签,既可以被class标签所包括(这类包括关系正是表明了类之间的继承关系),也能够与class标签平行。 当Joined-subclass标签的定义与class标签平行的时候,需要在Joined-subclass标签中,添加extends属性,里面的值是父类的全路径名称。子类的其它属性,像普通类1样,定义在joined-subclass标签的内部。

annotation注解
由于子类生成的表需要援用父类生成的表,所以只需要在父类设置具体表继承映照就能够了,其它子类只需要使用@Entity注解就能够了
@Entity @Inheritance(strategy=InheritanceType.JOINED) public class Animal {……}

具体表继承效力没有单表继承高,但是单表继承会出现过剩的庸于字段,具体表层次分明


类表继承
每一个具体类(Pig、Brid)映照成1个表(table per concrete class)(有1些限制)
<class name="com.clf.hibernate.Animal" table="t_animal"> <id name="id"column="id"><!-- 映照主键 --> <generator class="assigned"/><!-- 每一个具体类映照1个表主键生成策略不可以使用native --> </id> <property name="name"/><!-- 映照普通属性 --> <property name="sex"/> <!--使用<union-subclass>标签来映照"每一个具体类映照成1张表"的映照关系,实现上上面的表t_animal虽然映照到数据库中,但它没有任何作用。 --> <union-subclass name="com.clf.hibernate.Pig" table="t_pig"> <property name="weight"/> </union-subclass> <union-subclass name="com.clf.hibernate.Bird" table="t_bird"> <property name="height"/> </union-subclass> </class>


如果不想t_animal存在(由于它没有实际的作用),可以设置<class>标签中的abstract="true"(抽象表),这样在导出至数据库时,就不会生成t_animal表了
这类策略是使用union-subclass标签来定义子类的。每一个子类对应1张表,而且这个表的信息是完备的,即包括了所有从父类继承下来的属性映照的字段(这就是它跟joined-subclass的不同的地方,joined-subclass定义的子类的表,只包括子类特有属性映照的字段)。实现这类策略的时候,有以下步骤:
父类用普通<class>标签定义便可
子类用<union-subclass>标签定义,在定义union-subclass的时候,需要注意以下几点:
Union-subclass标签不再需要包括key标签(与joined-subclass不同)
Union-subclass标签,既可以被class标签所包括(这类包括关系正是表明了类之间的继承关系),也能够与class标签平行。 当Union-subclass标签的定义与class标签平行的时候,需要在Union-subclass标签中,添加extends属性,里面的值是父类的全路径名称。子类的其它属性,像普通类1样,定义在Union-subclass标签的内部。这个时候,虽然在union-subclass里面定义的只有子类的属性,但是由于它继承了父类,所以,不需要定义其它的属性,在映照到数据库表的时候,仍然包括了父类的所有属性的映照字段。
注意:在保存对象的时候id是不能重复的(不能使用自增生成主键)

annotation注解
只需要对父类进行注解就能够了,由于子类表的ID是不可以重复,所以1般的主键生成策略已不适应了,只有表主键生成策略。
首先使用@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)来注解继承映照,并且使用具体表继承方式,使用@TableGenerator来申明1个表主键生成策略再在主键上@GeneratedValue(generator="t_gen", strategy=GenerationType.TABLE)来注解生成策略为表生成策略,并且指定表生成策略的名称继承类只需要使用@Entity进行注解就能够了
@Entity @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) @TableGenerator( name="t_gen", table="t_gen_table", pkColumnName="t_pk", valueColumnName="t_value", pkColumnValue="person_pk", initialValue=1, allocationSize=1 )



3种继承关联映照的区分:
第1种:它把所有的数据都存入1个表中,优点:效力好(操作的就是1个表);缺点:存在庸于字段,如果将庸于字段设置为非空,则就没法存入数据;
第2种:层次分明,缺点:效力不好(表间存在关联表)
第3种:主键字段不可以设置为自增主键生成策略。
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐