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

發佈了56 篇原創文章 · 獲贊 6 · 訪問量 4916
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章