spring data jpa 注解实战使用
用hibernate 开发了一段时间,觉得自己不够规范,所以打算整理整理。这篇博文从实战的角度来编写了,每个用到的注解我会细细的描述一下。如有不规范的地方,也请道友及时指正。
项目搭建
建立简单的实体类 sql
先创建两张没有关联的表
DROP TABLE IF EXISTS `t_student`;
create table t_student(
`ID` bigint(11) AUTO_INCREMENT not null ,
`NAME` varchar(20) not null,
`CLASSROOM_ID` bigint(11) not null,
`VERSION` int(11) NOT NULL DEFAULT 0,
`CREATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CREATE_BY` bigint(20) NOT NULL DEFAULT 0,
`UPDATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
`UPDATE_BY` bigint(20) NOT NULL,
`IS_DELETED` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY(id)
)ENGINE = INNODB;
DROP TABLE IF EXISTS `t_classroom`;
create table t_classroom(
`CLASSROOM_ID` bigint(11) AUTO_INCREMENT not null , -- 这里一般定义ID即可,但是这是为了后面测试,写成这样。
`NAME` varchar(11) not null,
`VERSION` int(11) NOT NULL DEFAULT 0,
`CREATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CREATE_BY` bigint(20) NOT NULL DEFAULT 0,
`UPDATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
`UPDATE_BY` bigint(20) NOT NULL,
`IS_DELETED` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY(classroom_id)
)ENGINE = INNODB;
因为每张表操作都是带 VERSION(版本号) CREATE_AT(创建时间) CREATE_BY(创建者) UPDATE_AT(更新时间) UPDATE_BY(更新者) IS_DELETED(是否删除)
实体类干起来~
大家不急着注重底下的注解,我会娓娓道来的。
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@DynamicUpdate
@Data
public abstract class BaseEntity {
@Version
private Long version;
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private Date createAt;
@CreatedBy
private Long createBy;
@Temporal(TemporalType.TIMESTAMP)
@LastModifiedDate
private Date updateAt;
@LastModifiedBy
private Long updateBy;
@Column(name = "IS_DELETED", columnDefinition = "BIT default 0")
private boolean isDeleted = false;
}
@Entity
@Data
public class Student extends BaseEntity implements Serializable {
private static final long serialVersionUID = 3664271100710458554L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private Long classroom_id;
}
// 省略一堆do , service , controller , 主要说明白hibernate 注解带来的优势。
service 代码跑起来~
// 执行下面的代码,会发现case 执行成功。
@Transactional
public void save() {
Student student = new Student();
student.setName("张三");
student.setClassroom_id(1L);
}
既然case 能执行成功,说明BaseEntity中的注解肯定起着某些作用啦
我一不做,二不休,上面的代码再复制一遍下来
@Data
// 注解使用在父类上面,标识的类表示其不能映射到数据库表,它所拥有的属性能够映射在其子类对用的数据库表中
@MappedSuperclass
// 这个注解就是用来自动填充字段用的了,@CreatedDate , @LastModifiedDate 会自动在插入。但是CreatedBy和LastModifiedBy并没有赋值,因为需要实现AuditorAware接口来返回你需要插入的值
@EntityListeners(AuditingEntityListener.class)
// 该注解在更新的时候,只更新修改的字段(执行sql语句set 的字段只有被修改的)
@DynamicUpdate
@Data
public abstract class BaseEntity {
// 乐观锁标准
@Version
private Long version;
// @Temporal(TemporalType.DATE)——》实体类会封装成日期“yyyy-MM-dd”的 Date类型。
// @Temporal(TemporalType.TIME)——》实体类会封装成时间“hh-MM-ss”的 Date类型。
// @Temporal(TemporalType.TIMESTAMP)——》实体类会封装成完整的时间“yyyy-MM-dd
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private Date createAt;
@CreatedBy
private Long createBy;
@Temporal(TemporalType.TIMESTAMP)
@LastModifiedDate
private Date updateAt;
@LastModifiedBy
private Long updateBy;
// columnDefinition 定义字段的类型
@Column(name = "IS_DELETED", columnDefinition = "BIT default 0")
private boolean isDeleted = false;
}
一对一的映射
创建学生的idcard 和学生是一对一的关系
DROP TABLE IF EXISTS `t_card`;
create table t_card(
`ID` bigint(11) AUTO_INCREMENT not null ,
`CARD_ID` varchar(20) not null,
`STUDENT_ID` bigint(11) not null,
`VERSION` int(11) NOT NULL DEFAULT 0,
`CREATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CREATE_BY` bigint(20) NOT NULL DEFAULT 0,
`UPDATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
`UPDATE_BY` bigint(20) NOT NULL,
`IS_DELETED` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY(id),
CONSTRAINT fk01_studentId FOREIGN KEY(student_id) REFERENCES t_student(id)
)ENGINE = INNODB;
实体类Card
@OneToOne
@JoinColumn(name = "studentId", referencedColumnName = "ID", insertable = false,
updatable = false)
private Student student;
@Transactional
public void save() {
Student student = new Student();
student.setName("张三");
student.setClassroomId(1L);
studentRepository.save(student);
Card card = new Card();
card.setCardNo("20191214");
card.setStudent(student);
cardRepository.save(card);
}
当我执行上面save 方法的时候,报错了,说我student_id 没有值,可是我明明已经插入student对象了呀。
解决办法:
//1, insertable = false 修改成 true (默认 insertable 为 true)
//2, 在card 实体类中添加 private Integer studentId (个人偏爱这种方法,级联只做查询关联)
级联查询看看有什么好处
public Student getCard() {
// 一般情况下,不推荐使用getOne 方法。
// card 对象中的级联关系也会被查询出来
Card card = cardRepository.findOne(5L);
System.out.println(card.toString()); // 注意student类中tostring方法重写
return null;
}
一对多的映射
创建一个classroom 对象
@Entity
@Data
public class Classroom extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1105836439867182189L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name = "classroom_id")
private Long id;
/**
* 教室的名称
* name : 映射的是数据库字段,当我们数据库字段表达的意思趋近于关联实体类时,要修改实体类,那么就要添加 name 属性。
*/
@Column(name = "name", nullable = false)
private String classroomName;
@OneToMany(mappedBy = "classroom", fetch = FetchType.LAZY)
private List<Student> student;
}
student 类 需要添加
@ManyToOne
private Classroom classroom;
// 注释掉
//private Long classroomId;
首先我们再回到上面,看看t_student ,里面存在一个外键 classroom_id 。
mappedBy 的意思是:
Student 中是存在 Classroom 的ID 的,故Student 是可以改变ClassroomID,所以 Student 是控制一方,而 Classroom 是非控制一方 或叫做被控制一方。)
那么被控制一方上 需要有 mappedBy ,当然mappedBy 关联的可以使对象类型也可以是基本数据类型
而控制一方需要添加上 @ManyToOne private Classroom classroom; ,因为mappedBy 映射的classroom ,就是该对象。
插一系列数据瞧瞧
//查询 classroom_id = 1
Classroom classroom = classroomRepository.findOne(1L);
Student student = new Student();
student.setName("lisi");
student.setClassroom(classroom);
studentRepository.save(student);
Card card = new Card();
card.setCardNo("20191214A");
card.setStudentId(student.getId());
cardRepository.save(card);
擦,有报错了。 说什么 classroom_classoroom_id 什么鬼东西的没有赋值。
em…想想也知道,肯定跟我字段classroom_id 命名的原因有关系了。
继续修改
@ManyToOne
@JoinColumn(name="classroom_id")
private Classroom classroom;
发现成功的把这个问题解决了。
查查这些对象看看效果
分别以student 和 classroom 的视角查询。
public Student getStudent() {
Student student = studentRepository.findOne(9L);
System.out.println(student.toString());
return null;
}
@Override
public Classroom getClassroom() {
Classroom classroom = classroomRepository.findOne(1L);
System.out.println(classroom);
return null;
}
可以debug 下,正常看到级联的数据
复合主键
@Embeddable、@Embedded、@EmbeddedId
先举个例子,一班有一个人叫小明,两班有一个人叫小明,这个时候我们如果不添加主键ID,那么同时我们可以使用(班级id,加上学号id作为复合主键)。
-- 添加字段
alter table t_student add STU_NO VARCHAR(20) not null ;
-- 执行该语句前,把之前生成的数据设置为唯一值
ALTER TABLE `t_student` ADD unique(`STU_NO`);
在student对象中添加
@Column(name = "STU_NO" , unique = true)
private String studentNo;
创建表,专门用于统计整个年级组的分数
DROP TABLE IF EXISTS `t_score`;
create table t_score(
`classroom_id` bigint(11) not null ,
`stu_no` varchar(20) not null,
`score` bigint(11) null,
`VERSION` int(11) NOT NULL DEFAULT 0,
`CREATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CREATE_BY` bigint(20) NOT NULL DEFAULT 0,
`UPDATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
`UPDATE_BY` bigint(20) NOT NULL,
`IS_DELETED` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY(classroom_id,stu_no),
CONSTRAINT fk02_classroomId FOREIGN KEY(classroom_id) REFERENCES t_classroom(classroom_id)
)ENGINE = INNODB;
创建复合主键
// 该注解就是用在定义复合主键上面的
@Embeddable
@Data
public class ScorePK implements Serializable {
private static final long serialVersionUID = -5728533370830797453L;
@ManyToOne(cascade={CascadeType.DETACH})
@JoinColumn(name = "classroom_id")
private Classroom classroom = new Classroom();
@Column(name = "stu_no")
private String studentNo;
}
Score 对象创建
第一种方式
@Entity
@Data
public class Score extends BaseEntity implements Serializable {
// 标注该注解就是复合主键了吧
@EmbeddedId
private ScorePK pk = new ScorePK();
private Long score;
}
第二种方式
@Entity
@Data
@IdClass(ScorePK.class)
public class Score extends BaseEntity implements Serializable {
// 需要注意:组合主键类,映射到实体的多个字段,且类型一致(也就这里标记@Id的要和ScorePK 里字段类型一致)
@Id
@Column(name = "classroom_id", nullable = false)
private Classroom classroom;
@Id
@Column(name = "stu_no", nullable = false)
private String studentNo;
private Long score;
}
添加个数据瞧瞧
@Transactional
public void save() {
Classroom classroom = classroomRepository.findOne(1L);
Student student = new Student();
student.setClassroom(classroom);
student.setName("wangwu");
student.setStudentNo("1");
studentRepository.save(student);
Score score = new Score();
score.getPk().setClassroom(classroom);
score.getPk().setStudentNo(student.getStudentNo());
score.setScore(95L);
scoreRepository.save(score);
}
引用复合主键作为外键
每个人的分数都有语数外等,且每个人的评价都是不唯一标准去评判(当然我就是楞要凑个复合外键用用)
DROP TABLE IF EXISTS `t_score_item`;
create table t_score_item(
`id` bigint(11) AUTO_INCREMENT not null ,
`classroom_id` bigint(11) not null,
`stu_no` varchar(20) not null,
`stardard` varchar(20) not null,
`VERSION` int(11) NOT NULL DEFAULT 0,
`CREATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CREATE_BY` bigint(20) NOT NULL DEFAULT 0,
`UPDATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
`UPDATE_BY` bigint(20) NOT NULL,
`IS_DELETED` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY(id),
CONSTRAINT fk02_classroomId_stu_no FOREIGN KEY(classroom_id,stu_no) REFERENCES t_score(classroom_id,stu_no)
)ENGINE = INNODB;
@Entity
@Data
public class ScoreItem extends BaseEntity implements Serializable {
private static final long serialVersionUID = 2150094500422624893L;
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
// 分数 - 有多个标准
@ManyToOne
@JoinColumns({
@JoinColumn(name="classroom_id", referencedColumnName="classroom_id"),
@JoinColumn(name="stu_no", referencedColumnName="stu_no")
})
private Score score;
private String stardard;
}
插入个数据看看
@Transactional
public void save() {
Classroom classroom = classroomRepository.findOne(1L);
Student student = new Student();
student.setClassroom(classroom);
student.setName("王二");
student.setStudentNo("19");
studentRepository.save(student);
Score score = new Score();
score.getPk().setClassroom(classroom);
score.getPk().setStudentNo(student.getStudentNo());
score.setScore(97L);
scoreRepository.save(score);
ScoreItem scoreItem = new ScoreItem();
scoreItem.setScore(score);
scoreItemRepository.save(scoreItem);
}
主键使用于多张表
当我们想描述学生的一些信息时,使用student(id) 作为主键时
先创建一张关于学生信息的表
DROP TABLE IF EXISTS `t_student_item`;
create table t_student_item(
`id` bigint(11) not null ,
`address` varchar(100) not null,
`face_id` varchar(20) not null,
`VERSION` int(11) NOT NULL DEFAULT 0,
`CREATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CREATE_BY` bigint(20) NOT NULL DEFAULT 0,
`UPDATE_AT` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
`UPDATE_BY` bigint(20) NOT NULL,
`IS_DELETED` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY(id),
CONSTRAINT fk02_student_id3 FOREIGN KEY(id) REFERENCES t_student(id)
)ENGINE = INNODB;
创建对象看看
@Entity
@Data
// 引用 主键类
@PrimaryKeyJoinColumn(referencedColumnName="id")
public class StudentItem extends Student implements Serializable {
private static final long serialVersionUID = 7292903130582929393L;
private Long id;
private String address;
private String faceId;
}
当然需要在student 类中添加如下
// 让studentItem 能去关联id
@Inheritance( strategy = InheritanceType.JOINED )
看看结果
@Transactional
public void save() {
Classroom classroom = classroomRepository.findOne(1L);
StudentItem studentItem = new StudentItem();
studentItem.setStudentNo("999");
studentItem.setAddress("999号");
studentItem.setFaceId("723921930");
studentItem.setName("张三");
studentItem.setClassroom(classroom);
studenItemtRepository.save(studentItem);
}
分别在student 和studentItem 中保存成功了。且StudentItem 中的Id是student中id