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

发布了46 篇原创文章 · 获赞 6 · 访问量 2656

猜你喜欢

转载自blog.csdn.net/renguiriyue/article/details/103516596