spring-data-jpa的简单使用动态sql分页排序
Posted 落魄实习生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring-data-jpa的简单使用动态sql分页排序相关的知识,希望对你有一定的参考价值。
想直接看案例请直接跳转 五、案例
spring data jpa介绍
Spring Data JPA 是更大的 Spring Data 系列的一部分,可以轻松实现基于 JPA 的存储库。该模块处理对基于 JPA 的数据访问层的增强支持。它使构建使用数据访问技术的 Spring 驱动的应用程序变得更加容易。
实现应用程序的数据访问层已经很麻烦了。必须编写太多样板代码来执行简单的查询以及执行分页和审计。Spring Data JPA 旨在通过将工作量减少到实际需要的数量来显着改进数据访问层的实现。作为开发人员,您编写存储库接口,包括自定义 finder 方法,Spring 将自动提供实现。
官网地址https://spring.io/projects/spring-data-jpa
一、常用的注解
1.@Entity
@Entity 用于定义对象将会成为被 JPA 管理的实体,将字段映射到指定的数据库表中
2.@Table
@Table 用于指定数据库的表名
3.@Id
@Id 定义属性为数据库的主键,一个实体里面必须有一个,并且必须和@GeneratedValue 配合使用和成对出现
4.@GeneratedValue
@GeneratedValue 主键生成策略
public enum GenerationType {
//通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
TABLE,
//通过序列产生主键,通过 @SequenceGenerator 注解指定序列名, mysql 不支持这种方式;
SEQUENCE,
//采用数据库ID自增长, 一般用于mysql数据库
IDENTITY,
//JPA 自动选择合适的策略,是默认选项;
AUTO
}
5.@IdClass
@IdClass 利用外部类的联合主键
作为复合主键类,要满足以下几点要求。
必须实现 Serializable 接口。
必须有默认的 public 无参数的构造方法。
必须覆盖 equals 和 hashCode 方法。equals 方法用于判断两个对象是否相同,EntityManger 通过 find 方法来查找 Entity
时,是根据 equals 的返回值来判断的。本例中,只有对象的 name 和 email 值完全相同时或同一个对象时则返回 true,否
则返回 false。hashCode 方法返回当前对象的哈希码,生成 hashCode 相同的概率越小越好,算法可以进行优化。
6.@Basic
@Basic 表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为 @Basic。
7.@Transient
@Transient 表示该属性并非一个到数据库表的字段的映射,表示非持久化属性。JPA 映射数据库的时候忽略它,与 @Basic 相反的
作用
8.@Column
@Column 定义该属性对应数据库中的列名
不写则默认数据库字段采用下划线命名方式与属性名对应。
例:private String userName; 数据库中则生成user_name
9.@Temporal
@Temporal 用来设置 Date 类型的属性映射到对应精度的字段。
@Temporal(TemporalType.DATE)映射为日期 // date (只有日期)
@Temporal(TemporalType.TIME)映射为日期 // time (是有时间)
@Temporal(TemporalType.TIMESTAMP)映射为日期 // date time (日期+时间)
二、多表设计
数据库中多表之间存在着三种关系
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,
多对一。所以说四种更精确。
多表关联关系注解
@OneToOne、@JoinColumn、@ManyToOne、@ManyToMany、@JoinTable、@OrderBy
1)@JoinColumn 定义外键关联的字段名称
用法:@JoinColumn 主要配合 @OneToOne、@ManyToOne、@OneToMany 一起使用,单独使用没有意义。
2)@OneToOne 一对一关联关系
用法 @OneToOne 需要配合 @JoinColumn 一起使用。注意:可以双向关联,也可以只配置一方
假设一个学生对应一个班级,添加学生的同时添加班级,Student类:
@OneToOne(cascade = CascadeType.PERSIST)
//关联的外键字段
@JoinColumn(name = “grade_id”)
private Grade grade;
grade_id 指的是t_student表中的字段,cascade属性设置级联操作
一对一操作
@Entity
@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;
}
3)@OneToMany 一对多 & @ManyToOne 多对一
例:多个用户拥有一个角色
用户类
@Entity
@Table(name = "t_users")
public class Users {
//1. 一个角色下有多个用户(一对多)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
//多个用户拥有同一个角色
@ManyToOne
@JoinColumn(name = "roles_id")
private Roles roles;
}
角色类
@Entity
@Table(name = "t_roles")
public class Roles {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String rolename;创建RolesRepository接口
测试类
//一个角色被多个用户拥有
//fetch = FetchType.EAGER:立即加载
@OneToMany(mappedBy = "roles",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<Users> users = new HashSet<Users>();
}
4)@OrderBy 关联查询的时候的排序
一般和 @OneToMany 一起使用
例:
@Entity
@Table(name="user")
public class User implements Serializable{
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="user")
@OrderBy("role_name DESC")
private Set<role> setRole;
}
5)@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>();
)
}
6)@ManyToMany 多对多
@ManyToMany 表示多对多,和 @OneToOne、@ManyToOne 一样也有单向双向之分,单项双向和注解没有关系,只看实体类之间是否相互引用。 主要注意的是当用到 @ManyToMany 的时候一定是三张表,不要想着偷懒,否则会发现有很多麻烦
三、方法命名查询
关键字 | 例子 | sql |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound withappended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound withprepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter boundwrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = false |
FALSE | findByActiveFalse() | … where x.firstname like ?1 |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
例:
public interface UserRepository extends JpaRepository<User,Integer> {
//注意:方法的名称必须要遵循驼峰式命名规则。xxxBy(关键字)+属性名称(首字母大写)+查询条件(首字母大写)
/**
* 根据用户名精确查询
* @param userName
* @return
*/
List<User> findByUserName(String userName);
/**
* 模糊查询
* @param userName注意:方法名称除了find的前缀之外,还有以下前缀,具体可查看PartTree类的源码(快速搜索查看类快捷键:ctrl + shift + T)
PartTree类源码如下:
测试类
* @return
*/
List<User> queryByUserNameLike(String userName);
/**
* 根据用户名和地址模糊查询
* @param userName
* @param address
* @return
*/
List<User> findByUserNameLikeAndAddressLike(String userName,String address);
}
四、注解式查询
1.@Query
使用命名查询为实体声明查询是一种有效的方法,对于少量查询很有效。一般只需要关心 @Query 里面的 value 和 nativeQuery的值。使用声明式 JPQL 查询有个好处,就是启动的时候就知道你的语法正确不正确。
nativeQuery =true是使用原生SQL
2.@Param
默认情况下,参数是通过顺序绑定在查询语句上的,这使得查询方法对参数位置的重构容易出错。为了解决这个问题,可以使用@Param 注解指定方法参数的具体名称,通过绑定的参数名字做查询条件,这样不需要关心参数的顺序,推荐这种做法,比较利于代码重构
@Param注解必须提供且注解内容与命名参数一致
/**
* 命名参数查询
* @param username
* @param address
* @return
*/
@Query("from User where userName like :username% and address like %:address%")
List<User> findUserListByParam(@Param("username") String username,@Param("address") String
address);
3.@Modifying修改查询之更新
调用修改删除时需要开启事务@Transactional
@Modifying//修改查询,标记为更新
@Query("update User set userName =?1 where id =?2")
int updateUser(String username,int id);
4.@Modifying修改查询之删除
@Modifying//修改查询,标记为删除
@Query("delete from User where id =?1")
int deleteUser(int id);
顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的
名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询
按照Spring Data JPA 定义的规则,查询方法以 findBy 开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条
件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。
五、案例
1.基本使用
1.引入依赖
//我这里使用的是2.2.5版本
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
2.修改配置文件
server:
port: 8080
spring:
datasource:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_jpa?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
jpa:
hibernate:
ddl-auto: update #创建数据库的方式类型
show-sql: true ##显示sql语句
3.实体类
//@Data是lombok的注解不想写set get可以使用它
@Data
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String userName;
private Integer age;
private String address;
private String sex;
}
4.Repository数据操作层
要继承JpaRepository<User,Integer>接口User是对应的实体类,Integer是实体类主键
public interface UserRepository extends JpaRepository<User,Integer> {
//根据名称模糊查询
List<User> findByUserNameLike(String userName);
List<User> findByAgeGreaterThan(Integer age);
}
5.测试类
使用了spring-boot-starter-test
运行test1就可以添加一条user数据了
@SpringBootTest(classes = JpaApplication.class)
@RunWith(SpringRunner.class)
public class UserTest {
@Resource
private UserRepository userRepository;
@Resource
private UserService userService;
@Test
public void test1(){
User user = new User();
user.setUserName("lisi");
user.setAddress("山东济南");
user.setAge(20);
user.setSex("男");
user.setId(2);
User save = userRepository.save(user);
System.out.println(save);
}
}
2.分页查询
@Test
public void testPage(){
//当前页数
int pageNo=1;
//每页显示多少条
int pageSize=3;
//分页排序
// PageRequest pageRequest = PageRequest.of(pageNo-1, pageSize, Sort.Direction.DESC,"id");
//不使用排序
PageRequest pageRequest = PageRequest.of(pageNo-1, pageSize);
Page<User> page= userRepository.findAll(pageRequest);
System.out.println("当前页码"+page.getNumber());
System.out.println("每页显示数量"+page.getSize());
System.out.println("总数量"+page.getTotalElements());
System.out.println("总页数"+page.getTotalPages());
List<User> content = page.getContent();
content.forEach(item->{
System.out.println(item);
});
}
3.动态SQL
1.修改Repository接口,继承 JpaRepository<User,Integer>,JpaSpecificationExecutor
public interface UserRepository extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
}
2.创建service层
package com.erfou.service;
import com.erfou.entity.User;
import org.springframework.data.domain.Page;
import java.util.List;
/**
* @Author luzhiyong
* @Description 分页查询(支持动态查询+分页查询+排序)
* @Date 2021/7/18 16:39
*/
public interface UserService {
/**
* 动态sql拼接条件,并分页
* @param user
* @return 返回Page对象
*/
Page<User> findAll(User user);
}
实现service接口
package com.erfou.service.impl;
import com.erfou.entity.User;
import com.erfou.repository.UserRepository;
import com.erfou.service.UserService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.List;
/**
* @Author luzhiyong
* @Description
* @Date 2021/7/18 16:39
*/
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserRepository repository;
@Override
public Page<User> findAll(User user) {
PageRequest page = PageRequest.of(0, 10, Sort.Direction.DESC,"id");
Page<User> all = repository.findAll(new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
获取条件参数对象
Predicate predicate = cb.conjunction();
//判断user对象不为空
if (user != null) {
//判断用户名是否为空
if (user.getUserName() != null && !user.getUserName().equals("")) {
//如果用户名不为空则拼接条件 模糊查询
predicate.getExpressions().add(cb.like(root.get("userName"), "%" + user.getUserName() + "%"));
}
if (user.getAge() != null) {
//如果年龄不为空则拼接条件 精确匹配
predicate.getExpressions().add(cb.equal(root.get("age"), user.getAge()));
}
if (user.getAddress() != null) {
//如果地址不为空则拼接条件 模糊查询
predicate.getExpressions().add(cb.like(root.get("address"), "%" + user.getAddress() + "%"));
}
}
return predicate;
}
}, page);
return all;
}
}
测试
@Test
public void test(){
User user = new User();
user.setAge(18);
Page<User> all = userService.findAll(user);
System.out.println(all.getContent());
}
动态SQL参考:https://wangmaoxiong.blog.csdn.net/article/details/89633003
学习资料视频参考自:https://www.bilibili.com/video/BV1W54y167Ub?p=8
感谢以上大佬!!!!!!!
以上是关于spring-data-jpa的简单使用动态sql分页排序的主要内容,如果未能解决你的问题,请参考以下文章
spring-data-jpa和mybatis可以整合在一起使用有啥优缺点