【Spring Data JPA】JpaRepository 增删改查

  • Post author:
  • Post category:其他




引言【必读】

如果你只需要操作单个表的数据,那么只需要在

简单实体操作

里面找需要的操作即可;

如果你操作的是多个表关联的数据,请在

复杂实体操作

里面找需要的操作,切记,一定要看

关联表查询必看



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



简单实体操作

在这里我们创建实体类与继承了

JpaRepository

的接口,下面的增删改查都以实体类与接口做示例。



创建实体类

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

public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
}



增、删、改、查



增加、修改



JpaRepository

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

    @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);
    }



删除



删除【根据实体类删除】

    @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);
    }



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

    @Autowired
    private UserInfoRepository userInfoRepository;

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



查询



简单查询



查询单个信息【findBy】



JpaRepository

中根据某一个字段或者某几个字段查询时,就使用

findBy

方法。

这里给个例子,假设,我想根据

loginName

查询用户信息,就可以用

findByLoginName

查询用户信息,如果有多个条件后面就继续拼接

AndXXX



假设,我想查询

loginName

等于某值,并且

password

等于某值的,就可以使用

findByLoginNameAndPassword

public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
	// 根据登录名查询用户信息
    UserInfo findByLoginName(String loginName);
    // 根据登录名和密码查询用户信息
    UserInfo findByLoginNameAndPassword(String loginName, String password);
}



查询多个信息【findAllBy】



JpaRepository

中根据某一个字段或者某几个字段查询时,就使用

findAllBy

方法,而接口根据某个条件查询写法跟查询单个信息时一样。

这里给个例子,假设,我想查询

loginName

等于某值的所有用户信息时,就写做

findAllByLoginName

public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
	// 查询所有登录名叫做XXX的用户
	List<UserInfo> findAllByLoginName(String loginName);
}



查询多个信息并倒序排序【findAllBy + Order】



JpaRepository

中根据某一个字段或者某几个字段查询时,就使用

findAllBy

方法,而接口根据某个条件查询写法跟查询单个信息时一样。

这里给个例子,假设,我想查询

loginName

等于某值的所有用户信息时,就写做

findAllByLoginName

public interface UserInfoRepository extends JpaRepository<UserInfo, String> {
	// 查询所有登录名叫做XXX的用户,并把年龄倒序排序
	List<UserInfo> findAllByLoginNameOrderAgeDesc(String loginName);
}



JPA 函数查询表格

更多的 JPA 函数查询,请参考这篇文章


Spring Data JPA 常用查询方法



复杂查询



UserInfoRepository

中创建一个方法,使用

Specification



Pageable

去做复杂查询以及分页。

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);
}


查询等于某个条件的数据并分页
@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】
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();
};


大于等于【>=】
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();
};


大于【>】
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();
};


小于等于【<=】
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();
};


小于【<】
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】
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 学生年龄



实体创建



学校表实体:

@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

代表关联表的关联字段(关联表在这里代表学生表);

如果你是多个字段进行关联,请参考下面的使用方法:

@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;



学生表实体:

@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 接口

public interface SchoolRepository extends JpaRepository<School, String> {
}



查询



简单查询



查询单个信息【findBy】



JpaRepository

中根据某一个字段或者某几个字段查询时,就使用

findBy

方法。

这里给个例子,假设,我想根据

schoolName

查询学校信息并查询跟这个学校关联的学生信息,就可以用

findBySchoolName

查询息,如果有多个条件后面就继续拼接

AndXXX



假设,我想查询

schoolName

等于某值,就可以使用

findBySchoolName

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

就这样,你直接调用

SchoolRepository

接口的

findBySchoolName

方法就可以查询到学校名称等于XX的学校信息和关联该学校的所有学生信息;



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

如果你的查询条件是需要查询关联表的字段的时候就需要使用

Specification

,下面是关联表查询条件的用法:

首先,在做各种的查询之前,先把

Specification

的基本代码写出来,是下面的例子都是围绕我给出代码的

查询条件位置...

去变化,其他位置不再变化,这里的

Specification<School>

泛型中的对象

School

,根据我上述给出的数据库和实体模型,可以得出这里相当于是把

school

做为了主表,接下来需要使用查询

student

表中字段时,我会特意标注,代码如下:

@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

表,具体查询代码如下:

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 + >=】
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】
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】
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();
	}
};



关联表分页查询条件

@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();// 总共多少页
}



关联表查询必看

当我们查询后,如果在实体内定义了使用

懒加载

进行查询,在实际开发中会有一些问题,先定义懒加载的代码:

@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)



代码如下:

@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



版权声明:本文为weixin_43657300原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。