Spring-Data-Jpa关联查询

按照SpringData的规则,对于两个有关联关系的对象的查询,可以通过方法名中“_”下划线类标识,也可以通过Spring-Data-Jpa命名规范查询,同时SpringDataJpa还支持用@Query注解定义在数据访问层接口的方法上实现查询,下面来看一个示例进行理解。

1、创建一个springboot项目,在pom.xml中加入对应的依赖,在application.properties文件中配置数据源和JPA相关的属性。

2、创建持久化类

在项目中新建4个包,分别为entity(放置持久化类)、controller(控制器)、repository(定义数据访问接口的包)、service(业务逻辑处理类)。在entity包中创建两个持久化类学生类Stu.java和班级类Clazz.java,代码如下

Stu.java类(Stu和Clazz是多对一关系,表关联注解代码中有注释)

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.data.annotation.CreatedBy;

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

//用于标记持久化类,SpringBoot项目加载后会自动根据持久化类建表
@Entity
//设置表名为tb_stu
@Table(name="tb_stu")
public class Stu {
    private Integer id; //主键
    private String name;    //姓名
    private String address;     //地址
    private Integer age;    //年龄
    private char sex;   //性别
    //@JsonIgnore注解是类注解,作用是json序列化时将java bean中的一些属性忽略掉
    @JsonIgnore
    private Clazz clazz;

    /**
     * 使用@id指定主键。使用代码@GeneratedValue
     * 指定主键的生存策略,mysql默认为自动增长
     */
    @Id
    @GeneratedValue
    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    //关联表对应关系,学生与班级的多对一的关系
    @ManyToOne
    @CreatedBy
    public Clazz getClazz() {
        return clazz;
    }

    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }

    public Stu() {
    }

    public Stu(String name, String address, Integer age, char sex, Clazz clazz) {
        this.name = name;
        this.address = address;
        this.age = age;
        this.sex = sex;
        this.clazz = clazz;
    }
}

Class.java类(Clazz和Stu是一对多关系,表关联注解代码中有注释)

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

//用于标记持久化类,SpringBoot项目加载后会自动根据持久化类建表
@Entity
//设置表名为tb_clazz
@Table(name="tb_clazz")
public class Clazz {
    private Integer id; //主键
    private String name;    //班级名称
    //@JsonIgnore注解是类注解,作用是json序列化时将java bean中的一些属性忽略掉
    @JsonIgnore
    private List<Stu> stuList = new ArrayList<>();

    /**
     * 使用@id指定主键。使用代码@GeneratedValue(strategy = GenerationType.IDENTITY)
     * 指定主键的生存策略,mysql默认为自动增长
     */
    @Id
    @GeneratedValue
    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    //班级与学生是一对多的关联,mappedBy对应Stu表中clazz字段
    @OneToMany(cascade=CascadeType.ALL,mappedBy="clazz")
    public List<Stu> getStuList() {
        return stuList;
    }

    public void setStuList(List<Stu> stuList) {
        this.stuList = stuList;
    }

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

    public Clazz() {
    }
}

从上述代码可以看出,班级与学生是一对多的关系,一个班级可以有多名学生,此处做的是双向关联,即在班级对象中关联了学生对象,在学生对象中也关联了班级对象。

3、定义数据访问层接口

之后在repository包下新建一个接口,命名为StuRepository,该接口继承JpaRepository接口,以持久化对象Stu作为JpaRepository的第一个类型参数,表示当前所操作的持久化对象类型,Integer作为JpaRepository的第二个类型参数,用于指定ID类型,同时创建一个接口名称为ClazzRepository,继承JpaRepository的接口,用于访问班级信息的数据。

ClazzRepository.java接口

import com.mcy.springdatajpa.entity.Clazz;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ClazzRepository extends JpaRepository<Clazz, Integer> {
    
}

StuRepository.java接口(包含了一些关联查询)

import com.mcy.springdatajpa.entity.Stu;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Map;

public interface StuRepository extends JpaRepository<Stu, Integer> {
    /**
     * 根据班级名称查询这个班级所有学生的信息
     * 相当于JPQL语句:select s from stu s where s.clazzname = ?1
     * @param clazzNam
     * @return
     */
    List<Stu> findByClazz_name(String clazzNam);

    /**
     * 根据班级名称查询这个班级所有学生的信息
     * 相当于JPQL语句:select s from stu s where s.clazzname = ?1
     * @param clazzNam
     * @return
     */
    List<Stu> findByClazzName(String clazzNam);

    /**
     * @Query方法
     * 根据班级名称查询这个班级所有学生的信息
     * ?1此处使用的是参数的位置,代表的是第一个参数
     * 此写法与findByClazz_name方法实现的功能完全一致
     * @param clazzName
     * @return
     */
    @Query("select s from Stu s where s.clazz.name = ?1")
    List<Stu> findStuByClazzName(String clazzName);

    /**
     * 使用@Query注解的形式,查询某个班级下所有学生的姓名和性别
     * @param clazzName
     * @return
     */
    @Query("select new Map(s.name, s.sex) from Stu s where s.clazz.name = ?1")
    List<Map<String, Object>> findNameAndSexByClazzName(String clazzName);

    /**
     * 使用@Query注解的形式,查询某个班级下某种性别的所有学生的姓名
     * 上面方法用的是参数的位置来查询的,spring-data-jpa中还支持用名称来匹配查询,使用格式“:参数名称”引用
     * @param clazzName
     * @param sex
     * @return
     */
    @Query("select s.name from Stu s where s.clazz.name=:clazzName and s.sex=:sex")
    List<String> findNameByClazzNameAndSex(@Param("clazzName")String clazzName, @Param("sex")char sex);

    /**
     * 使用@Query注解的形式,查询某个学生属于哪个班级
     * @param stuName
     * @return
     */
    @Query("select c.name from Clazz c inner join c.stuList s where s.name = ?1")
    String findClazzNameByStuName(String stuName);

    /**
     * 执行更新查询,使用@Query与@Modifying可以执行更新操作
     * 例如根据姓名删除学生
     * @param stuName
     * @return
     */
    @Modifying
    @Query("delete from Stu s where s.name = ?1")
    Integer deleteStuByStuName(String stuName);
}
  1. 从上述代码中可以看出,List<Stu> findByClazz_name(String clazzNam)和findByClazzName(String clazzNam)方法是关联查询的方法,关联的属性可以用下划线“_”链接,或者根据Spring-Data-Jpa命名规范去查询,此方法定义在学生对象的数据访问层接口下,相当于JPQL语句:select s from stu s where s.clazzname = ?1
  2. @Query("select s from Stu s where s.clazz.name = ?1")定义在方法List<Stu> findStuByClazzName(String clazzName)上,与List<Stu> findByClazz_name(String clazzNam)方法的功能是完全一致的,只是这里采用的是@Query注解的形式进行查询。@Query注解中可以直接定义JPQL语句进行数据的访问操作,@Query查询摆脱了像命名查询那样的约束,将查询直接在相应的接口方法上声明,结构更为清晰,这是Spring Data的特有实现,非常方便。JPQL语句中“?1”指代的是取方法形参列表中第1个参数值,1代表的是参数位置,以此类推。
  3. @Query("select new Map(s.name, s.sex) from Stu s where s.clazz.name = ?1")List<Map<String, Object>> findNameAndSexByClazzName(String clazzName);此方法使用JPQL语句直接返回List<Map<String, Object>>对象。
  4. @Query("select s.name from Stu s where s.clazz.name=:clazzName and s.sex=:sex")List<String> findNameByClazzNameAndSex(@Param("clazzName")String clazzName, @Param("sex")char sex)之前的方法是使用参数的位置来获取参数的值的,除此之外,SpringDataJpa还支持用名称来匹配获取参数的值,使用格式为“:参数名称”,如上面方法所示,@Param注解用于声明参数的名称,“:参数名称”,用于提前对应参数的值。
  5. @Query("select c.name from Clazz c inner join c.stuList s where s.name = ?1")String findClazzNameByStuName(String stuName)此方法,实现了关联查询,查询某个学生所属的班级的名称。
  6. @Modifying@Query("delete from Stu s where s.name = ?1")
    Integer deleteStuByStuName(String stuName)在此方法中,SpringDataJpa支持使用@Modifying和@Query注解组合更新查询操作,上述方法即使用了此操作删除了某个学生数据。

4、定义业务层类

在service包下新建一个SchoolService类,代码如下(几个关联查询方法)

import com.mcy.springdatajpa.entity.Clazz;
import com.mcy.springdatajpa.entity.Stu;
import com.mcy.springdatajpa.repository.ClazzRepository;
import com.mcy.springdatajpa.repository.StuRepository;
import com.mcy.springdatajpa.repository.StudentRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SchoolService {
    //注入数据访问层接口对象
    @Resource
    private StuRepository stuRepository;
    @Resource
    private ClazzRepository clazzRepository;

    //事务管理
    @Transactional
    public void saveClazzAll(List<Clazz> clazzes){
        clazzRepository.saveAll(clazzes);
    }

    @Transactional
    public void saveStuAll(List<Stu> stus){
        stuRepository.saveAll(stus);
    }

    public List<Map<String, Object>> getStusByClazzName(String clazzName){
        //使用"-",驼峰式命名和@Query查询方式结果一致
        List<Stu> stus = stuRepository.findByClazz_name(clazzName);
        //List<Stu> stus = stuRepository.findByClazzName(clazzName);
        //List<Stu> stus = stuRepository.findStuByClazzName(clazzName);

        List<Map<String, Object>> results = new ArrayList<>();
        //遍历查询出的学生对象,提取姓名,年龄,性别信息
        for(Stu stu : stus){
            Map<String, Object> s = new HashMap<>();
            s.put("name", stu.getName());
            s.put("age", stu.getAge());
            s.put("sex", stu.getSex());
            results.add(s);
        }
        return results;
    }

    public List<Map<String, Object>> findNameAndSexByClazzName(String clazzName){
        return stuRepository.findNameAndSexByClazzName(clazzName);
    }

    public List<String> findNameByClazzNameAndSex(String clazzName, char sex){
        return stuRepository.findNameByClazzNameAndSex(clazzName, sex);
    }

    public String findClazzNameByStuName(String stuName){
        return stuRepository.findClazzNameByStuName(stuName);
    }

    @Transactional
    public Integer deleteStuByStuName(String stuName){
        return stuRepository.deleteStuByStuName(stuName);
    }
}

在业务层中需要注入数据访问层对象,在上述代码中是通过@Resources注解将StuRepository和ClazzRepository接口对应的实现类对象注入进来的。

5、定义控制器类

在controller包下新建一个StuController类,代码如下(访问方法)。

import com.mcy.springdatajpa.entity.Clazz;
import com.mcy.springdatajpa.entity.Stu;
import com.mcy.springdatajpa.service.SchoolService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/stu")
public class StuController {
    //注入ShcoolService
    @Resource
    private SchoolService schoolService;

    //保存,初始化数据
    @RequestMapping("/save")
    public String save(){
        Clazz clazz1 = new Clazz("软件工程1班");
        Clazz clazz2 = new Clazz("软件工程2班");
        //保存班级对象数据
        List<Clazz> clazzs = new ArrayList<>();
        clazzs.add(clazz1);
        clazzs.add(clazz2);
        schoolService.saveClazzAll(clazzs);

        Stu stu1 = new Stu("张三", "湖北", 20, '男', clazz1 );
        Stu stu2 = new Stu("李四", "湖北", 18, '女', clazz1 );
        Stu stu3 = new Stu("王五", "湖北", 17, '男', clazz2 );

        List<Stu> stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);
        stus.add(stu3);
        schoolService.saveStuAll(stus);
        return "数据保存成功!";
    }

    /**
     * 查询某个班级所有学生的姓名,年龄,性别
     * @param clazzName
     * @return
     */
    @RequestMapping("/getClazzStus")
    public List<Map<String, Object>> getClazzStus(String clazzName){
        return schoolService.getStusByClazzName(clazzName);
    }

    /**
     * 查询某个班级所有学生的姓名,性别
     * @param clazzName
     * @return
     */
    @RequestMapping("/findNameAndSexByClazzName")
    public List<Map<String, Object>> findNameAndSexByClazzName(String clazzName){
        return schoolService.findNameAndSexByClazzName(clazzName);
    }

    /**
     * 查询某个班级某种性别的所有学生的姓名
     * @param clazzName
     * @param sex
     * @return
     */
    @RequestMapping("/findNameByClazzNameAndSex")
    public List<String> findNameByClazzNameAndSex(String clazzName, Character sex){
        return schoolService.findNameByClazzNameAndSex(clazzName, sex);
    }

    /**
     * 查询某个学生属于哪个班级
     * @param stuName
     * @return
     */
    @RequestMapping("/findClazzNameByStuName")
    public String findClazzNameByStuName(String stuName){
        return schoolService.findClazzNameByStuName(stuName);
    }

    /**
     * 删除某个学生对象
     * @param stuName
     * @return
     */
    @RequestMapping("/deleteStuByStuName")
    public String deleteStuByStuName(String stuName){
        return "删除数据:"+schoolService.deleteStuByStuName(stuName);
    }
}

6、测试应用

启动MySQL数据库,在数据库中创建名为jpa的数据库。springboot项目启动后,JPA会在数据库中自动创建持久化类对应的tb_stu和tb_clazz表。

测试添加学生和班级信息,在浏览器中输入http://localhost:8080/stu/save地址,请求会提交给StuController类的save方法进行处理,执行完成返回“数据保存成功!”

查看数据库中的数据如图(StuController类的save方法中数据对应):

测试根据班级名称查询班级的学生信息,在浏览器中输入http://localhost:8080/stu/getClazzStus?clazzName=软件工程1班,请求会提交StuController类的getClazzStus方法进行处理,执行完成后返回查询出的学生姓名和性别,年龄,如图

测试查询某个班级的女学生,在浏览器中输入http://localhost:8080/stu/findNameByClazzNameAndSex?clazzName=软件工程1班&sex=女,请求会提交给StuController类的findNameByClazzNameAndSex方法进行处理,执行完成返回查询出的某个班级的女学生,如图:

测试查询某个学生属于哪个班级,在浏览器中输入http://localhost:8080/stu/findClazzNameByStuName?stuName=张三,请求会提交到StuController类的findClazzNameByStuName方法进行处理,执行完成返回查询出的班级名称,如图

测试删除某个学生对象,在浏览器中输入http://localhost:8080/stu/deleteStuByStuName?stuName=张三,请求会提交给StuController类的deleteStuByStuName方法进行处理,执行完成返回“删除数据:1”,查询数据库如图:

案例代码下载链接:https://github.com/machaoyin/spring-data-jpa

上一篇:Spring-Data-Jpa条件查询

下一篇:Spring-Data-Jpa动态查询(Specification)

发布了112 篇原创文章 · 获赞 223 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/qq_40205116/article/details/103273739