一、两张类似的表与两个类似的实体类
现在我有一个betspider数据库,里面放着我自己的爬虫爬来的足球赔率,以bs_odds_1x2(胜负平赔率)、bs_odds_handicap(让球赔率)这两张表作为例子,他们在数据库中的表结构是这样的:
首先不考虑实体类的继承,这两张表对应着两个独立的实体类:(请暂时忽略下面实体类属性的数据类型与mysql的表字段类型的不完全匹配 =。=#)
@Entity @Table(name = "bs_odds_1x2") public class Odds1x2 implements Serializable { private static final long serialVersionUID = 1L; @Column(name = "id") @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @Column(name = "match_id") private long matchId; @Column(name = "update_time") private long updateTime; @Column(name = "bookmaker_id") private long bookmakerId; @Column(name = "active") private boolean active; @Column(name = "home_odds") private double homeOdds; @Column(name = "draw_odds") private double drawOdds; @Column(name = "away_odds") private double awayOdds; }
@Entity @Table(name = "bs_odds_handicap") public class OddsHandicap implements Serializable { private static final long serialVersionUID = 1L; @Column(name = "id") @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @Column(name = "match_id") private long matchId; @Column(name = "update_time") private long updateTime; @Column(name = "bookmaker_id") private long bookmakerId; @Column(name = "active") private boolean active; @Column(name = "granter") private char granter; @Column(name = "granter_number") private int granterNumber; @Column(name = "home_odds") private double homeOdds; @Column(name = "away_odds") private double awayOdds; }
但实际上我的betspider库里有好多odss_xxx表,我可不想为每种赔率都写一大段的实体类定义。
二、提取父类
从上面的描述我们可以看到,Odds1x2与OddsHandicap这两种赔率其实很类似,他们共同的属性包括id、关联的比赛id、更新时间、博彩数据供应商id、有效性字段。
那么从面向对象的角度来看的话,我们可以把他们的共同属性提取出来,作为一个父类叫做Odds。
public class Odds { protected long id; protected long matchId; protected long updateTime; protected long bookmakerId; protecetd boolean active; }
但是如何将类之间的继承关系映射到数据库的表中呢,而且先说明的是,我希望映射到数据库后,orm框架为我们自动生成的数据库表依然如上面描述的那样,只有bs_odds_1x2与bs_odds_handicap表,而不存在odds表。
那么,来看一下jpa规范中提供的实体类继承策略。
三、javax.persistence.Inheritance
jpa的@Inheritance注解规定了三种策略:JOINED、SINGLE_TABLE、TABLE_PER_CLASS
3.1 JOINED
使用这种映射策略,继承链中的每个实体类都各自映射到数据库中独立的表,子类表只拥有自身的属性,从父类继承的属性都放在父类表中,同时在父类中定义的主键、将会被所有子类共享作为主键+外键。下面是例子:
@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Odds { @Column(name = "id") @Id @GeneratedValue(strategy = GenerationType.AUTO) protected long id; protected long matchId; protected long updateTime; protected long bookmakerId; protected boolean active; }
@Entity @Table(name = "bs_odds_1x2") public class Odds1x2 extends Odds { @Column(name = "home_odds") private double homeOdds; @Column(name = "draw_odds") private double drawOdds; @Column(name = "away_odds") private double awayOdds; }
@Entity @Table(name = "bs_odds_handicap") public class OddsHandicap extends Odds { @Column(name = "granter") private char granter; @Column(name = "granter_number") private int granterNumber; @Column(name = "home_odds") private double homeOdds; @Column(name = "away_odds") private double awayOdds; }
将hibernate的hibernate.hbm2ddl.auto属性配置为create-drop。运行后看控制台的输出:
Hibernate: drop table if exists Odds Hibernate: drop table if exists bs_odds_1x2 Hibernate: drop table if exists bs_odds_handicap Hibernate: create table Odds ( id bigint not null auto_increment, active bit not null, bookmakerId bigint not null, matchId bigint not null, updateTime bigint not null, primary key (id) ) Hibernate: create table bs_odds_1x2 ( away_odds double precision, draw_odds double precision, home_odds double precision, id bigint not null, primary key (id) ) Hibernate: create table bs_odds_handicap ( away_odds double precision, granter char(1), granter_number integer, home_odds double precision, id bigint not null, primary key (id) ) Hibernate: alter table bs_odds_1x2 add constraint FK_cu80slpty9rvkqutehdh9j8yi foreign key (id) references Odds (id) Hibernate: alter table bs_odds_handicap add constraint FK_h5jiior7m529wka437y4vlki5 foreign key (id) references Odds (id)
可以看到,hibernate为我们创建了三个数据库:Odds(因为我没有给Odds实体类配置表名,所以默认表名为类名)、bs_odds_1x2、bs_odds_handicap。同时用Odds的id作为外键将两个子类表与父类表关联起来。
3.2 SINGLE_TABLE
使用这种映射策略的话,实体类的继承链中,所有的类都会被叠加到一起映射到数据库的唯一的一张表中,该表名以父类对应的表名为准。另外,这张表中,会额外增加一个字段DTYPE,用来判断一行数据是属于哪一个子类。
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public class Odds { @Column(name = "id") @Id @GeneratedValue(strategy = GenerationType.AUTO) protected long id; protected long matchId; protected long updateTime; protected long bookmakerId; protected boolean active; }
Odds1x2与OddsHandicap两个子类的定义不变。运行后检查hibernate的输出:
Hibernate: drop table if exists Odds Hibernate: create table Odds ( DTYPE varchar(31) not null, id bigint not null auto_increment, active bit not null, bookmakerId bigint not null, matchId bigint not null, updateTime bigint not null, away_odds double precision, draw_odds double precision, home_odds double precision, granter char(1), granter_number integer, primary key (id) )
3.3 TABLE_PER_CLASS
使用这种策略,会对继承链中的每个类创建对应的表,父类的表中只有父类属性,子类的表中则包括了自身属性以及从父类继承过来的属性,而且子类表与父类表不在有任何关系。
但是使用这种策略有一个弊端,就是不能再将主键的生成策略设置为数据库自动生成(AUTO/IDENTITY/SEQUENCE),而只能使用GenerationType.TABLE来自动生成主键或者不使用自动生成主键。
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Odds { @Column(name = "id") @Id protected long id; protected long matchId; protected long updateTime; protected long bookmakerId; protected boolean active; }
这样,我们将会得到三张无任何关系的表:
Hibernate: create table Odds ( id bigint not null, active bit not null, bookmakerId bigint not null, matchId bigint not null, updateTime bigint not null, primary key (id) ) Hibernate: create table bs_odds_1x2 ( id bigint not null, active bit not null, bookmakerId bigint not null, matchId bigint not null, updateTime bigint not null, away_odds double precision, draw_odds double precision, home_odds double precision, primary key (id) ) Hibernate: create table bs_odds_handicap ( id bigint not null, active bit not null, bookmakerId bigint not null, matchId bigint not null, updateTime bigint not null, away_odds double precision, granter char(1), granter_number integer, home_odds double precision, primary key (id) )
四、父类非实体类
三种介绍的例子,我们使用的父类都是实体类,即都用@Entity注解过。如果不使用@Entity注解父类的话,结果可想而知,实体的子类虽然依然可以继承父类属性,但这些属性将无法被映射到数据库表字段中。但从我现在的应用场景考虑,我就是需要将Odds作为非实体类,因为我不需要在数据库中有一张odds的父类表,但我又希望父类的属性能被写入到子类的表中。为了满足这种需求,jpa提供了@MappedSuperclass注解。
@MappedSuperclass public class Odds { @Column(name = "id") @Id @GeneratedValue(strategy = GenerationType.AUTO) protected long id; protected long matchId; protected long updateTime; protected long bookmakerId; protected boolean active; }
@Entity @Table(name = "bs_odds_1x2") public class Odds1x2 extends Odds { @Column(name = "home_odds") private double homeOdds; @Column(name = "draw_odds") private double drawOdds; @Column(name = "away_odds") private double awayOdds; }
@Entity @Table(name = "bs_odds_handicap") public class OddsHandicap extends Odds { @Column(name = "granter") private char granter; @Column(name = "granter_number") private int granterNumber; @Column(name = "home_odds") private double homeOdds; @Column(name = "away_odds") private double awayOdds; }
对于的hibernate输出为:
Hibernate: drop table if exists bs_odds_1x2 Hibernate: drop table if exists bs_odds_handicap Hibernate: create table bs_odds_1x2 ( id bigint not null auto_increment, active bit not null, bookmakerId bigint not null, matchId bigint not null, updateTime bigint not null, away_odds double precision, draw_odds double precision, home_odds double precision, primary key (id) ) Hibernate: create table bs_odds_handicap ( id bigint not null auto_increment, active bit not null, bookmakerId bigint not null, matchId bigint not null, updateTime bigint not null, away_odds double precision, granter char(1), granter_number integer, home_odds double precision, primary key (id) )
来看一下自动创建出来的数据库表:
跟本文开头想要的一模一样(当然除了数据类型呀,因为在类定义中没有匹配好)
参考:
https://www.ibm.com/developerworks/cn/java/j-lo-hibernatejpa/
让球数grant_number写错了。。。写成了granter_number