SpringBoot 入门(三)——数据持久化

  • Post author:
  • Post category:其他


后端一定得与数据打交道,所以一定会做数据持久化,在 SpringBoot 中提供的 JPA 的 Api,可以很方便的实现数据的 CRUD。

一 添加依赖

在数据库部分,我使用的是 MySQL,所以在 pom 文件中需要添加如下依赖:

        <!-- MySQL 连接依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Jpa 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

二 定义实体类

需要持久化的实体类,可以通过注解很方便的定义,通过给实体类添加一些注解,SpringBoot 就会自动创建表,列的约束以及表之间的关系等。

下面定义一个用户类:

package com.qinshou.springbootdemo.bean;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * Description:用户实体类
 * Author: QinHao
 * Date: 2019/7/26 9:01
 */
@Entity
@Table(name = "user")
public class UserBean {
    /**
     * 自增长 Id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    /**
     * 用户名
     */
    @Column(name = "username")
    private String username;
    /**
     * 密码
     */
    @Column(name = "password")
    private String password;
    /**
     * 昵称
     */
    @Column(name = "nickname")
    private String nickname;
    /**
     * 生日
     */
    @Column(name = "birthday")
    private Long birthday;
}

运行程序后可以看到建表语句(如果配置文件中设置显示 SQL 语句的话):


2.1 数据持久化常用的注解

1.@Entity

使用了该注解的实体类表示需要持久化到数据库,SpringBoot 会创建该实体类对应的数据库。

属性:

name:表名,如果不指定,则会用下划线命名法来转换类名作为表名。表名也可以使用 @Table 注解指定。

2.@Table

该注解用于表的定义。

属性:

name:表名,如果不指定,则会使用 @Entity 注解的表名命名规则。

catalog:数据库名,一般来说,数据库名是在数据库连接语句中指定的(也就是配置文件中),如果不指定该属性,则会使用连接语句中的数据库,如果指定了,则会在指定的数据库中创建该表,但如果指定的数据库不存在则会抛出异常。

schema:

uniqueConstraints:指定唯一约束,既可以指定单列约束也可以指定多列约束,如果是单列约束的时候,效果跟在 @Column 注解上指定 unique 属性一样。

indexes:指定索引,既可以指定单个索引约束也可以指定联合索引。

3.@UniqueConstraint

该属性用在 @Table 注解的 uniqueConstraints 属性中,用于指定约束。

属性:

name:约束名,如果不指定则随机生成。

columnNames:约束的参考列,如果指定的列名不存在则会抛出异常。

4.@Index

该属性用在 @Table 注解的 indexes属性中,用于指定索引。

属性:

name:索引名,如果不指定则随机生成。

columnNames:索引的参考列,如果指定的列名不存在则会抛出异常。

unique:是否唯一,如果为 true,则效果跟使用 @UniqueConstraint 注解一样。

5.@Id

该注解用于标识该属性为主键

6.@GeneratedValue

该注解用于指定主键生成策略。

属性:

strategy:主键生成策略,可选值有:

1)TABLE:使用一个特定的数据库表格来保存主键,持久化引擎通过关系数据库的一张特定的表格来生成主键。

2)SEQUENCE:在某些数据库中,不支持主键自增长,比如 Oracle,所以提供了一种“序列”的机制来生成主键。

3)IDENTITY:主键自增长,相当于 MySQL 中的 AUTO_INCREMENT。

4)AUTO:主键生成策略交给持久化引擎决定。

generator:主键生成器的名称。

7.@Column

该注解用于列的定义。

属性:

name:列名,如果不指定,则会用下划线命名法来转换属性名作为列名。

unique:是否唯一。

nullable:是否可以为空。

insertable:使用 “INSERT” 脚本插入数据时,是否需要插入该字段的值。

updatable:使用 “UPDATE” 脚本插入数据时,是否需要插入该字段的值。

columnDefinition:自定义列的 DDL,在有一些个性化需求的时候会用到,如 “TEXT”,”NOT NULL” 等。

table:当映射多个表时,指定表的表中的字段。默认值为主表的表名。

length:指定字段的长度。

precision:当字段类型为 double 时,表示数值的总长度

scale:当字段类型为 double 时,表示小数点所占的位数。

三 表与表的关系

表与表之间的关系只有三种,一对一,一对多和多对多。这三种关系都可能用到一个注解 @JoinColumn。

@JoinColumn:该注解用于指定关联列。如果不使用该注解的话有可能会生成一些中间表。

属性:

name:用于指定外键的名称。

分别用几个例子来说明一下这几种关系用到的注解。

3.1 一对一

用户实体类:

package com.qinshou.springbootdemo.bean;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;

/**
 * Description:用户实体类
 * Author: QinHao
 * Date: 2019/7/26 9:01
 */
@Entity()
@Table(name = "user")
public class UserBean {
    /**
     * 自增长 Id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    /**
     * 用户名
     */
    @Column(name = "username")
    private String username;
    /**
     * 密码
     */
    @Column(name = "password")
    private String password;
    /**
     * 昵称
     */
    @Column(name = "nickname")
    private String nickname;
    /**
     * 生日
     */
    @Column(name = "birthday")
    private Long birthday;
    /**
     * 驾照
     */
    @JoinColumn(name = "driving_license_id")
    @OneToOne(cascade = CascadeType.ALL,orphanRemoval = true)
    private DrivingLicenseBean mDrivingLicenseBean;

    ...
}

驾照实体类:

package com.qinshou.springbootdemo.bean;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

/**
 * Description:驾照实体类
 * Author: QinHao
 * Date: 2019/7/26 15:08
 */
@Entity
@Table(name = "driving_license")
public class DrivingLicenseBean {
    /**
     * 自增长 Id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    /**
     * 编号
     */
    @Column(name = "number")
    private String number;
    /**
     * 用户
     */
    @OneToOne(mappedBy = "mDrivingLicenseBean")
    private UserBean mUserBean;

    @Override
    public String toString() {
        return "DrivingLicenseBean{" +
                "id=" + id +
                ", number='" + number + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public UserBean getUserBean() {
        return mUserBean;
    }

    public void setUserBean(UserBean userBean) {
        mUserBean = userBean;
    }
}

@OneToOne

该注解表示一对一关系,如一个丈夫对应一个妻子,一个人对应一本驾照等。

属性:

targetEntity:对应实体类的 class,如果不指定该属性则会是 @OneToOne 注解修饰的成员变量的 class。一般不用指定。

cascade:级联关系,如果该属性指定得不对,在做对应操作时会抛出异常。级联关系通常在维护关系的一方中设置。

1)CascadeType.PERSIST:级联插入。

2)CascadeType.REMOVE:级联删除。

3)CascadeType.REFRESH:级联刷新,这个刷新不同于更新,是每次操作一方时,会先重新查询一下另一方的数据的意思。

4)CascadeType.MERGE:级联更新。

4)CascadeType.ALL:以上四种全部操作。

fetch:对应实体类的加载方式。

1)FetchType.EAGER:默认方式,查询时也立即把对应另一方的数据查询出来。

2)FetchType.LAZY:懒加载,什么时候用到了什么时候才会查询另一方的数据。

optional:表示对应实体类是否可以存在 null 值,true 表示可以,false 表示不可以。

mapperBy:用在被维护方,设置了该属性后,被维护方的表就不会生成对应列,注意该属性的值必须是维护方中指定被维护方的变量名,如上方的 mDrivingLicenseBean。

orphanRemoval:是否删除孤儿数据,设置为 true 时,当被维护方的数据没有被维护方关联时会自动删除,比如当一个人的驾照更新,这个人会与新的驾照数据建立关联,与旧的驾照数据也会解除关联,如果该属性为 true,则旧驾照的数据会自动删除。

3.2 一对多(多对一)

用户实体类:

package com.qinshou.springbootdemo.bean;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;

/**
 * Description:用户实体类
 * Author: QinHao
 * Date: 2019/7/26 9:01
 */
@Entity()
@Table(name = "user")
public class UserBean {
    /**
     * 自增长 Id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    /**
     * 用户名
     */
    @Column(name = "username")
    private String username;
    /**
     * 密码
     */
    @Column(name = "password")
    private String password;
    /**
     * 昵称
     */
    @Column(name = "nickname")
    private String nickname;
    /**
     * 生日
     */
    @Column(name = "birthday")
    private Long birthday;
    /**
     * 驾照
     */
    @JoinColumn(name = "driving_license_id")
    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    private DrivingLicenseBean mDrivingLicenseBean;
    /**
     * 拥有的汽车
     */
    @JoinColumn(name = "user_id")
    @OneToMany(cascade = CascadeType.ALL)
    private List<CarBean> mCarBeanList;

    ...
}

汽车实体类:

package com.qinshou.springbootdemo.bean;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

/**
 * Description:汽车实体类
 * Author: QinHao
 * Date: 2019/7/26 14:29
 */
@Entity
@Table(name = "car")
public class CarBean {
    /**
     * 自增长 Id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    /**
     * 品牌
     */
    @Column(name = "brand")
    private String brand;
    /**
     * 型号
     */
    @Column(name = "model")
    private String model;
    /**
     * 车主
     */
    @JoinColumn(name = "user_id")
    @ManyToOne()
    private UserBean user;

    ...
}

@OneToMany

该注解表示一对多中的一方,如一个人拥有多辆车,一个部门有多个员工等,通常“一”方也是维护关系的一方。它的各属性跟 @OneToOne 差不多,需要注意的是,使用了该注解的属性,应该也要使用 @JoinColumn 注解,并且 @JoinColumn 注解的 name 属性的值应该是一方的参考列,参考上面代码。

@ManyToOne

该注解表示一对多中的多方。它的各属性也跟 @OneToOne 差不多,需要注意的是,使用了该注解的属性,应该也要使用 @JoinColumn 注解,并且 @JoinColumn 注解的 name 属性的值应该是一方的参考列,参考上面代码,如果不加 @JoinColumn 注解则会多生成一张关系表。

3.3 多对多

老师实体类:

package com.qinshou.springbootdemo.bean;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

/**
 * Description:老师实体类
 * Author: QinHao
 * Date: 2019/7/31 9:31
 */
@Entity
@Table(name = "teacher")
public class TeacherBean {
    /**
     * 自增长 Id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    /**
     * 姓名
     */
    @Column(name = "name")
    private String name;
    /**
     * 学生列表
     */
    @JoinTable(name = "teacher_student_rel"
            , joinColumns = {@JoinColumn(name = "teacher_id", referencedColumnName = "id")}
            , inverseJoinColumns = {@JoinColumn(name = "student_id", referencedColumnName = "id")})
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<StudentBean> mStudentBeanList;

    public TeacherBean() {
    }

    public TeacherBean(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "TeacherBean{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", mStudentBeanList=" + mStudentBeanList +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<StudentBean> getStudentBeanList() {
        return mStudentBeanList;
    }

    public void setStudentBeanList(List<StudentBean> studentBeanList) {
        mStudentBeanList = studentBeanList;
    }
}

学生实体类:

package com.qinshou.springbootdemo.bean;

import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

/**
 * Description:学生实体类
 * Author: QinHao
 * Date: 2019/7/31 9:31
 */
@Entity
@Table(name = "student")
public class StudentBean {
    /**
     * 自增长 Id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    /**
     * 姓名
     */
    @Column(name = "name")
    private String name;
    /**
     * 老师列表
     */
    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "mStudentBeanList")
    private List<TeacherBean> mTeacherBeanList;

    public StudentBean() {
    }

    public StudentBean(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "StudentBean{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<TeacherBean> getTeacherBeanList() {
        return mTeacherBeanList;
    }

    public void setTeacherBeanList(List<TeacherBean> teacherBeanList) {
        mTeacherBeanList = teacherBeanList;
    }
}

@ManyToMany

该注解表示多对多关系,如一个老师可以有多个学生,一个学生也可以有多个老师;一个学生可以选多门选修课,一门选修课也可以被多个学生选。该注解和 @JoinTable 注解更配哦,@JoinTable 等一下记录。因为都是多方,所以两方都是使用 @ManyToMany 注解来标识,但是一般也会指定一个关系维护方,一般维护方会设置级联关系,加载方式是瞬时加载,@JoinTable 也是加在维护方的,而被维护方一般都是懒加载。

@JoinTable

该注解用于设置多对多关系的中间表,一般是关系维护方才会加该注解。

属性:

name:关系表的名字。

joinColumns:该属性用于指定自己这一方中在关系表的列。该属性的值是 @JoinColumn 注解数组,@JoinColumn 的 name 表示此方在关系表中的列名,referencedColumnName 表示关系表中外键的参考字段。

inverseJoinColumns:该属性指定对方在关系表的列,该属性的值跟 joinColumns 的值一样。

四 小结

至此,数据持久化常用的注解基本就是这些,当然有了这些注解还不够,这只是利用注解定义了对应的表,还没有进行 CRUD 的操作,后面就会接着学习如何使用 JPA 来进行数据的 CRUD。



版权声明:本文为zgcqflqinhao原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。