【Spring Data JPA】基于 JpaRepository 增删改查

引言【必读】

如果你只需要操作单个表的数据,那么只需要在 简单实体操作 里面找需要的操作即可; 如果你操作的是多个表关联的数据,请在 复杂实体操作 里面找需要的操作,切记,一定要看 关联表查询必看

如果文章中出现了错误,请在评论区指正,感谢!

简单实体操作

在这里我们创建实体类与继承了JpaRepository的接口,下面的增删改查都以实体类与接口做示例。

创建实体类

java 复制代码
import java.io.Serializable;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.Column;

@Data
@Entity
@Table(name="user_info")
public class UserInfo implements Serializable {
	
	private static final long serialVersionUID = 1L;

    /**
     * id
     */
    @Id
    private String id;

    /**
     * 登录名
     */
    @Column(name = "login_name")
    private String loginName;

    /**
     * 密码
     */
    private String password;

    /**
     * 年龄
     */
    private int age;

}

定义JPA查询接口

定义一个接口,继承JpaRepository

java 复制代码
public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
}

增、删、改、查

增加、修改

JpaRepository中,当保存的实体类主键ID在数据库中存在时进行修改操作,不存在则进行保存。

java 复制代码
    @Autowired
    private UserInfoRepository userInfoRepository;

    public void addUserInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId("jHfnKlsCvN");
        userInfo.setLoginName("登录名");
        userInfo.setPassword("123456");
        userInfo.setAge(18);
        // 保存或修改用户信息, 并返回用户实体类
        UserInfo save = userInfoRepository.save(userInfo);
    }

删除

删除【根据实体类删除】

java 复制代码
    @Autowired
    private UserInfoRepository userInfoRepository;

    public void deleteUserInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId("jHfnKlsCvN");
        userInfo.setLoginName("登录名");
        userInfo.setPassword("123456");
        userInfo.setAge(18);
        // 根据实体类删除
        userInfoRepository.delete(userInfo);
    }

删除【根据实体类主键删除】

java 复制代码
    @Autowired
    private UserInfoRepository userInfoRepository;

    public void deleteUserInfo() {
        // 根据实体类主键删除
        userInfoRepository.deleteById("111");
    }

查询

简单查询

查询单个信息【findBy】

JpaRepository中根据某一个字段或者某几个字段查询时,就使用findBy方法。 这里给个例子,假设,我想根据loginName查询用户信息,就可以用findByLoginName查询用户信息,如果有多个条件后面就继续拼接AndXXX。 假设,我想查询loginName等于某值,并且password等于某值的,就可以使用findByLoginNameAndPassword

java 复制代码
public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
	// 根据登录名查询用户信息
    UserInfo findByLoginName(String loginName);
    // 根据登录名和密码查询用户信息
    UserInfo findByLoginNameAndPassword(String loginName, String password);
}
查询多个信息【findAllBy】

JpaRepository中根据某一个字段或者某几个字段查询时,就使用findAllBy方法,而接口根据某个条件查询写法跟查询单个信息时一样。 这里给个例子,假设,我想查询loginName等于某值的所有用户信息时,就写做findAllByLoginName

java 复制代码
public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
	// 查询所有登录名叫做XXX的用户
	List<UserInfo> findAllByLoginName(String loginName);
}
查询多个信息并倒序排序【findAllBy + Order】

JpaRepository中根据某一个字段或者某几个字段查询时,就使用findAllBy方法,而接口根据某个条件查询写法跟查询单个信息时一样。 这里给个例子,假设,我想查询loginName等于某值的所有用户信息时,就写做findAllByLoginName

java 复制代码
public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
	// 查询所有登录名叫做XXX的用户,并把年龄倒序排序
	List<UserInfo> findAllByLoginNameOrderAgeDesc(String loginName);
}
JPA 函数查询表格

更多的 JPA 函数查询,请参考这篇文章 Spring Data JPA 常用查询方法

复杂查询

UserInfoRepository中创建一个方法,使用SpecificationPageable 去做复杂查询以及分页。

java 复制代码
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
	// 用于复杂查询
    Page<UserInfo> findAll(Specification<UserInfo> specification, Pageable pageable);
}
查询等于某个条件的数据并分页
java 复制代码
@Autowired
private UserInfoRepository userInfoRepository;
    
public void queryUserInfo() {
	Pageable pageable = org.springframework.data.domain.PageRequest.of(Math.toIntExact(num - 1), pageSize);
	Specification<UserInfo> specifications = new Specification<UserInfo>() {
		@Override
		public Predicate toPredicate(Root<UserInfo> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
			List<Predicate> predicates = new ArrayList<>();
			// 条件【查询所有登录名等于张三的用户】
			predicates.add(criteriaBuilder.equal(root.get("loginName"), "张三"));
			return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
		}
	};

	// JAVA8 lambda写法
//	Specification<UserInfo> specifications = (root, query, criteriaBuilder) -> {
//            List<Predicate> predicates = new ArrayList<>();
			// 条件【查询所有登录名等于张三的用户】
//			predicates.add(criteriaBuilder.equal(root.get("loginName"), "张三"));
//            return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
//	};

	final Page<UserInfo> planPage = userInfoReportRepository.findAll(specifications, pageable);
	long totalElements = planPage.getTotalElements();// 总共多少条数据
	int totalPages = planPage.getTotalPages();// 总共多少页
}
模糊查询【like】
java 复制代码
Specification<UserInfo> specification = (root, query, criteriaBuilder) -> {
	List<Predicate> predicates = new ArrayList<>();
	// 条件【查询所有登录名等于张三的用户】
	predicates.add(criteriaBuilder.like(root.get("loginName"), "%张%"));
	return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
};
大于等于【>=】
java 复制代码
Specification<UserInfo> specification = (root, query, criteriaBuilder) -> {
	List<Predicate> predicates = new ArrayList<>();
	// 条件【查询大于等于10岁的用户】
	predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("age"), 10));
	return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
};
大于【>】
java 复制代码
Specification<UserInfo> specification = (root, query, criteriaBuilder) -> {
	List<Predicate> predicates = new ArrayList<>();
	// 条件【查询大于10岁的用户】
	predicates.add(criteriaBuilder.greaterThan(root.get("age"), 10));
	return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
};
小于等于【<=】
java 复制代码
Specification<UserInfo> specification = (root, query, criteriaBuilder) -> {
	List<Predicate> predicates = new ArrayList<>();
	// 条件【查询小于等于20岁的用户】
	predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("age"), 20));
	return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
};
小于【<】
java 复制代码
Specification<UserInfo> specification = (root, query, criteriaBuilder) -> {
	List<Predicate> predicates = new ArrayList<>();
	// 条件【查询小于20岁的用户】
	predicates.add(criteriaBuilder.lessThan(root.get("age"), 20));
	return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
};
查询多个Id的用户【In】
java 复制代码
List<String> ids = new ArrayList();
ids.add("1");
ids.add("2");

Specification<UserInfo> specification = (root, query, criteriaBuilder) -> {
	List<Predicate> predicates = new ArrayList<>();
	// 条件【查询多个Id】
	CriteriaBuilder.In<String> in = criteriaBuilder.in(root.get("id"));
	for (String id : ids) {
		in.value(id);
    }
	return query.where(criteriaBuilder.and(in)).getGroupRestriction();
};

toPredicate方法参数说明:

  • Root:主要用于处理实体和字段、实体与实体之间的关系,还可以做join操作;
  • CriteriaQuery:主要用于对查询结果的处理,有groupBy、orderBy、having、distinct等操作;
  • CriteriaBuilder:主要是各种条件查询,就像刚刚的equal(等于)操作等;

复杂实体操作

示例表

这里有两张表,分别是School学校表和Student学生表; School学校表中有:

字段名 备注
id id(主键)
school_name 学校名称

Student学生表中有:

字段名 备注
id id(主键)
school_id 学校id(关联school表主键)
student_name 学生名
age 学生年龄

实体创建

学校表实体:

java 复制代码
@Data
@Entity
@Table(name="school")
public class School implements Serializable {

    /**
     * id
     */
    @Id
    private String id;

    /**
     * 学校名
     */
    @Column(name = "school_name")
    private String schoolName;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "id", referencedColumnName = "school_id", insertable = false, updatable = false)
	private Set<Student> students;

}

这里的 @OneToMany 代表学校表一条信息关联学生表多条信息的意思,里面的 cascade 是操作类型的意思,fetch 的意思是当查询是关联表的信息是一次性查出来还是当使用的时候查询,其实就是懒汉式和饿汉式加载,如果使用了懒加载,一定要在使用这个实体对象的方法上加上 @Transactional(readOnly = true),加上该注解是为了保证数据库 Session 不断开,具体的解释请看 关联表查询必看@JoinColumn 是关联条件的意思,name 代表当前表的字段名称(当前表在这里代表学校表),referencedColumnName 代表关联表的关联字段(关联表在这里代表学生表); 如果你是多个字段进行关联,请参考下面的使用方法:

java 复制代码
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumns({
	@JoinColumn(name = "id", referencedColumnName = "school_id", insertable = false, updatable = false),
	@JoinColumn(name = "当前表字段名称", referencedColumnName = "关联表字段名称", insertable = false, updatable = false)
})
private Set<Student> students;

学生表实体:

java 复制代码
@Data
@Entity
@Table(name="student")
public class Student implements Serializable {

    /**
     * id
     */
    @Id
    private String id;

    /**
     * 关联学校Id
     */
    @Column(name = "school_id")
    private String schoolId;

    /**
     * 学生名
     */
    @Column(name = "student_name")
    private String studentName;

    /**
     * 学生年龄
     */
    @Column(name = "age")
    private Integer age;

}

定义 JpaRepository 接口

java 复制代码
public interface SchoolRepository extends JpaRepository<School, String> {
}

查询

简单查询

查询单个信息【findBy】

JpaRepository中根据某一个字段或者某几个字段查询时,就使用findBy方法。 这里给个例子,假设,我想根据schoolName查询学校信息并查询跟这个学校关联的学生信息,就可以用findBySchoolName查询息,如果有多个条件后面就继续拼接AndXXX。 假设,我想查询schoolName等于某值,就可以使用findBySchoolName

java 复制代码
public interface SchoolRepository extends JpaRepository<School, String> {
	// 根据学校名查询学校信息,并查询出关联的学生信息
    School findBySchoolName(String schoolName);
}

就这样,你直接调用SchoolRepository接口的findBySchoolName方法就可以查询到学校名称等于XX的学校信息和关联该学校的所有学生信息;

复杂查询【JPA 核心思想是操作对象等同于操作数据库】

如果你的查询条件是需要查询关联表的字段的时候就需要使用 Specification,下面是关联表查询条件的用法:

首先,在做各种的查询之前,先把 Specification 的基本代码写出来,是下面的例子都是围绕我给出代码的 查询条件位置... 去变化,其他位置不再变化,这里的 Specification<School> 泛型中的对象 School,根据我上述给出的数据库和实体模型,可以得出这里相当于是把 school 做为了主表,接下来需要使用查询 student 表中字段时,我会特意标注,代码如下:

java 复制代码
@Autowired
private SchoolRepository schoolRepository;
    
public void querySchool() {
	// 将school做为主表进行查询
	Specification<School> specifications = new Specification<School>() {
		@Override
		public Predicate toPredicate(Root<School> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
			List<Predicate> predicates = new ArrayList<>();
			// 查询条件位置...
			return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
		}
	};

	// JAVA8 lambda写法
//	Specification<School> specifications = (root, query, criteriaBuilder) -> {
//            List<Predicate> predicates = new ArrayList<>();
			// 查询条件位置...
//            return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
//	};

	final List<School> schoolList = userInfoReportRepository.findAll(specifications);
}

关联表查询条件

模糊查询子表字段【like】

这里给出一个需求,方便使用我想查询学生姓张的学校和学生信息,就需要查询 student 表的 student_name 字段,但是在JPA中,我是以 school 表做为主表进行查询的,所以就需要进行 LEFT JOIN 左关联 student 表进行查询,那么在 Specification 的用法中,是以你在实体模型中如何定义去使用,就拿下面的代码来说,root.join("students", JoinType.LEFT) 就相当于左关联 student 表,这里的 students,注意后面有一个 s,你别以为我写错了,是因为在 School 实体模型中关联的 Student 实体对象的名称就叫 students,忘记的同学可以上去瞄一眼,得到一个 join 对象,这个 join 对象就相当于是 student 表,后续如何使用 join 对象就相当于如何操作 student 表,具体查询代码如下:

java 复制代码
Specification<School> specifications = new Specification<School>() {
	@Override
	public Predicate toPredicate(Root<School> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
		// 查询条件集合
		List<Predicate> predicates = new ArrayList<>();
		/**
		 * root.join("students", JoinType.LEFT) 就相当于左关联 student 表
		 * 注意:这里的 students 没写错,在 School 实体模型中关联 Student 模型的字段就叫 students
		 * 操作 join 对象就相当于操作 student 表
		 */
		Join<Object, Object> join = root.join("students", JoinType.LEFT);
		
		/**
		 * join.get("studentName") 相当于查询 student 表中 student_name 字段的数据
		 * 注意:这里的 studentName 在 Student 实体模型中所对应的就是 student_name 字段
		 * criteriaBuilder.like 是规定了使用 like 查询(模糊查询)
		 */
		predicates.add(criteriaBuilder.like(join.get("studentName"), "张%"));
		
		return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
	}
};
模糊 + 大于查询子表字段【like + >=】
java 复制代码
Specification<School> specifications = new Specification<School>() {
	@Override
	public Predicate toPredicate(Root<School> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
		// 查询条件集合
		List<Predicate> predicates = new ArrayList<>();
		/**
		 * root.join("students", JoinType.LEFT) 就相当于左关联 student 表
		 * 注意:这里的 students 没写错,在 School 实体模型中关联 Student 模型的字段就叫 students
		 * 操作 join 对象就相当于操作 student 表
		 */
		Join<Object, Object> join = root.join("students", JoinType.LEFT);
		
		/**
		 * join.get("studentName") 相当于查询 student 表中 student_name 字段的数据
		 * 注意:这里的 studentName 在 Student 实体模型中所对应的就是 student_name 字段
		 * criteriaBuilder.like 是规定了使用 like 查询(模糊查询)
		 */
		// 姓张的同学
		predicates.add(criteriaBuilder.like(join.get("studentName"), "张%"));
		// 学生的年龄大于 18 岁
		predicates.add(criteriaBuilder.greaterThan(join.get("age"), 18));
		
		return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
	}
};
使用 in 查询子表字段【in】
java 复制代码
Specification<School> specifications = new Specification<School>() {
	@Override
	public Predicate toPredicate(Root<School> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
		// 查询条件集合
		List<Predicate> predicates = new ArrayList<>();
		
		/**
		 * root.join("students", JoinType.LEFT) 就相当于左关联 student 表
		 * 注意:这里的 students 没写错,在 School 实体模型中关联 Student 模型的字段就叫 students
		 * 操作 join 对象就相当于操作 student 表
		 */
		Join<Object, Object> join = root.join("students", JoinType.LEFT);
		
		/**
		 * join.get("studentName") 相当于查询 student 表中 student_name 字段的数据
		 * criteriaBuilder.like 是规定了使用 in 查询
		 * studentNameIn 对象就是你接下来要操作的数据对象
		 */
		CriteriaBuilder.In<String> studentNameIn = criteriaBuilder.in(join.get("studentName"));
		
		// 这里就当于 sudent_name in (张三, 李四, 王五)
		studentNameIn.value("张三");
		studentNameIn.value("李四");
		studentNameIn.value("王五");
		
		// 注意:别忘了把你的查询条件放到条件集合中
		predicates.add(studentNameIn);
		
		return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
	}
};
模糊查询主表字段,in 查询子表字段【like + in】
java 复制代码
Specification<School> specifications = new Specification<School>() {
	@Override
	public Predicate toPredicate(Root<School> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
		// 查询条件集合
		List<Predicate> predicates = new ArrayList<>();
		// 模糊查询学校前缀名叫 山东 的
		predicates.add(criteriaBuilder.like(root.get("schoolName"), "山东%"));
		
		/**
		 * root.join("students", JoinType.LEFT) 就相当于左关联 student 表
		 * 注意:这里的 students 没写错,在 School 实体模型中关联 Student 模型的字段就叫 students
		 * 操作 join 对象就相当于操作 student 表
		 */
		Join<Object, Object> join = root.join("students", JoinType.LEFT);
		
		/**
		 * join.get("studentName") 相当于查询 student 表中 student_name 字段的数据
		 * criteriaBuilder.like 是规定了使用 in 查询
		 * studentNameIn 对象就是你接下来要操作的数据对象
		 */
		CriteriaBuilder.In<String> studentNameIn = criteriaBuilder.in(join.get("studentName"));
		
		// 这里就当于 sudent_name in (张三, 李四, 王五)
		studentNameIn.value("张三");
		studentNameIn.value("李四");
		studentNameIn.value("王五");
		
		// 注意:别忘了把你的查询条件放到条件集合中
		predicates.add(studentNameIn);
		
		return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
	}
};

关联表分页查询条件

java 复制代码
@Autowired
private SchoolRepository schoolRepository;

@Transactional(readOnly = true)
public void queryUserInfo() {
	Pageable pageable = org.springframework.data.domain.PageRequest.of(Math.toIntExact(num - 1), pageSize);
	Specification<School> specifications = new Specification<UserInfo>() {
		@Override
		public Predicate toPredicate(Root<School> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
			List<Predicate> predicates = new ArrayList<>();
			// 左关联 student 表
			Join<Object, Object> join = root.join("students", JoinType.LEFT);
			// 模糊查询 student 表中的 student_name 字段
			predicates.add(criteriaBuilder.like(join.get("studentName"), "张%"));
			return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getGroupRestriction();
		}
	};

	final Page<School> planPage = schoolRepository.findAll(specifications, pageable);
	long totalElements = planPage.getTotalElements();// 总共多少条数据
	int totalPages = planPage.getTotalPages();// 总共多少页
}

关联表查询必看

当我们查询后,如果在实体内定义了使用 懒加载 进行查询,在实际开发中会有一些问题,先定义懒加载的代码:

java 复制代码
@Data
@Entity
@Table(name="school")
public class School implements Serializable {

    /**
     * id
     */
    @Id
    private String id;

    /**
     * 学校名
     */
    @Column(name = "school_name")
    private String schoolName;

	// fetch = FetchType.LAZY 就相当于使用懒加载查询关联表数据
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "id", referencedColumnName = "school_id", insertable = false, updatable = false)
	private Set<Student> students;

}

@Data
@Entity
@Table(name="student")
class Student implements Serializable {

    /**
     * id
     */
    @Id
    private String id;

    /**
     * 关联学校Id
     */
    @Column(name = "school_id")
    private String schoolId;

    /**
     * 学生名
     */
    @Column(name = "student_name")
    private String studentName;

    /**
     * 学生年龄
     */
    @Column(name = "age")
    private Integer age;

}

下面给一个查询示例代码,这里查询一下 school 表种的数据,得到多个 School 实体对象,这时候如果我们想用 School 实体对象中的 Student 对象,也就是想要关联查询 student 表的数据就需要在方法上加上 @Transactional(readOnly = true),如果不加 @Transactional(readOnly = true) 会出现一个 Session 中断的错误,其实就是因为使用了懒加载,JPA 在查询主表信息后,并没有查询关联的子表信息,而这时连接数据库的 Session 断掉了,就会有这个问题出现,其实解决方法有两种,一种是不使用懒加载,一种就是我刚刚说的,在方法上加上 @Transactional(readOnly = true); 代码如下:

java 复制代码
@Autowired
private SchoolRepository schoolRepository;

@Transactional(readOnly = true)
public void queryUserInfo() {
	// 查询 School 数据
	final List<School> schoolList = schoolRepository.findAll();
	for(School school : schoolList) {
		// 查询 Student 数据
		Set<Student> studentList = school.getStudents();
	}	
}

End

相关推荐
千叶寻-23 分钟前
正则表达式
前端·javascript·后端·架构·正则表达式·node.js
小咕聊编程1 小时前
【含文档+源码】基于SpringBoot的过滤协同算法之网上服装商城设计与实现
java·spring boot·后端
追逐时光者8 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_8 小时前
敏捷开发流程-精简版
前端·后端
苏打水com8 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧9 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧9 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧10 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧10 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧10 小时前
Spring Cloud Gateway详解与应用实战
后端