为了演示,我们在Student的基础上添加了:
@Entity public class Teacher extends Person { private double fee;
三种策略实现映射和存取:
Person vip = new Person(); vip.setName("vip"); Student atai = new Student(); atai.setName("atai"); Teacher fg = new Teacher(); fg.setName("fg");
em.persist(vip); em.persist(atai); em.persist(fg);
Person vip = em.find(Person.class, 1); Student atai = em.find(Student.class, 2); Teacher fg = em.find(Teacher.class, 3); //运用多态 Person vip = em.find(Teacher.class, 3);
不管父类子类都注解上@Entity
演示:只生成了1个以基类命名的表Person,包含所有列,用DType列标记类型,值为类名
DType用于子类查询(父类不需要):
where student0_.id=? and student0_.DTYPE='Student'
@想一想@:为什么?这样用Teacher的Id去查Student,就只会返回NULL,而不会崩……
演示:多态没有问题
Student s = (Student)whos; //不会再次查询
父类上不注解@Entity而是@MappedSuperclass(没有注解会抛异常)
演示:
父类上注解@Entity和@Inheritance(strategy = InheritanceType.JOINED)
演示:生成了3个表。父类id自增,子类id不自增
存子类对象的时候,先存父类对象,获得id,再用该id存入子类对象:
insert into Person (age, isMale, sname) values (?, ?, ?) 2021-十月-11 09:42:54 DEBUG -Natively generated identity: 5 2021-十月-11 09:42:54 DEBUG - insert into Student (created, description, enroll, rest, score, id) values (?, ?, ?, ?, ?, ?)
取的时候,使用JOIN:
from Person person0_ left outer join Teacher person0_1_ on person0_.id=person0_1_.id left outer join Student person0_2_ on person0_.id=person0_2_.id@想一想@:为什么呢?还是为了多态,为了类型转换的时候不再查询一次数据库
from Student student0_ inner join Person student0_1_ on student0_.id=student0_1_.id
Student atai = new Student(); atai.setName("atai"); Student bo = new Student(); atai.setName("bo"); Student lang = new Student(); atai.setName("lang"); Teacher fg = new Teacher(); fg.setName("fg"); Teacher xy = new Teacher(); fg.setName("xy");
Hibernate提供了4中注解:
顾名思义,被注解的这个字段/属性,可以有多个entity指向它,比如:
public class Student extends Person { @ManyToOne @JoinColumn(foreignKey = @ForeignKey(name = "FK_Person2Teacher")) private Teacher teacher;
即:可以有多个Student指向一个Teacher。
或者更简单的记成:标记成@ManyToOne的,就是外键。
我们还添加注解了@JoinColumn(连接列),目的是为了自定义外键的名称,否则就会使用无意义的“编码”……
//所有的entity都要首先persist() em.persist(atai); em.persist(bo); em.persist(fg); //建立关联关系 atai.setTeacher(fg); bo.setTeacher(fg);演示:查看生成的表结构和SQL语句
alter table Student add constraint FK_Person2Teacher foreign key (teacher_id) references Teacher (id)
@想一想@:这样是不是有点傻?
关键在于我们persist()的顺序,应该先Techer再Student呀!
//先保存Teacher,获得其id em.persist(fg); //这样atai指向的Teacher就是有id的 atai.setTeacher(fg); bo.setTeacher(fg); em.persist(atai); em.persist(bo);
这样就只有INSERT没有UPDATE的操作了。
public class Teacher extends Person { @OneToMany(mappedBy = "taughtby") private Set<Student> students = new HashSet<>();说明:
private Teacher taughtBy;
演示:仍然是一个外键关系
@OneToMany private Set<Student> students = new HashSet<>();
演示:额外生出一张额外的表Teacher_Student
@想一想@:这是不是就是多对多?
仔细观察,发现这张表最大的问题就是它在studentsId上面加了唯一约束
alter table Teacher_Student add constraint UK_aeood6sxmnikrc6y1d80qlemr unique (students_id)这显然是和多对多的关系表要求相违背的。
这才是多对多正确的做法。
还配合@JoinTable指定外键名称:
public class Teacher extends Person { @ManyToMany @JoinTable(name = "Teacher2Student", //指定表名 joinColumns = @JoinColumn(name="teacher",foreignKey = @ForeignKey(name="FK_TeacherId")), inverseJoinColumns = @JoinColumn(name="student", foreignKey = @ForeignKey(name="FK_StudentId"))) private Set<Student> students = new HashSet<>();
说明(简单理解):
演示:单向多对多的表结构和存储
使用add()方法添加关系:
fg.getStudents().add(atai); fg.getStudents().add(bo);
可以单向,也可以双向:
@ManyToMany(mappedBy = "students") //students是字段名 private Set<Teacher> teachers = new HashSet<>();
但是要注意:
//lang是Student,非owner的一方,mappedBy的一方 //所以不会被持久化 lang.getTeachers().add(fg);
单向的一对一:
@OneToOne(optional = false) private Teacher taughtBy;
这样就会建立一个taughtBy的外键列,一样可以使用@ForeignKey指定外键名称等……(略)
和@ManyToOne不同的地方是使用了UNIQUE约束
alter table Student add constraint UK_ptebl24wokufgwsgxuoogye5m unique (taughtBy_id)
另:optional指定映射时是否允许该外键列为NULL。(默认为true,可以为NULL)
演示:建立关系
lang.setTeacher(fg);
还可以双向:
@OneToOne(mappedBy = "taughtBy") private Student student;
以前在Hibernate里被称之为components,JPA中被称之为value type。典型的特征:没有Id。所以需要嵌在其他entity中使用,由其父entity导出
@Embeddable:定义类
@Embeddable public class Contact {
@Embedded:标记字段
@Embedded private Contact contact;
演示:Contact映射成一张表,而是“嵌入”到Student表中……
@想一想@:ValueType也可能/可以有一对多么?
Hibernate默认类名对表名,字段/属性名对列名,关系表用下划线连接……
我们也可以使用annotation自定义,但除此以外还有:
比如我们使用TPC继承的时候,在父类中定义的字段,想在不同的子类中映射成不同的列名:
//注意这里的id字段,不是定义在Student而是Person中的 @AttributeOverride(name="id", column = @Column(name="sid")) public class Student extends Person {
本质上Hibernate通过NamingStrategy(命名策略)来确定映射生成的表名列名等。
其命名策略又可以分为:
我们这只就ImplicitNamingStrategy的实现做一个介绍。
首先声明一个ImplicitNamingStrategy类(但通常我们不会直接实现ImplicitNamingStrategy,而是继承它的实现类):
public class YQBImplicitNamingStrategy extends ImplicitNamingStrategyJpaCompliantImpl
并(在persistence.xml中)告诉Hibernate使用这个策略:
<property name="hibernate.implicit_naming_strategy" value="YQBImplicitNamingStrategy"/>
然后通过@Override实现自己的命名策略,比如我们来修改外键名称的生成规则:
@Override public Identifier determineForeignKeyName(ImplicitForeignKeyNameSource source) { //toIdentifier()参考基类获得 return toIdentifier(String.join( "_", /* 因为mysql的语法要求,不要使用短横线 */ "FK", source.getTableName().getText(), //为了简便,假设外键列只有一列 source.getColumnNames().get(0).getText(), source.getReferencedTableName().getText()), source.getBuildingContext()); }生成的外键名:
add constraint FK_Student_taughtBy_id_Teacher
多快好省!前端后端,线上线下,名师精讲
更多了解 加: