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