文章目录
说明: spring-data-jpa 为spring-data项目的一个子项目,而spring-data是spring的一个整合日常常用的数据库等数据持久化的一个子项目。
spring-data执行的持久化方式常用的有
- spring Data JPA: 一款遵循JPA规范的框架,其中Hibernate就是Jpa的一种实现
- spring Data MongoDB: 访问mongodb数据库的
- spring Data Redis: 访问redis的
- spring Data Couchbase: 访问Couchbase的
- spring Data Elasticsearch:访问ES的
- spring Data Neo4j:访问Neo4j的
更多支持查看(英)
JPA: 为POJO提供持久化标准规范,能够脱离容器独立运行,很方便开发和测试。能动态切换数据库
Spring Data JPA简介
- spring-data-jpa 底层使用的是Hibernate实现的,从包引用就可以看出。
- spring-data-jpa 1.8.x+ 底层依赖Hibernate5.X
- 例子案例 spring-data-jpa 版本为1.11.6 Mysql5.7
- 因其底层使用的是hibernate故其支持Hibernate的HQL语句
接口相关
类图
说明: spring-data-jpa提供了很多Reposity接口以供使用。 如下:
Repository
Repository 为标记接口(空接口),目的是为了统一所有得Repository得实现。其提供了根据 @Query 来进行查询 和 根据方法名称进行规则查询如下:
根据方法名来查询
根据方法名来查询有局限性。其只能做单表查询,且查询的结果一定是实体对象。毕竟其只是 InvocationHandler 的实现。
命名规则: findby+字段条件(eg:Pwd)+关键字(eg:And)+…(字段条件) + Orderby +排序关键字+排序方向
关键字 | 方法例子 | SQL子句 |
---|---|---|
And | findByIdAndName | where id= ? and Name = ? |
Or | findByNameOrAge | where name = ? or Age = ? |
Between | findByAgeBetween | where age = ? between ? |
LessThan | findBySalaryLessThan | where salary < ? |
LessThanEqual | findBySalaryLessThanEqual | where salary <= ? |
GreaterThan | findBySalaryGreaterThan | where salary > ? |
GreaterThanEqual | findBySalaryGreaterThanEqual | where salary >= ? |
After | findBySalaryAfter | where salary > ? |
Before | findBySalaryBefore | where salary < ? |
IsNull | findByNameIsNull | where name is null |
IsNotNull | findByNameIsNotNull | where name is not null |
IsNot,Not | findByAgeIsNot,findByNameNot | where age <> ? |
Like | findByNameLike | where name like ? |
NotLike | findByNameNotLike | where name not like ? |
StartingWith,StartsWith | findByNameStartingWith,findByNameStartsWith | where name like ‘?%’ |
EndingWith,EndsWith | findByNameEndingWith,findByNameEndsWith | where name like ‘%?’ |
Containing,Contains | findByNameContaining,findByNameContains | where name like ‘%?%’ |
OrderBy | findByOrderBySalary | where order by ? asc |
OrderBy?Asc | findByOrderBySalaryAsc | where order by ? asc |
In | findByIdIn | where id in (?) |
NotIn | findByIdNotIn | where id not in (?) |
True | findByIsTopTrue | where is_top = 1 |
False | findByIsTopFalse | where is_top = 0 |
根据 @Query 来进行查询
当方法上面有 @Query 注解得时候会使得 方法命名规则 失效,Query注解中得value可以填写 JPQL 语句来进行查询;
JPQL语句和Hibernate的对象查询语句HQL类似(格式:Select/update/delete … from … where … group by … having … order by … asc/desc
);
其在传参的时候,可以使用变量的方式进行对象封装。其只能进行查询,若想进行CRUD操作需要添加 @Transactional @Modifying 注解
我们可以通过 nativeQuery 来进行SQL查询。这样查询效率会比JPQL快,因为少了一个语法转换.CRUD操作需要加上 @Transactional @Modifying 注解
//新增: 使用SQL语句 或者使用 CrudRepository的save|saveALL
@Transactional
@Modifying
@Query(value = "insert into dept (`name`,`order_no`,`is_top`) values(:name,:orderNo,0)",nativeQuery = true)
int addDept(@Param("name") String name,@Param("orderNo") Integer orderNo);
//删除
@Transactional
@Modifying
@Query(value = "delete from dept where name=:name AND order_no =:orderNo ",nativeQuery = true)
int removeDept(@Param("name") String name,@Param("orderNo") Integer orderNo);
//改
@Transactional
@Modifying
@Query("update Dept d set d.name=:name where d.id=:id")
int changeNameById(@Param("name") String name,@Param("id") Integer id);
//多表查询
//注意点: 所有的表名应都是用的bean对象
//返回的若为对象 需要写上对象的 new 全路径() 并且这个对象有这个全参的创建方法
@Query(value = "select new com.example.demo5datajpa.domain.UserInfo(u.id,u.name,u.age,d.name AS deptName)"
+" from User AS u,Dept AS d,UserDept AS ud WHERE u.id=:id AND u.id=ud.userId AND ud.deptId = d.id")
List<UserInfo> queryNameInfo(@Param("id") Integer id);
//查询部分字段
@Query(value = "select new com.example.demo5datajpa.domain.UserInfo(u.id,u.name,u.age,d.name AS deptName)"
+" from User AS u,Dept AS d,UserDept AS ud WHERE u.id=:id AND u.id=ud.userId AND ud.deptId = d.id" )
List<UserInfo> queryNameInfo(@Param("id") Integer id);
/**
条件 new UserInfo() 的构造函数决定其是否是指定字端的查询
**/
CrudRepository
CrudRepository 其提供基础的操作方法例如 save|saveAll|findById|findAll|findAllById|count|deleteById
等,其没有定义独立的更新方法,
调用新增数据方法(save)的时候,底层会根据方法参数决定是新增数据还是更新数据。因为底层是Hibernate,Hibernate中有方法saveOrUpdate,而spring-data-jpa中提供的save方法底层使用的就是Hibernate中的saveOrUpdate方法。判断依据,就是数据是否存在,就是数据对象的id是否存在。
方法列表
-
save(…) – 保存
-
saveAll(…)- 多个对象。这里,我们可以传多个对象批量保存。
-
findOne(…) – 根据传入的主键值获取单一对象。
-
findAll(…) – 查询数据库中所有对象。
-
count() – 返回表中记录总数。
-
delete(…) – 根据传入的对象删除记录。
-
deleteById(…) - 根据传入的id去删除
-
exists(…) – 根据传入的主键值判断对象是否存在。
PagingAndSortingRepository
PagingAndSortingRepository 其是提供了分页和排序查询的接口(注意其不提供有条件的分页和排序)
//page -1 单个order by 操作
PageRequest request = PageRequest.of(1, 9, Sort.Direction.DESC,"id");
//多个
Sort.Order order1 = new Sort.Order(Sort.Direction.ASC,"orderNo");
Sort.Order order2 = new Sort.Order(Sort.Direction.DESC, "id");
Sort sort = Sort.by(order1, order2);
//分页操作
PageRequest request = PageRequest.of(1, 9,sort);
Page<Dept> all = deptRepository.findAll(request);
System.out.println("总页数:"+all.getTotalPages());
System.out.println("总条数:"+all.getTotalElements());
System.out.println("当前页条数:"+all.getNumber());
System.out.println("当前内容"+Arrays.toString(all.getContent().toArray()));
如上例描述的一样,其分页需要通过 PageReqeust.of(pageNumber -1, pageSize) 去构造 Pageable 对象去进行分页及排序,其重要属性分别有如下几个:
总页数 | 总条数 | 当前页条数 | 当前页内容集 |
---|---|---|---|
getTotalPages() | getTotalElements() | getNumber() | getContent() |
在使用 分页时得注意如下几点:
- pageNumber 为当前查询页码 -1
- 与 mybatis 著名得插件 PageHelper 不同得是其查询页数如果超过最大页数,getContent() 方法将会是 null.而 PageHelper 默认会将最后一页得数据返回
- Sort 排序 如果只有一个参数可以使用
PageRequest.of(1, 9, Sort.Direction.DESC,"id")
如果是多个 则需要构建多个Order
内部类(见上例或测试类) - 其排序字段并非为 mysql 得表字段而是对应得 Bean 对象的属性,如上述的
orderNO
如果按照 mysql 的表字段其应为order_no
可此处不应如此
方法列表
- Iterable findAll(Sort sort); - 排序
- Page findAll(Pageable pageable); - 分页查询(含排序功能)
JpaRepository
JpaRepository 提供了JPA的相关的方法。
方法列表
void flush(); - 执行缓存与数据库同步,因为 执行persist()、merger()时(实体五状态),数据并不是立即写入数据库中,而是由JPA缓存起来,在执行flush()时写入。在事务提交的时候,JPA会自动执行flush()一次性保存所有数据。
T saveAndFlush(T entity); - 检查id是否存在存在则更新否则新增
void deleteInBatch(Iterable entities); - 删除一个实体集合
JpaSpecificationExecutor
其提供了 Criteria 查询,不过这个接口并不是 Reposotory 接口的实现。故若想使用这个接口必须同时继承 Repository 里面的方法
public interface DeptRepository extends JpaRepository<Dept,Integer>, JpaSpecificationExecutor<Dept> {
}
打开 JpaSpecificationExecutor 接口可以看到如下方法
Optional<T> findOne(@Nullable Specification<T> var1);
List<T> findAll(@Nullable Specification<T> var1);
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
long count(@Nullable Specification<T> var1);
从上诉的方法接口中可以看出需要创建一个 Specification 对象,Specification 类的实现需继承toPredicate如下:
Specification<Dept> queryDeptSpec = new Specification<Dept>(){
@Override
public Predicate toPredicate(Root<Dept> root,
CriteriaQuery<?> criteriaQuery,
CriteriaBuilder criteriaBuilder) {
ArrayList<Predicate> list = new ArrayList<>();
//root 查询的实体类
// path 为查询的字段的路劲
Expression<Integer> orderNo = root.get("orderNo").as(Integer.class);
//CriteriaBuilder 构造查询条件
Predicate predicate1 = criteriaBuilder.gt(orderNo, 5);
list.add(predicate1);
Expression<Boolean> isTop = root.get("isTop").as(boolean.class);
Predicate predicate2 = criteriaBuilder.equal(isTop, false);
list.add(predicate2);
return criteriaBuilder.and(list.toArray(new Predicate[list.size()]));
}
};
//分页 排序
PageRequest pageRequest = PageRequest.of(0, 3, Sort.Direction.DESC, "id");
Page<Dept> all = deptRepository.findAll(queryDeptSpec, pageRequest);
其有Root ,CriteriaQuery<?> , CriteriaBuilder 三个对象,root为我们查询的bean对象的属性,CriteriaBuilder包含 exists、and、or、not等方法
使用 criteriaQuery 去查
Expression<Integer> orderNo = root.get("orderNo").as(Integer.class);
//CriteriaBuilder 构造查询条件
criteriaQuery.where(criteriaBuilder.gt(orderNo, 5));
Expression<Boolean> isTop = root.get("isTop").as(boolean.class);
criteriaQuery.where(criteriaBuilder.equal(isTop, false));
return criteriaQuery.getRestriction();
注解相关
@GeneratedValue(JPA注解)
public class Dept {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
@GeneratedValue 用于标注主键的生成策略,通过strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:
在javax.persistence.GenerationType中定义了以下几种可供选择的策略:
- IDENTITY:采用数据库id自增长的方式来自增主键字段,Oracle 不支持这种方式;
- AUTO: JPA自动选择合适的策略,是默认选项;
- SEQUENCE:通过序列产生主键,通过@SequenceGenerator 注解指定序列名,MySql不支持这种方式
- TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
@Column(JPA注解)
@Column(name = "salary",nullable = false,precision = 10,scale = 2)
private BigDecimal salary;
@Column 用于类对应的表的字段的描述,其属性值含义分别如下:
- name 对应的数据库的字段名
- nullable 该字段是否可以为空 true|flase
- length 字段长度
- percision和scalle 用于double percision 标识字段的总长度,scale标识小数点的位数
@Entity(JPA注解)
@Entity
@Table(name = "USER")
public class User {}
将该实体对象映射到数据库表中
@Id(JPA注解)
@Id
private Integer id;
主键标记
@Transient(JPA注解)
用于标记与数据表映射的对象实体中,业务性属性。该标记用的比较少,一般映射的实体对象不会做更改,如有更改需求会做VO、BO等领域对象的创建。
@Entity
@Table(name = "USER")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
//用户表是不存在deptNames 这个字段的
@Transient
List<String> deptNames;
}
@Temporal(JPA注解)
用于向数据库映射 Date 属性时来调整映射精度。有 DATE(日期)、TIME(事件) 、 TIMESTAMP(日期加事件)三种实现
@Column(name = "create_time_stamp")
@Temporal(TemporalType.TIMESTAMP)
private Date createTimeStamp;
@Column(name = "create_date")
@Temporal(TemporalType.DATE)
private Date createDate;
@Column(name = "create_time")
@Temporal(TemporalType.TIME)
private Date createTime;
不过如果表中的字段类型定义的很清晰的话此注解可以不需要添加如下情况加了和没加区别不大。如下字段类型则没必要
字段 | 类型 |
---|---|
create_time_stamp | datetime |
create_date | date |
create_time | time |
@NamedQuery
@NamedQuery(name = "Dept.queryByIsTop",
query = "select d from Dept d where d.isTop=?1")
public class Dept{
...
}
public interface DeptRepository extends JpaRepository<Dept,Integer>{
List<Dept> queryByIsTop(boolean isTop);
}
@NamedQuery 注解用于对应的对象上面,然后在对饮的Repository 编写方法就可以了。即一个名称映射一个查询
语句。
若有多个NamedQuery 请用 @NamedQueries()
来进行多个的引用
遗留问题
找了一下全球ORM框架使用的分布图,发现 中、日、韩三国使用mybatis的比较多其他国家都是jpa的占优势。猜了下原因,可能是因为这三个国家的业务复杂度偏高,但是这并不能说明什么问题,不可能全球就这三个国家的业务复杂度搞。
本想对jpa来一个比较全面的笔记的,但是了国内很多JPA的相关文档,发现都差不多。感觉写习惯了mybatis写jpa有点难以上手。
比如复杂对象,对象中包含 List 集合的那种,例如:
// 统计每个部门的学历情况
class dept{
private Integer id;
private String name;
private String banner;
// 学历 eg: 本科:12,硕士:5,博士:2
private Map<String,Integer> educations;
}
该怎么查出??