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表