Spring Data JPA相关——多表联合查询中注解的使用
Spring Data JPA相关——多表联合查询中注解的使用
基本注解
@Entity
@Entity 用于定义对象将会成为被 JPA 管理的实体,将字段映射到指定的数据库表中
@Table
用于指定实体类对应的数据库表名
public @interface Table {
//表的名字,可选。如果不填写,系统认为好实体的名字一样为表名。
String name() default "";
//此表的catalog,可选
String catalog() default "";
//此表所在schema,可选
String schema() default "";
//唯一性约束,只有创建表的时候有用,默认不需要。
UniqueConstraint[] uniqueConstraints() default { };
//索引,只有创建表的时候使用,默认不需要。
Index[] indexes() default {}; }
@id
@Id 定义属性为数据库的主键,一个实体里面必须有一个,并且必须和 @GeneratedValue 配合使用和成对出现
@GeneratedValue
主键生成策略
public @interface GeneratedValue {
//Id的生成策略
GenerationType strategy() default AUTO;
//通过Sequences生成Id,常见的是Orcale数据库ID生成规则,这个时候需要配合@SequenceGenerator使用
String generator() default "";
}
GenerationType 一共有以下四个值:
public enum GenerationType {
//通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。 TABLE,
//通过序列产生主键,通过 @SequenceGenerator 注解指定序列名, MySql 不支持这种方式; SEQUENCE,
//采用数据库ID自增长, 一般用于mysql数据库
IDENTITY,
//JPA 自动选择合适的策略,是默认选项;
AUTO
}
@IdClass
@IdClass 利用外部类的联合主键
作为复合主键类,要满足以下几点要求。
- 必须实现 Serializable 接口。
- 必须有默认的 public 无参数的构造方法。
- 必须覆盖 equals 和 hashCode 方法。equals 方法用于判断两个对象是否相同,EntityManger 通过 find 方法来查找 Entity 时,是根据 equals 的返回值来判断的。本例中,只有对象的 name 和 email 值完全相同时或同一个对象时则返回 true,否 则返回 false。hashCode 方法返回当前对象的哈希码,生成 hashCode 相同的概率越小越好,算法可以进行优化。
@Basic
@Basic 表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为 @Basic。
@Transient
@Transient 表示该属性并非一个到数据库表的字段的映射,表示非持久化属性。JPA 映射数据库的时候忽略它,与 @Basic 相反的作用
@Column
@Column 定义该属性对应数据库中的列名
public @interface Column {
//数据库中的表的列名;可选,如果不填写认为字段名和实体属性名一样。
String name() default "";
//是否唯一。默认flase,可选。
boolean unique() default false;
//数据字段是否允许空。可选,默认true。
boolean nullable() default true;
//执行insert操作的时候是否包含此字段,默认,true,可选。
boolean insertable() default true;
//执行update的时候是否包含此字段,默认,true,可选。
boolean updatable() default true;
//表示该字段在数据库中的实际类型。
String columnDefinition() default "";
//数据库字段的长度,可选,默认255
int length() default 255;
}
@Temporal
@Temporal 用来设置 Date 类型的属性映射到对应精度的字段。
- @Temporal(TemporalType.DATE)映射为日期 // date (只有日期)
- @Temporal(TemporalType.TIME)映射为日期 // time (是有时间)
- @Temporal(TemporalType.TIMESTAMP)映射为日期 // date time (日期+时间)
@Lob
@Lob 将属性映射成数据库支持的大对象类型,支持以下两种数据库类型的字段
- Clob(Character Large Ojects)类型是长字符串类型,java.sql.Clob、Character[]、char[] 和 String 将被映射为 Clob 类 型。
- Blob(Binary Large Objects)类型是字节类型,java.sql.Blob、Byte[]、byte[]和实现了 Serializable 接口的类型将被映射为 Blob 类型。
- 由于 Clob,Blob 占用内存空间较大一般配合 @Basic(fetch=FetchType.LAZY) 将其设置为延迟加载。
多表关联注解
@JoinColumn
定义外键关联的字段名称
@JoinColumn 主要配合 @OneToOne、@ManyToOne、@OneToMany 一起使用,单独使用没有意义。
@JoinColumn 可以定义多个字段的关联关系。
@OneToOne
一对一关联关系
用法 @OneToOne 需要配合 @JoinColumn 一起使用。注意:可以双向关联,也可以只配置一方,看实际需求
源码:
public @interface OneToOne {
//关系目标实体,非必填,默认该字段的类型。
Class targetEntity() default void.class;
//cascade 级联操作策略
/*
1. CascadeType.PERSIST 级联新建
2. CascadeType.REMOVE 级联删除
3. CascadeType.REFRESH 级联刷新
4. CascadeType.MERGE 级联更新
5. CascadeType.ALL 四项全选
6. 默认,关系表不会产生任何影响 */
CascadeType[] cascade() default {};
//数据获取方式EAGER(立即加载)/LAZY(延迟加载)
FetchType fetch() default EAGER;
//是否允许为空
boolean optional() default true;
//关联关系被谁维护的。 非必填,一般不需要特别指定。
//注意:只有关系维护方才能操作两者的关系。被维护方即使设置了维护方属性进行存储也不会更新外键关联。1)mappedBy不 能与@JoinColumn或者@JoinTable同时使用。2)mappedBy的值是指另一方的实体里面属性的字段,而不是数据库字段,也不是 实体的对象的名字。既是另一方配置了@JoinColumn或者@JoinTable注解的属性的字段名称。 String mappedBy() default "";
//是否级联删除。和CascadeType.REMOVE的效果一样。两种配置了一个就会自动级联删除 boolean orphanRemoval() default false;
}
案例:假设一个学生对应一个班级,添加学生的同时添加班级,Student类的内容如下:
@OneToOne(cascade = CascadeType.PERSIST)
//关联的外键字段
@JoinColumn(name = "grade_id")
private Grade grade;
如果需要双向关联,Grade类的内容如下:
@OneToOne(mappedBy = "grade")
private Student student;
Student实体类
@Entity
@Data
@Table(name = "t_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String studentname;
//一对一关联
@OneToOne(cascade = CascadeType.PERSIST)
//关联的外键字段
@JoinColumn(name = "grade_id")
private Grade grade;
private String sex;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", studentname='" + studentname + '\'' + ",grade=" + this.grade.getGradename() +
'}';
}
}
Grade实体类
@Data
@Entity
@Table(name = "t_grade")
public class Grade {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String gradename;
//一对一
@OneToOne(mappedBy = "grade")
private Student student;
@Override
public String toString() {
return "Grade{" +
"id=" + id +
", gradename='" + gradename + '\'' +
'}';
}
}
StudentRepository接口
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
测试类
@SpringBootTest
public class OneToOneTest {
@Autowired
private StudentRepository studentRepository;
@Test
public void testOneToOne(){
//创建年级对象
Grade grade = new Grade();
grade.setGradename("一年级");
//创建学生对象
Student student = new Student();
student.setStudentname("张浩");
student.setSex("男");
//设置关联关系
student.setGrade(grade);
//保存
studentRepository.save(student);
}
@Test
public void testSearch(){
Optional<Student> student = studentRepository.findById(1);
System.out.println("学生信息:"+student);
System.out.println("年级信息:"+student.get().getGrade());
}
}
@OneToMany 一对多 & @ManyToOne 多对一
@OneToMany源码语法
public @interface OneToMany {
Class targetEntity() default void.class;
//cascade 级联操作策略:(CascadeType.PERSIST、CascadeType.REMOVE、CascadeType.REFRESH、 CascadeType.MERGE、CascadeType.ALL) 如果不填,默认关系表不会产生任何影响。 CascadeType[] cascade() default {};
//数据获取方式EAGER(立即加载)/LAZY(延迟加载) FetchType fetch() default LAZY;
//关系被谁维护,单项的。注意:只有关系维护方才能操作两者的关系。 String mappedBy() default "";
//是否级联删除。和CascadeType.REMOVE的效果一样。两种配置了一个就会自动级联删除 boolean orphanRemoval() default false;
}
public @interface ManyToOne {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default EAGER;
boolean optional() default true;
}
创建User实体类
-
多个用户拥有同一个角色(多对一)
-
toString()方法不添加Roles属性
-
添加无参构造方法及带参(用户名,角色类型)构造方法
@Entity
@NoArgsConstructor
@Table(name = "t_users")
public class User {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
public User(String username) {
this.username = username;
}
//多个用户拥有同一个角色
@ManyToOne
@JoinColumn(name = "roles_id")
private Role role;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
'}';
}
}
创建Role实体类
-
一个角色下有多个用户(一对多)
-
toString()方法不添加users属性
-
添加无参构造方法及带参(角色名称)构造方法
@Entity
@NoArgsConstructor
@Table(name = "t_roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String rolename;
public Role(String rolename) {
this.rolename = rolename;
}
//一个角色被多个用户拥有
// fetch = FetchType.EAGER:立即加载
@OneToMany(mappedBy = "role",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<User> users = new HashSet<User>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRolename() {
return rolename;
}
public void setRolename(String rolename) {
this.rolename = rolename;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", rolename='" + rolename + '\'' +
'}';
}
}
创建RolesRepository接口
@Repository
public interface RoleRepository extends JpaRepository<Role, Integer> {
}
测试类
@SpringBootTest
public class test {
@Autowired
private RoleRepository roleRepository;
/*** 级联添加:添加角色同时添加用户 */
@Test
public void test1(){
//创建角色对象
Role role = new Role("经理");
//创建两个用户
User user1 = new User();
user1.setUsername("王五");
user1.setRole(role);
User user2 = new User();
user2.setUsername("赵四");
user2.setRole(role);
//设置关联关系
role.getUsers().add(user1);
role.getUsers().add(user2);
//级联保存用户
roleRepository.save(role);
}
//级联查询
@Test
@Transactional
public void test2(){
Role role = roleRepository.getOne(1);
System.out.println(role.getRolename());
Set<User> users = role.getUsers();
users.forEach(System.out::println);
}
}
@JoinTable 关联关系表
@JoinTable 是指如果对象与对象之间有个关联关系表的时候,就会用到这个,一般和 @ManyToMany 一起使用。
用法
假设 Blog 和 Tag 是多对多的关系,有个关联关系表 blog_tag_relation ,表中有两个属性 blog_id 和 tag_id ,那么 Blog 实
体里面的写法如下:
@Entity public class Blog{
@ManyToMany
@JoinTable( name="blog_tag_relation", joinColumns=@JoinColumn(name="blog_id",referencedColumnName="id"), inverseJoinColumn=@JoinColumn(name="tag_id",referencedColumnName="id")
private List<Tag> tags = new ArrayList<Tag>(); )
}
@ManyToMany 多对多
@ManyToMany 表示多对多,和 @OneToOne、@ManyToOne 一样也有单向双向之分,单项双向和注解没有关系,只看实体类之 间是否相互引用。 主要注意的是当用到 @ManyToMany 的时候一定是三张表,不要想着偷懒,否则会发现有很多麻烦
多对多的关联关系案例
需求:一个项目由多个员工负责,一个员工参与多个项目(多对多关系)--给项目分配员工
员工:多方
项目:多方
创建Project实体类
-
一个项目由有多个员工负责(一对多)
-
toString()方法不添加Employee属性
-
添加无参构造方法及带参(项目名称)构造方法
@NoArgsConstructor
@Entity
@Table(name = "t_projects")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer projectId;
private String projectName;
public Project(String projectName) {
this.projectName = projectName;
}
/* 配置项目到员工的多对多关系
* * 配置多对多的映射关系
* * 1.声明表关系的配置
* * @ManyToMany(targetEntity = Employee.class)
* //多对多 * targetEntity:代表对方的实体类字节码
* * 2.配置中间表(包含两个外键)
* * @JoinTable
* * name : 中间表的名称
* * joinColumns:配置当前对象在中间表的外键
* * @JoinColumn的数组
* * name:外键名
* * referencedColumnName:参照的主表的主键名
* * inverseJoinColumns:配置对方对象在中间表的外键
*
* */
@OrderBy("employee_name DESC")
@ManyToMany(targetEntity = Employee.class, cascade = CascadeType.ALL)
//第三张表(外键关系表、中间表)
// name属性:第三张表的表名称
@JoinTable(name = "t_employee_project",
//在中间表的主键名称,当前类的主键名称
joinColumns = @JoinColumn(name = "project_id", referencedColumnName = "projectId"),
//对象类在中间表的主键名称,当前类的主键名称
inverseJoinColumns = @JoinColumn(name = "employee_id", referencedColumnName = "empId")
)
private Set<Employee> employees = new HashSet<>();
public Integer getProjectId() {
return projectId;
}
public void setProjectId(Integer projectId) {
this.projectId = projectId;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public Set<Employee> getEmployees() {
return employees;
}
public void setEmployees(Set<Employee> employees) {
this.employees = employees;
}
@Override
public String toString() {
return "Project{" +
"projectId=" + projectId +
", projectName='" + projectName + '\'' +
'}';
}
}
创建Employee实体类
-
一个员工负责多个项目(一对多)
-
toString()方法不添加Project属性
-
添加无参构造方法及带参(员工名称)构造方法
@NoArgsConstructor
@Entity
@Table(name = "t_employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer empId;
private String employeeName;
public Employee(String employeeName) {
this.employeeName = employeeName;
}
//多对多放弃维护权:被动的一方放弃
@ManyToMany(mappedBy = "employees")
private Set<Project> projects = new HashSet<>();
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
@Override
public String toString() {
return "Employee{" +
"empId=" + empId +
", employeeName='" + employeeName + '\'' +
'}';
}
}
创建ProjectRepository接口
public interface ProjectRepository extends JpaRepository<Project,Integer> {
}
测试多对多
- 创建2个项目(超市管理系统、酒店管理系统),3个员工(张三、李四、王五)
- 张三、李四、王五负责超市管理系统
- 张三、李四同时负责酒店管理系统
/*** 级联添加:添加角色同时添加用户 */
@Test
//以下两个注解必须提供
@Transactional
//开启事务
@Rollback(false)
//取消回滚
public void testAdd() {
//创建两个项目对象
Project project1 = new Project("超市管理系统");
Project project2 = new Project("酒店管理系统");
//创建三个员工对象
Employee employee1 = new Employee("张三");
Employee employee2 = new Employee("李四");
Employee employee3 = new Employee("王五");
//设置关联关系
//给超市管理系统分配员工
project1.getEmployees().add(employee1); project1.getEmployees().add(employee2); project1.getEmployees().add(employee3);
//给酒店管理系统分配员工
project2.getEmployees().add(employee1); project2.getEmployees().add(employee2);
//保存
projectRepository.save(project1);
projectRepository.save(project2);
}
必须在事务环境中运行,否则会出现 detached entity passed to persist 错误
结果
生成的t_employee_project表
生成的t_employees表
生成的t_projects表