1 知识点
1、 这里的关系映射指的是对象之间的关系,并不是指数据库的关系,本章解决的问题是当对象之间处于下列关系之一时,数据库表该如何映射,编程上该如何对待
2、 简化问题
a) 怎样写Annotation
b) 增删改查CRUD怎么写
3、 一对一
a) 单向(主键、外键)
b) 双向(主键、外键)
c) 中间表(用得很少)
4、 一对多
a) 单向
b) 双向
5、 多对一
a) 单向
b) 双向
6、 多对多
a) 单向
b) 双向
7、 集合映射
a) List
b) Set
c) Map
8、 继承关系(不重要)
a) 单表
b) 多表
c) 一张主表,多张子表
9、 组件映射
a) @Embeddable
b) @Embedded
一对一关联
1、 一对一单向外键关联
a) Annotation:@OneToOne @JoinColumn
b) XML:<many-to-one unique>
2、 一对一双向外键关联
a) Annotation:@OneToOne(mappedBy)
b) XML:<many-to-one unique <one-to-one property-ref
c) 规律:凡是双向关联,必设mappedBy
3、 一对一单向主键关联(不重要)
a) @PrimaryKeyJoinColumn
b) XML:<one-to-one id使用foreignclass>
4、 一对一双向主键关联(不重要)
a) @PrimaryKeyJoinColumn
b) XML:<one-to-one id使用foreignclass和<one-to-one property-ref
5、 联合主键
a) @JoinColumns
b) XML:
2 Power Designer中的逆向导出数据库
在PowerDesigner中不仅可以可以通过PDM(Physical Date Model)导出建表语句,并且逆向导出。即连接已存在的DB,后读取DB的表信息,再以PDM的形式导出到Power Designer中。主要有以下步骤:
a) 配置数据源
b) 设置环境变量
c) 逆向导出数据库
i. 选择schema
ii. 选择需要导出的表
现在我们按照以上步骤尝试导出DB。首先我们数据源选择为VMware中Linux下的Oracle 10gR2。先执行关闭Linux下的防火墙、开启Oracle监听、启动数据库,这些步骤在此不演示了。
步骤一:配置数据源,PowerDesigner中新建PDM,选择Oracle 10gR2(如图1‑1);之后,菜单栏上选择“Database”,“Configure Connections”,“Connection Profiles”,选择功能选项“AddData Source File(Ctrl+N)”,如图 1‑2。
图 1‑1
图 1‑2
配置选项中的“JDBC driverjar files”需要找到Oracle对应的JDBC包(可以在Oracle安装目录下取得)。
步骤二(非必须):在一些较低版本的Power Designer中,即使上面配置中指定了“JDBC driver jar files”,但还需要将此路径配置到环境变量中的CLASSPATH中,如下图 1‑3:
图 1‑3
步骤三:PowerDesigner下菜单栏中选择“database”→“Updatemodel from database”,旧版本此选项是“Reverse Engineer Database”。当然也可以使用快捷键“Ctrl+R”,新旧版本一样。
图 1‑4
图 1‑5
图 1‑6
图 1‑7
这里我们选择HR用户,导出它的部分表,结果如下:
图 1‑8
3 一对一的单向外键关联
3.1 一对一映射
我们创建一对一关系的实体类:Husband与Wife。
Husband类,在此类中添加与Wife的关系映射:
package com.wolex.hibernate.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.SequenceGenerator;
@Entity public class Husband { private int id; private String name; private Wife wife;
@Id @SequenceGenerator(name = "Husband_seq", sequenceName = "husband_seq_db", allocationSize = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Husband_seq") // @SequenceGenerator也可以写在类名上面 public int getId() { return id; }
public String getName() { return name; }
@OneToOne() @JoinColumn(name = "wifeId") // @JoinColumn可选,如果不指定@JoinColumn的name,则默认取名为wife_id public Wife getWife() { return wife; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setWife(Wife wife) { this.wife = wife; } } |
相比较于Teacher类,Husband类多添加了两个注解:@OneToOne,@JoinColumn。其中@OneToOne表示设置下面属性为外键,而且关系是“一对一”;而@JoinColumn是对这个外键的细节补充,如name这个可选元素表示生成的外键字段的名,referencedColumnName表示引用在表中已存在的外键字段的名。
还需注意,上面的@SequenceGenerator注解也可以添加到实体类上面(推荐)。
Wife类,没啥特别的,不添加与Husband的关系:
package com.wolex.hibernate.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.SequenceGenerator;
@Entity @SequenceGenerator(name = "wife_seq", allocationSize = 1) public class Wife { private int id; private String name;
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "wife_seq") public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } } |
编写完Husband类和Wife类,记得把其映射添加到hibernate.cfg.xml中,而且我们暂时把hbm2ddl.auto项注释掉,因为下面我们使用SchemaExport类只打印其建表语句看看。
测试类,SpouseTest类:
package com.wolex.hibernate.model; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport;
public class SpouseTest { public static void main(String[] args) { new SchemaExport(new Configuration().configure()).create(true, false); // false表示只打印DDL语句而不在DB中执行。 } } |
2013-6-15 20:11:23 org.hibernate.tool.hbm2ddl.SchemaExport execute INFO: HHH000227: Running hbm2ddl schema export
drop table Husband cascade constraints
drop table Wife cascade constraints
drop sequence husband_seq_db
drop sequence wife_seq
create table Husband ( id number(10,0) not null, name varchar2(255 char), wifeId number(10,0), primary key (id) )
create table Wife ( id number(10,0) not null, name varchar2(255 char), primary key (id) )
alter table Husband add constraint FK_kruq9jfxa0jrc2od8dbh09mia foreign key (wifeId) references Wife
create sequence husband_seq_db
create sequence wife_seq 2013-6-15 20:11:23 org.hibernate.tool.hbm2ddl.SchemaExport execute INFO: HHH000230: Schema export complete |
使用Annotation方式配置的一对一关系映射,到此已经完成了。
3.2 使用XML配置
使用XML配置比Annotation的方式麻烦,但了解XML的配置方式还是很有必要的,因为遗留系统有可能是使用XML配置的,我们对此至少要做到读得懂。
我们创建一个学生ID卡类StuIdCard,与Student类一对一关系,StuIdCard类如下:
package com.wolex.hibernate.model;
public class StuIdCard { private int id; private String num;// 学号 private Student student;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getNum() { return num; }
public void setNum(String num) { this.num = num; }
public Student getStudent() { return student; }
public void setStudent(Student student) { this.student = student; } } |
此类对应的XML配置文件StuIdCard.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">
<hibernate-mapping package="com.wolex.hibernate.model"> <class name="StuIdCard"> <id name="id"> <generator class="native"></generator> </id> <property name="num" /> <many-to-one name="student" column="studentId" unique="true"></many-to-one> </class> </hibernate-mapping> |
需要解释的是这里配置的是many-to-one标签。但many-to-one不是表示“多对一”吗?在这里,如果没有unique项,则确实是表示:StuIdCard类与Student类(name的值不区分大小写,为什么?)是多对一的关系,但是如果指定了unique=“true”,则表示将在DB中添加studentId字段唯一约束,即变为了“一对一关系”。
4 一对一的双向的外键关联
这种关联指的是在两个表中都添加外键关联,其实这是一种冗余,一般都不推荐设,如下所示,在Wife类中添加Husband类的ID属性。Wife类中添加的语句如下,其他省略:
…… private Husband husband; …… @OneToOne public Husband getHusband() { return husband; } public void setHusband(Husband husband) { this.husband = husband; } …… |
执行测试类。
package com.wolex.hibernate.model; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport;
public class SpouseTest { public static void main(String[] args) { new SchemaExport(new Configuration().configure()).create(true, false); } } |
drop table Husband cascade constraints
drop table Wife cascade constraints
drop sequence husband_seq_db
drop sequence wife_seq
create table Husband ( id number(10,0) not null, name varchar2(255 char), wifeId number(10,0), primary key (id) )
create table Wife ( id number(10,0) not null, name varchar2(255 char), husband_id number(10,0), primary key (id) )
alter table Husband add constraint FK_kruq9jfxa0jrc2od8dbh09mia foreign key (wifeId) references Wife
alter table Wife add constraint FK_n86d0ervyo0jv9a18yx655329 foreign key (husband_id) references Husband
create sequence husband_seq_db
create sequence wife_seq |
可以看到,此时确实完成了一对一双向外键关联,在wife表添加了husband_id字段并且修改为外键约束。在实际开发中,这样会带来不少麻烦,不管插入还是删除记录时都不方便。所以,如果我们非要在Wife类中添加@OneToOne注解表示它是与Husband一对一关联的时候,就必须指定mappedBy值,此optional element表示的是由对方来设置外键字段,本类不干任何事。如下所示:
…… private Husband husband; …… @OneToOne(mappedBy = "wife") // 这里的“wife”表示Husband类中的wife属性,而非Wife类 public Husband getHusband() { return husband; } public void setHusband(Husband husband) { this.husband = husband; } …… |
此处需要特别注意的是,“wife”指的是在此Wife类中调用“husband.getWife”返回的结果。
执行测试类,观察结果:
package com.wolex.hibernate.model; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport;
public class SpouseTest { public static void main(String[] args) { new SchemaExport(new Configuration().configure()).create(true, false); } } |
drop table Husband cascade constraints
drop table Wife cascade constraints
drop sequence husband_seq_db
drop sequence wife_seq
create table Husband ( id number(10,0) not null, name varchar2(255 char), wifeId number(10,0), primary key (id) )
create table Wife ( id number(10,0) not null, name varchar2(255 char), primary key (id) )
alter table Husband add constraint FK_kruq9jfxa0jrc2od8dbh09mia foreign key (wifeId) references Wife
create sequence husband_seq_db
create sequence wife_seq |
可以发现,创建出来的wife表没有添加husband外键字段。
4.1 XML实现
继续使用刚才的Student类和StuIdCard类实验。XML实现一对一双向外键关联只需在Student类中添加一个StuIdCard属性,然后在Student.hbm.xml中添加以下标签:
<one-to-one name="stuIdCard" property-ref="student"></one-to-one> |
其中property-ref的作用等同于上面提到的mappedBy,其值取自StuIdCard类中的属性名student。实验结果不演示,自己动手实验。
5 联合主键关联
我们模拟商家与产品专卖的关系。其中这里的产品为Camera,每一个商家只能申请一种相机专卖,而每一种相机专卖由品牌和供应商确定(这种关系虽然不是很恰当)。即商家与相机的关系为一对一
Camera实体的主键有两个,brand和manufacturer。这里使用一个组件类来实现联合主键
Camera类:
package com.wolex.hibernate.model;
import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass;
@Entity @IdClass(CameraPK.class) public class Camera {
@Id private String brand; @Id private String manufacturer;
private String color;
public String getBrand() { return brand; }
public void setBrand(String brand) { this.brand = brand; }
public String getManufacturer() { return manufacturer; }
public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; }
public String getColor() { return color; }
public void setColor(String color) { this.color = color; } } |
CameraPK类:
package com.wolex.hibernate.model;
import java.io.Serializable;
@SuppressWarnings("serial") public class CameraPK implements Serializable {
private String brand; private String manufacturer; public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((brand == null) ? 0 : brand.hashCode()); result = prime * result + ((manufacturer == null) ? 0 : manufacturer.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CameraPK other = (CameraPK) obj; if (brand == null) { if (other.brand != null) return false; } else if (!brand.equals(other.brand)) return false; if (manufacturer == null) { if (other.manufacturer != null) return false; } else if (!manufacturer.equals(other.manufacturer)) return false; return true; } } |
Merchant类:
package com.wolex.hibernate.model; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.OneToOne;
@Entity public class Merchant { // 店主:shopkeeper @Id private int id; private String name; @OneToOne private Camera camera;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Camera getCamera() { return camera; }
public void setCamera(Camera camera) { this.camera = camera; } } |
测试:
package com.wolex.hibernate.model; import org.hibernate.Session; import org.hibernate.tutorial.util.HibernateUtil;
public class MarchantTest { public static void main(String[] args) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.getTransaction().commit(); HibernateUtil.getSessionFactory().close(); } } |
Hibernate: drop table Camera cascade constraints Hibernate: drop table Merchant cascade constraints Hibernate: create table Camera ( brand varchar2(255 char) not null, manufacturer varchar2(255 char) not null, color varchar2(255 char), primary key (brand, manufacturer) ) Hibernate: create table Merchant ( id number(10,0) not null, name varchar2(255 char), camera_brand varchar2(255 char), camera_manufacturer varchar2(255 char), primary key (id) ) Hibernate: alter table Merchant add constraint FK_le6d06q6sndg05tcmpb8af6wn foreign key (camera_brand, camera_manufacturer) references Camera |
可见,在merchant表中添加了camera表的两个字段(联合主键),但此时这两个字段添加到merchant中名字是自动生成的(camera_brand和camera_manufacturer),如果我们想要自己定义其名字,则需要添加@JoinColumns注解。修改后的Merchant类:
…… @JoinColumns(value = { @JoinColumn(name = "c_brand", referencedColumnName = "brand"), @JoinColumn(name = "c_manufacturer", referencedColumnName = "manufacturer") }) private Camera camera; …… |
测试结果(变化部分highlight表示):
Hibernate: drop table Camera cascade constraints Hibernate: drop table Merchant cascade constraints Hibernate: create table Camera ( brand varchar2(255 char) not null, manufacturer varchar2(255 char) not null, color varchar2(255 char), primary key (brand, manufacturer) ) Hibernate: create table Merchant ( id number(10,0) not null, name varchar2(255 char), c_brand varchar2(255 char), c_manufacturer varchar2(255 char), primary key (id) ) Hibernate: alter table Merchant add constraint FK_thauoohh4o50hr1f1du8vjh46 foreign key (c_brand, c_manufacturer) references Camera |
6 组件映射
组件映射表示将一个组件映射为一张表的一部分,即在面向对象中是两个类,但只映射为一张数据库表。我们现在创建有两个类:Character和Weapon,假设每一个Character只能配有一种Weapon,即他们是一对一的关系(指的是面向对象中的关系),所以在Character类中包含了对Weapon类的引用。因为Weapon只是Character一部分,所以Weapon类不需要ID属性。
使用到的注解是@Embedded,注意与联合主键中的@EmbeddedId和@Embeddable区别。
还需要注意,组件映射只用在面向对象中一对一关系。Character类:
package com.wolex.hibernate.model; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.Id;
@Entity public class Character { @Id private int id; private String name; @Embedded private Weapon weapon;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Weapon getWeapon() { return weapon; }
public void setWeapon(Weapon weapon) { this.weapon = weapon; } } |
Weapon类,此类不需要写任何注解(前提是属性名与Character类的属性名不冲突)。
package com.wolex.hibernate.model;
public class Weapon { private String weaponName; private int attackRange;
public String getWeaponName() { return weaponName; }
public void setWeaponName(String weaponName) { this.weaponName = weaponName; }
public int getAttackRange() { return attackRange; }
public void setAttackRange(int attackRange) { this.attackRange = attackRange; } } |
如果,上面的Weapon类中weaponName属性名字改为name,则需要添加@Column注解,如下:
…… @Column(name="w_name") private String name; …… |
测试:
package com.wolex.hibernate.model; import org.hibernate.Session; import org.hibernate.tutorial.util.HibernateUtil;
public class CharacterTest { public static void main(String[] args) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.getTransaction().commit(); HibernateUtil.getSessionFactory().close(); } } |
Hibernate: drop table Character cascade constraints Hibernate: create table Character ( id number(10,0) not null, name varchar2(255 char), attackRange number(10,0) not null, weaponName varchar2(255 char), primary key (id) ) |
7 多对一与一对多单向关联
7.1 多对一单向关联
一个continent可以有多个country,而一个country只属于一个continent,所以country与continent明显是多对一的关系。下面我们在多的一方(Country类)中设置关系映射,而Continent类中不设置。Continent类如下:
package com.wolex.hibernate.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id;
@Entity public class Continent { @Id @GeneratedValue private int id; private String name; private float area;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public float getArea() { return area; }
public void setArea(float area) { this.area = area; } } |
Country类如下:
package com.wolex.hibernate.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne;
@Entity public class Country { @Id @GeneratedValue private int id; private String name; private int population; @ManyToOne @JoinColumn(name="continentId") private Continent continent;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getPopulation() { return population; }
public void setPopulation(int population) { this.population = population; }
public Continent getContinent() { return continent; }
public void setContinent(Continent continent) { this.continent = continent; } } |
可见,多对一的关系映射十分容易,只需要添加@ManyToOne注解即可。如果你要手动指定列名,则需要添加@JoinColumn(name=...)注解。
打印DDL语句:
package com.wolex.hibernate.model; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport;
public class ShemaExportDemo { public static void main(String[] args) { new SchemaExport(new Configuration().configure()).create(true, false); } } |
drop table Continent cascade constraints
drop table Country cascade constraints
drop sequence hibernate_sequence
create table Continent ( id number(10,0) not null, area float not null, name varchar2(255 char), primary key (id) )
create table Country ( id number(10,0) not null, name varchar2(255 char), population number(10,0) not null, continentId number(10,0), primary key (id) )
alter table Country add constraint FK_kpolel0a3heorqp4qdb0tekmy foreign key (continentId) references Continent
create sequence hibernate_sequence |
上面为Annotation的设置,关于XML的设置大家自行实验。
7.2 一对多单向关联
在面对对象的关系设计中,我们也可以在“一方”设置一对多关系,即在Continent类中设置,如下:
package com.wolex.hibernate.model; import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany;
@Entity public class Continent { @Id @GeneratedValue private int id; private String name; private float area; @OneToMany @JoinColumn(name = "country_id") private Set<Country> countries = new HashSet<Country>();
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public float getArea() { return area; }
public void setArea(float area) { this.area = area; }
public Set<Country> getCountries() { return countries; }
public void setCountries(Set<Country> countries) { this.countries = countries; } } |
勘误:上面的country_id应该改为continent_id,因为它是在country表中生成的字段,见下面的DDL语句。
注意如果在Continent类中添加对Country类的引用,则需要将Country属性定义为集合类型,在这里使用数组、Map、List等都可以,但是使用Set是最合适的,因为Set集合规定每个记录是唯一的,这符合了country表中每行记录都唯一的约束。
还有,@JoinColumn(name=…)注解在一对多(“一方”)中是必须设的,否则Hibernate会生成三张表,即多生成一张中间表,此表字段包括两实体的主键,这明显不是我们想要的,因为中间表用于多对多的关联中。
Country类,不需要添加Continent属性。
package com.wolex.hibernate.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id;
@Entity public class Country { @Id @GeneratedValue private int id; private String name; private int population;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getPopulation() { return population; }
public void setPopulation(int population) { this.population = population; } } |
打印DDL:
package com.wolex.hibernate.model; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport;
public class ShemaExportDemo { public static void main(String[] args) { new SchemaExport(new Configuration().configure()).create(true, false); } } |
drop table Continent cascade constraints
drop table Country cascade constraints
drop sequence hibernate_sequence
create table Continent ( id number(10,0) not null, area float not null, name varchar2(255 char), primary key (id) )
create table Country ( id number(10,0) not null, name varchar2(255 char), population number(10,0) not null, country_id number(10,0), primary key (id) ) -- country_id明显应该改为continent_id alter table Country add constraint FK_nii5ybl8dn7nh52xk0p6n9jtg foreign key (country_id) references Continent
create sequence hibernate_sequence |
8 一对多与多对一的双向关联
一对多与多对一的双向关联其实一样,但我们需要考虑,把主导设置在哪方?把mappedBy设置在哪方?根据数据库中表设计的角度(3NF),应该把主导设置在多的一方(Country类)。修改后的Country类:
package com.wolex.hibernate.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne;
@Entity public class Country { @Id @GeneratedValue private int id; private String name; private int population; @ManyToOne private Continent continent;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getPopulation() { return population; }
public void setPopulation(int population) { this.population = population; }
public Continent getContinent() { return continent; }
public void setContinent(Continent continent) { this.continent = continent; } } |
而Continent类中必须设置mappedBy,如下:
package com.wolex.hibernate.model; import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany;
@Entity public class Continent { @Id @GeneratedValue private int id; private String name; private float area; @OneToMany(mappedBy = "continent") // "continent"来自Country类中的属性。 private Set<Country> countries = new HashSet<Country>();
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public float getArea() { return area; }
public void setArea(float area) { this.area = area; }
public Set<Country> getCountries() { return countries; }
public void setCountries(Set<Country> countries) { this.countries = countries; } } |
打印DDL:
package com.wolex.hibernate.model; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport;
public class ShemaExportDemo { public static void main(String[] args) { new SchemaExport(new Configuration().configure()).create(true, false); } } |
drop table Continent cascade constraints
drop table Country cascade constraints
drop sequence hibernate_sequence
create table Continent ( id number(10,0) not null, area float not null, name varchar2(255 char), primary key (id) )
create table Country ( id number(10,0) not null, name varchar2(255 char), population number(10,0) not null, continent_id number(10,0), primary key (id) )
alter table Country add constraint FK_mmhc76t5q1rodo0njifgoee60 foreign key (continent_id) references Continent
create sequence hibernate_sequence |
当然你也可以在Country类中的Continent属性上添加@JoinColumn(name=...)注解以自定义外键名。
9 多对多关联
9.1 多对多单向关联
现在创建两个类:Shopkeeper类和Clerk类,模拟店主和店员的关系,一个店主可以由多个店员,一个店员也可以为多个店主打工。即他们是多对多的关系。而数据库角度上看,多对多关联是通过添加一张中间表来实现,但object-oriented中则存在单向和双向的区别。如果是单向的话,并且关系设置在店主类中,则程序中,只有店主能找到其店员,而店员则无法取得其店主信息,反之亦然。而双向的话,各方都可以找到对方。
下面演示单向关联,并且把关系设置到Shopkeeper类中,如下:
package com.wolex.hibernate.model; import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany;
@Entity public class Shopkeeper { @Id @GeneratedValue private int id; private String name; @ManyToMany private Set<Clerk> clerks = new HashSet<Clerk>();
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Set<Clerk> getClerks() { return clerks; }
public void setClerks(Set<Clerk> clerks) { this.clerks = clerks; } } |
Clerk类,不需要额外设置。
package com.wolex.hibernate.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id;
@Entity public class Clerk { @Id @GeneratedValue private int id; private String name; private float salary;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public float getSalary() { return salary; }
public void setSalary(float salary) { this.salary = salary; } } |
打印DDL:
package com.wolex.hibernate.model; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport;
public class ShemaExportDemo { public static void main(String[] args) { new SchemaExport(new Configuration().configure()).create(true, false); } } |
drop table Clerk cascade constraints
drop table Shopkeeper cascade constraints
drop table Shopkeeper_Clerk cascade constraints
drop sequence hibernate_sequence
create table Clerk ( id number(10,0) not null, name varchar2(255 char), salary float not null, primary key (id) )
create table Shopkeeper ( id number(10,0) not null, name varchar2(255 char), primary key (id) )
create table Shopkeeper_Clerk ( Shopkeeper_id number(10,0) not null, clerks_id number(10,0) not null, primary key (Shopkeeper_id, clerks_id) )
alter table Shopkeeper_Clerk add constraint FK_cytxsmy30xg2ih82rhd1tu30i foreign key (clerks_id) references Clerk
alter table Shopkeeper_Clerk add constraint FK_q6i3x7paq77cnk4curxhire9i foreign key (Shopkeeper_id) references Shopkeeper
create sequence hibernate_sequence |
上面由Hibernate生成的DDL中,在clerk表中对于salary字段采取了float数据类型,在Oracle数据库设计表中,我们可以设置为number(10,2)。至于哪种好以及他们的区别,待探究??
如果不指定中间表的相关名字,Hibernate会自动为其起名字。当然,我们可以自定义,但比较麻烦,如下,修改后的Shopkeeper类:
…… @ManyToMany @JoinTable(name = "s_t", joinColumns = @JoinColumn(name = "s_id"), inverseJoinColumns = @JoinColumn(name = "c_id") ) private Set<Clerk> clerks = new HashSet<Clerk>(); …… |
可见其繁琐程度了吧。joinColumns中写的是本类(Shopkeeper)对应的表的主键映射到中间表的外键名,而inverseJoinColumn写的是对方类(Clerk)的外键名。那为什么还要多写@JoinColumn这注解呢,直接joinColumn= “s_id”和inverseJoinColumn = “c_id”不是更简洁易懂吗?
因为如果多对多关联中,彼此两个类ID都不止一个呢?此时中间表则不止两个字段了。而@JoinColumn注解则可以派上用场了,而且此时需要同时添加referencedColumnName选项。
我们现在来折腾一下,假设Shopkeeper类和Clerk类都把id和name作为ID,而且自定义中间表的信息。
修改后的Shopkeeper类:
package com.wolex.hibernate.model; import java.io.Serializable; import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany;
@SuppressWarnings("serial") @Entity public class Shopkeeper implements Serializable { @Id @GeneratedValue private int id; @Id private String name; @ManyToMany @JoinTable(name = "s_t", joinColumns = { @JoinColumn(name = "s_id", referencedColumnName = "id"), @JoinColumn(name = "s_name", referencedColumnName = "name") }, inverseJoinColumns = { @JoinColumn(name = "c_id", referencedColumnName = "id"), @JoinColumn(name = "c_name", referencedColumnName = "name") } ) private Set<Clerk> clerks = new HashSet<Clerk>();
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Set<Clerk> getClerks() { return clerks; }
public void setClerks(Set<Clerk> clerks) { this.clerks = clerks; }
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((clerks == null) ? 0 : clerks.hashCode()); result = prime * result + id; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; }
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Shopkeeper other = (Shopkeeper) obj; if (clerks == null) { if (other.clerks != null) return false; } else if (!clerks.equals(other.clerks)) return false; if (id != other.id) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } |
注意,由于我们使用联合主键,而且直接在一个类中设置(即不添加组件类,如“ShopkeeperPK类”),所以Shopkeeper类必须实现Serializable接口,而且建议覆写hashCode()和equals()。Clerk类类似,如下:
package com.wolex.hibernate.model; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id;
@SuppressWarnings("serial") @Entity public class Clerk implements Serializable { @Id @GeneratedValue private int id; @Id private String name; private float salary;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public float getSalary() { return salary; }
public void setSalary(float salary) { this.salary = salary; }
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + Float.floatToIntBits(salary); return result; }
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Clerk other = (Clerk) obj; if (id != other.id) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (Float.floatToIntBits(salary) != Float.floatToIntBits(other.salary)) return false; return true; } } |
打印结果:
package com.wolex.hibernate.model; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport;
public class ShemaExportDemo { public static void main(String[] args) { new SchemaExport(new Configuration().configure()).create(true, false); } } |
drop table Clerk cascade constraints
drop table Shopkeeper cascade constraints
drop table s_t cascade constraints
drop sequence hibernate_sequence
create table Clerk ( name varchar2(255 char) not null, id number(10,0) not null, salary float not null, primary key (name, id) )
create table Shopkeeper ( name varchar2(255 char) not null, id number(10,0) not null, primary key (name, id) )
create table s_t ( s_name varchar2(255 char) not null, s_id number(10,0) not null, c_name varchar2(255 char) not null, c_id number(10,0) not null, primary key (s_name, s_id, c_name, c_id) )
alter table s_t add constraint FK_3d1eim15lm38rthuimofwiv8f foreign key (c_name, c_id) references Clerk
alter table s_t add constraint FK_ehx7ett683n2ixw8k7b1qwxwp foreign key (s_name, s_id) references Shopkeeper
create sequence hibernate_sequence |
上面的例子不要求记住,只需要看懂即可,如果在实际开发中需要用到,做到会通过查API文档实现目的即可。
同样,XML方式的配置这里不再演示,请自行查文档实验。
9.2 多对多双向关联
在上面的基础上,只需要在Clerk类中添加关联,就可达到多对多双向关联,即既可以通过Shopkeeper类找到Clerk类,也可以通过Clerk类找到Shopkeeper类。Clerk类中需添加的部分:
…… @ManyToMany(mappedBy = "clerks") private Set<Shopkeeper> shopkeepers = new HashSet<Shopkeeper>();
public Set<Shopkeeper> getShopkeepers() { return shopkeepers; } public void setShopkeepers(Set<Shopkeeper> shopkeepers) { this.shopkeepers = shopkeepers; } …… |
输出结果和上面一样,这里不再重复。