学习目标
- 3.1 JPA多表查询
- 3.1.1 数据库表及关系
- 3.1.2 多表联接查询
- 3.1.3 关联映射
- 3.1.3.1 单向多对一关联
- 3.1.3.2 双向一对多关联
- 3.2 Spring Boot 集成 MyBAtis
- 3.2.1 回顾MyBatis
- 3.2.2 Spring Boot 集成MyBAtis
- 3.2.2.1 MyBAtis-Spring-Boot-Starter
- 3.2.2.2 XML配置版集成
- 3.2.2.3 注解配置版集成
(如果没有了解可以去我主页看看 第一至二章的内容来学习)我们已经对JPA的基本概念、对象/关系映射注解,仅仅通过少量配置实现单表大部分CRUD操作,进行分页,自定义QL语句,自定义动态查询完成复杂查询,本章我们继续学习JPA高级应用,掌握多表联接查询,以及通过关联映射完成延迟加载、级联操作等。
本章我们除了JPA的多表联接查询、关联映射外,还会在后半部分快速完成Spring Boot 和MyBatis的集成开发,包括基于XML配置和注解配置两种方式。
3.1 JPA多表查询
多表查询在 Spring Data JPA 中有两种实现方式,第一种是创建一个结果集的接口来接收多表接查询后的结果,第二种是利用JPA的关联映射来实现。
3.1.1 数据库表及关系
我们通常不直接编写代码来定义数据库表及其关系,因为这些操作更多是在数据库层面(如使用SQL语句)或通过ORM(对象关系映射)框架来完成的。不过,我可以向你展示如何使用Java代码结合JDBC(Java Database Connectivity)来执行SQL语句,这些SQL语句可以创建表、定义关系等。
使用JDBC创建表和关系
以下是一个简单的Java示例,展示了如何使用JDBC连接数据库并执行SQL语句来创建表及定义表之间的关系。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement; public class DatabaseExample { public static void main(String[] args) { // 数据库连接信息(请根据你的数据库进行更改) String url = "jdbc:mysql://localhost:3306/yourdatabase"; String user = "username"; String password = "password"; try { // 加载并注册JDBC驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 建立连接 Connection conn = DriverManager.getConnection(url, user, password); // 创建Statement对象来执行SQL语句 Statement stmt = conn.createStatement(); // 创建表 String sqlCreateTable1 = "CREATE TABLE IF NOT EXISTS Student (" + "id INT AUTO_INCREMENT PRIMARY KEY, " + "name VARCHAR(255) NOT NULL)"; String sqlCreateTable2 = "CREATE TABLE IF NOT EXISTS Course (" + "id INT AUTO_INCREMENT PRIMARY KEY, " + "name VARCHAR(255) NOT NULL)"; // 创建关系表 String sqlCreateRelationTable = "CREATE TABLE IF NOT EXISTS Enrollment (" + "student_id INT, " + "course_id INT, " + "FOREIGN KEY (student_id) REFERENCES Student(id), " + "FOREIGN KEY (course_id) REFERENCES Course(id), " + "PRIMARY KEY (student_id, course_id))"; // 执行SQL语句 stmt.executeUpdate(sqlCreateTable1); stmt.executeUpdate(sqlCreateTable2); stmt.executeUpdate(sqlCreateRelationTable); // 关闭资源 stmt.close(); conn.close(); System.out.println("Tables and relations created successfully"); } catch (Exception e) { e.printStackTrace(); } }
}
使用JPA或Hibernate
在Java中,更常见的方式是使用JPA(Java Persistence API)或Hibernate这样的ORM框架来管理数据库表及关系。这些框架允许你通过定义Java类(实体)及其关系来映射到数据库表及表之间的关系。
Hibernate很多公司都会用MyBatis代替它,Hibernate是一个对象关系映射(ORM)框架,它允许Java开发者以面向对象的方式与数据库进行交互,减少了直接编写SQL的需求。Hibernate的优点包括简化数据库操作、支持多种数据库、高性能的事务管理、高度灵活的查询语言HQL(Hibernate Query Language),以及良好的数据库无关性。此外,Hibernate还提供了缓存和延迟加载等技术,有效地提高了数据访问的性能,并保证了数据的一致性。
3.1.2 多表联接查询
在Spring Data JPA中,你可以通过@Query注解在Repository接口中定义自定义的JPQL(Java Persistence Query Language)查询,或者使用@EntityGraph来优化加载关联数据。但是,直接在JPQL中编写多表联接查询是更常见的做法。
假设你有两个实体类Student和Course,以及一个联接表Enrollment(虽然通常联接逻辑是通过实体关系来处理的,而不是物理的联接表)。
@Entity
public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 省略getter和setter @OneToMany(mappedBy = "student") private List<Enrollment> enrollments;
} @Entity
public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 省略getter和setter @OneToMany(mappedBy = "course") private List<Enrollment> enrollments;
} @Entity
@Table(name = "enrollment")
public class Enrollment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "student_id") private Student student; @ManyToOne @JoinColumn(name = "course_id") private Course course; // 省略getter和setter
}
然后,在你的Repository接口中,你可以定义一个自定义查询来执行多表联接:
public interface StudentRepository extends JpaRepository<Student, Long> { @Query("SELECT s FROM Student s JOIN FETCH s.enrollments e JOIN FETCH e.course c WHERE c.name = :courseName") List<Student> findStudentsByCourseName(@Param("courseName") String courseName);
}
注意:上面的查询使用了JOIN FETCH来优化加载关联数据,但请注意,这可能会根据数据库和JPA提供者的不同而有所不同,并且可能不是所有情况下都是最优选择。
3.1.3 关联映射
在Spring Boot中,关联映射通常是通过JPA(Java Persistence API)来实现的,它允许你定义实体之间的关系,并通过这些关系来映射数据库中的表。以下是一个简单的示例,展示了如何在Spring Boot项目中使用JPA来定义实体之间的关联映射。
假设我们有两个实体:Student 和 Course,以及一个关联表 Enrollment(虽然在实际操作中,JPA通常通过实体关系来管理关联,而不是物理的联接表,但这里为了说明目的,我们假设有这样一个表)。然而,在JPA中,我们通常不会直接映射到物理的联接表,而是会通过实体之间的关联关系来隐含这个联接。
首先,我们定义Student和Course实体,以及它们之间的关联:
import javax.persistence.*;
import java.util.List; @Entity
public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 使用@OneToMany注解来映射与Enrollment的关联关系 // mappedBy属性指向Enrollment中拥有当前实体引用的属性名 @OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true) private List<Enrollment> enrollments; // 省略getter和setter
} @Entity
public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 使用@OneToMany注解来映射与Enrollment的关联关系 // mappedBy属性指向Enrollment中拥有当前实体引用的属性名 @OneToMany(mappedBy = "course", cascade = CascadeType.ALL, orphanRemoval = true) private List<Enrollment> enrollments; // 省略getter和setter
} @Entity
@Table(name = "enrollment")
public class Enrollment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "student_id") private Student student; @ManyToOne @JoinColumn(name = "course_id") private Course course; // 省略getter和setter
}
在上面的代码中,Student 和 Course 实体都通过 @OneToMany 注解与 Enrollment 实体建立了关联。
注意:mappedBy 属性在 @OneToMany 注解中用于指定关系的“所有者”端,即拥有外键的那个实体。在这个例子中,Enrollment 实体包含了指向 Student 和 Course 的外键,因此 mappedBy 指向了 Enrollment 实体中相应的属性名。
3.1.3.1 单向多对一关联
在Spring Boot中,使用JPA(Java Persistence API)来实现单向多对一(Many-To-One)关联映射是一种常见的做法。单向多对一意味着一个实体(多的一方)包含对另一个实体(一的一方)的引用,但反过来则不然。
以下是一个简单的示例,展示了如何在Spring Boot项目中定义单向多对一关联映射的Java代码。
首先,我们定义“一的一方”实体,比如Course:
import javax.persistence.*; @Entity
public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 通常不需要额外的字段或方法来维护多对一关系,因为这是一端 // 省略getter和setter
}
然后,我们定义“多的一方”实体,比如Student,并包含对Course的引用:
import javax.persistence.*; @Entity
public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 单向多对一关联 @ManyToOne @JoinColumn(name = "course_id") // 指定外键列名 private Course course; // getter和setter public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Course getCourse() { return course; } public void setCourse(Course course) { this.course = course; }
}
在这个例子中,Student实体通过@ManyToOne注解与Course实体建立了单向多对一关联。@JoinColumn注解用于指定数据库中用于连接这两个表的外键列名(在这个例子中是course_id)。这意味着在Student表中将会有一个名为course_id的列,该列是Course表主键的外键。
请注意:由于这是单向关联,所以Course实体中不包含对Student实体的任何引用或集合。如果你需要双向关联(即Course实体也知道它有哪些学生),你需要在Course实体中添加一个@OneToMany注解的集合,并设置mappedBy属性指向Student实体中维护关联的字段名(在这个单向关联的示例中,我们不会这样做)。
3.1.3.2 双向一对多关联
在Spring Boot中,使用JPA(Java Persistence API)来实现双向一对多(One-To-Many)关联映射是一种常见的做法。双向关联意味着两个实体都包含对彼此的引用。以下是一个简单的示例,展示了如何在Spring Boot项目中定义双向一对多关联映射的Java代码。
首先,我们定义“一的一方”实体,比如Course,它包含一个指向多个Student的集合:
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set; @Entity
public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 双向一对多关联:多的一端在Student中,这里使用mappedBy来指定关系的维护端在Student @OneToMany(mappedBy = "course", cascade = CascadeType.ALL, orphanRemoval = true) private Set<Student> students = new HashSet<>(); // 省略getter和setter,但请确保为students提供getter和setter // 添加学生的方法,用于维护双向关系 public void addStudent(Student student) { this.students.add(student); student.setCourse(this); // 维护双向关系 } // 移除学生的方法,也用于维护双向关系 public void removeStudent(Student student) { this.students.remove(student); student.setCourse(null); // 维护双向关系 }
}
然后,我们定义“多的一方”实体,比如Student,它包含一个对Course的引用:
import javax.persistence.*; @Entity
public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 双向一对多关联:一的一端在Course中 @ManyToOne @JoinColumn(name = "course_id") private Course course; // 省略getter和setter // 设置课程时,如果这是双向关联,通常不需要额外的操作,因为JPA会处理它 // 但如果你需要在设置课程时执行一些自定义逻辑,可以在这里添加 public void setCourse(Course course) { this.course = course; }
}
在这个例子中,Course实体通过@OneToMany注解与Student实体建立了双向一对多关联,并通过mappedBy属性指定了关系的维护端在Student实体中(即Student实体中的course字段)。cascade = CascadeType.ALL表示对Course实体执行的操作(如保存、更新、删除)将级联到其关联的Student实体上。orphanRemoval = true表示如果Student实体不再被Course实体引用(即从students集合中移除),则这些Student实体也将被从数据库中删除。
请注意:为了维护双向关系的一致性,我们在Course实体中提供了addStudent和removeStudent方法,这些方法在添加或移除学生时也会更新学生实体中的course字段。这是确保双向关系一致性的关键步骤。
3.2 Spring Boot 集成 MyBAtis
3.2.1 回顾MyBatis
MyBatis是一款标准的ORM框架,被广泛的应用于各企业开发中。MyBAtis最早是Apache的一个开源项目 iBatis,2010年这个项目由 Apache Software Foundation迁移到了Google Code,并且改名为 MyBAtis,2013年11月又迁移到Github。
优点:
- SQL被统一提取出来,便于统一管理和优化。
- SQL和代码解耦,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰、更易维护、更易单元测试。
- 提供映射标签,支持对象与数据库的ORM字段关系映射
- 提供对象关系映射标签,支持对象关系组件维护
- 灵活书写动态SQL,支持各种条件来动态生成不同的SQL
缺点:
- 编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此
- SQL语句依赖于数据库,导致数据库移植性差
3.2.2 Spring Boot 集成MyBAtis
3.2.2.1 MyBAtis-Spring-Boot-Starter
MyBAtis-Spring-Boot-Starter是MyBAtis帮助我们快速集成Spring Boot 提供的一个组件包,使用这个组件可以做到以下几点:
- 构建独立的应用
- 几乎可以零配置
- 需要很少的XML配置
3.2.2.2 XML配置版集成
要在Spring Boot项目中使用XML配置,你需要做几件事情:
- 引入Spring XML配置支持: 确保你的pom.xml(Maven)或build.gradle(Gradle)文件中包含了Spring Boot的起步依赖,并且可能还需要添加Spring的XML配置支持依赖(尽管这通常不是必需的,因为Spring Boot的starter已经包含了这些)。
- 创建XML配置文件: 在你的src/main/resources目录下创建XML配置文件,比如applicationContext.xml。
- 在Java配置中导入XML配置: 使用@ImportResource注解来导入你的XML配置文件。这可以在你的主配置类(通常带有@SpringBootApplication注解的类)上完成,或者在任何其他配置类上。
下面是一个简单的例子:
-
Maven依赖(如果尚未包含)
确保你的pom.xml文件中包含了Spring Boot的起步依赖。对于XML支持,通常不需要额外的依赖,因为Spring Boot的starter已经包含了必要的库。 -
创建XML配置文件
在src/main/resources目录下创建applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myBean" class="com.example.MyBeanClass"/> </beans>
- 在Java配置中导入XML配置
在你的主配置类(或任何配置类)上使用@ImportResource注解来导入XML配置:
package com.example.demo; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource; @SpringBootApplication
@ImportResource("classpath:applicationContext.xml")
public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
}
- 使用XML中定义的Bean
现在,你可以在Spring Boot应用的任何地方通过自动装配(@Autowired)来使用myBean了,就像它是通过Java配置定义的Bean一样。
请注意,尽管你可以这样做,但通常建议尽可能使用基于注解的配置,因为它更简洁、更易于理解和维护。XML配置在需要时可以作为补充,但不应成为主要的配置方式。
3.2.2.3 注解配置版集成
在Spring Boot中,使用注解配置(Annotation-based Configuration)是推荐的方式来管理应用的配置和Bean的定义。这种方式利用Java配置类(通过@Configuration注解标识)和@Bean注解来替代传统的XML配置文件。
以下是一个简单的Spring Boot注解配置版集成Java代码的示例:
- 创建一个配置类
首先,你需要创建一个配置类,该类使用@Configuration注解来标记它是一个配置类。在这个类中,你可以定义Bean,使用@Bean注解来标注方法,Spring容器会调用这些方法并返回对象实例,然后将这些对象注册为Bean。
package com.example.demo.config; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class AppConfig { @Bean public MyService myService() { return new MyService(); }
}
在这个例子中,AppConfig是一个配置类,它定义了一个Bean,即MyService的一个实例。MyService可能是一个你自定义的服务类。
- 自定义服务类
接下来,定义MyService类,这个类可以包含你需要的业务逻辑。
package com.example.demo.service; public class MyService { public void doSomething() { // 实现你的业务逻辑 System.out.println("Doing something..."); }
}
- 使用定义的Bean
现在,你可以在任何需要MyService的地方,通过Spring的自动装配功能(例如使用@Autowired注解)来使用它。
package com.example.demo.controller; import com.example.demo.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
public class MyController { private final MyService myService; @Autowired public MyController(MyService myService) { this.myService = myService; } @GetMapping("/doSomething") public String doSomething() { myService.doSomething(); return "Something has been done!"; }
}
在这个例子中,MyController是一个REST控制器,它使用@Autowired注解来自动装配MyService的实例。然后,在doSomething方法中,它调用了MyService的doSomething方法,并返回一个响应。
- 启动类
确保你的Spring Boot应用有一个带有@SpringBootApplication注解的启动类。这个注解是一个方便的注解,它包含了@Configuration、@EnableAutoConfiguration和@ComponentScan注解。
package com.example.demo; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
}
这个启动类会扫描包含@SpringBootApplication注解的类所在的包及其子包下的所有组件(如配置类、控制器、服务等),并将它们注册到Spring容器中。由于AppConfig位于启动类所在包的子包中(在这个例子中是com.example.demo.config),因此它会被自动扫描并注册。
以上就是Spring Boot注解配置版集成Java代码的一个简单示例。通过这种方式,你可以完全摆脱XML配置文件,只使用Java代码来配置你的Spring应用。