Spring Data JPA(续)
目录
上一个博客介绍了Spring Data JPA,但是总感觉不够,因而加了此篇博客作为续,以后关于JPA的东西都写在这篇文章里,毕竟在实际运用中会遇到很多需要注意的地方。
1.jpa自定义sql查询
直接上代码:
//自定义查询
@Query(value = "select * from person where name=?1 and age>18",nativeQuery = true)
List<Person> findByMyQuery(String name);
此处跟之前的@Query有区别,nativeQuery=true,这样的话就用本地的查询语句了,根据value里面的正常的sql语句进行查询,注意这里写的就是真实的表名了。而如果不是nativeQuery=true,写的是类名。
2.jpa更新
直接上代码:
//更新操作(更新,删除的操作必须有事务,这个事务可以在这里写,也可以在service里写)
@Transactional
@Modifying
@Query(value = "update Person set age=?2 where id=?1")
int updatePersonAge(Long id,int age);
注意,我在这里加了事务,还有@Modifying,这个是必须的
3.jpa删除
直接上代码:
@Transactional
//删除操作
int deleteByAge(int age);
同样的,也得加事务,这个事务也可以写在service里
4.一些常用注解的理解
@Entity和@Table的区别:
@Entity说明这个class是实体类,并且使用默认的orm规则,即class名即数据库表中表名,class字段名即表中的字段名
如果想改变这种默认的orm规则,就要使用@Table来改变class名与数据库中表名的映射规则
@Column:
改变class中字段名与db中表的字段名的映射规则
具体见以下描述:
@Entity注释指名这是一个实体Bean,@Table注释指定了Entity所要映射带数据库表,其中@Table.name()用来指定映射表的表名。
如果缺省@Table注释,系统默认采用类名作为映射表的表名。实体Bean的每个实例代表数据表中的一行数据,行中的一列对应实例中的一个属性。
@Column注释定义了将成员属性映射到关系表中的哪一列和该列的结构信息,属性如下:
1)name:映射的列名。如:映射tbl_user表的name列,可以在name属性的上面或getName方法上面加入;
2)unique:是否唯一;
3)nullable:是否允许为空;
4)length:对于字符型列,length属性指定列的最大字符长度;
5)insertable:是否允许插入;
6)updatetable:是否允许更新;
7)columnDefinition:定义建表时创建此列的DDL;
8)secondaryTable:从表名。如果此列不建在主表上(默认是主表),该属性定义该列所在从表的名字。
@Id注释指定表的主键,它可以有多种生成方式:
1)TABLE:容器指定用底层的数据表确保唯一;
2)SEQUENCE:使用数据库德SEQUENCE列莱保证唯一(Oracle数据库通过序列来生成唯一ID);
3)IDENTITY:使用数据库的IDENTITY列莱保证唯一;
4)AUTO:由容器挑选一个合适的方式来保证唯一;
5)NONE:容器不负责主键的生成,由程序来完成。
@GeneratedValue注释定义了标识字段生成方式。
@Temporal注释用来指定java.util.Date或java.util.Calender属性与数据库类型date、time或timestamp中的那一种类型进行映射。
@Temporal(value=TemporalType.TIME)
实例如下:
package com.just.springjpa.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Date;
/**
* 1.@Entity说明这个class是实体类,并且使用默认的orm规则,即class名即数据库表中表名,class字段名即表中的字段名
* 如果想改变这种默认的orm规则,就要使用@Table来改变class名与数据库中表名的映射规则,
* 2.@Column来改变class中字段名与db中表的字段名的映射规则
*/
@Entity
@Table(name = "t_student")
public class Student {
@Id
@GeneratedValue
private Long id;
/**
* varchar类型长度50
*/
@Column(name = "s_name",length = 50)
private String name;
/**
* 不允许为空
*/
@Column(name="s_age",nullable = false)
private Integer age;
/**
* columnDefinition 创建表时,该字段创建的SQL语句,更灵活的创建表
*/
@Column(name="created_time",columnDefinition = "datetime COMMENT '创建时间' ")
private Date createdTime;
/**
* 精度12,小数点后2位
*/
@Column(name="money",precision = 12,scale = 2)
private BigDecimal money;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreateTime(Date createdTime) {
this.createdTime = createdTime;
}
}
具体效果可以看下自己生成的表:
2018-08-07
5.自定义查询简单实例:
还是用到了上一篇博客的Specification,简化版,提供的是一种直接简单用的思路,java8的函数式编程风格
1.repository要继承JpaSpecificationExecutor
public interface PersonRepository extends JpaRepository<Person, Long> ,JpaSpecificationExecutor<Person> {
}
来看看JpaSpecificationExecutor源码,我们可以用里面的一些方法查询,其中还有分页的查询:
/*
* Copyright 2008-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
/**
* Interface to allow execution of {@link Specification}s based on the JPA criteria API.
*
* @author Oliver Gierke
*/
public interface JpaSpecificationExecutor<T> {
/**
* Returns a single entity matching the given {@link Specification}.
*
* @param spec
* @return
*/
T findOne(Specification<T> spec);
/**
* Returns all entities matching the given {@link Specification}.
*
* @param spec
* @return
*/
List<T> findAll(Specification<T> spec);
/**
* Returns a {@link Page} of entities matching the given {@link Specification}.
*
* @param spec
* @param pageable
* @return
*/
Page<T> findAll(Specification<T> spec, Pageable pageable);
/**
* Returns all entities matching the given {@link Specification} and {@link Sort}.
*
* @param spec
* @param sort
* @return
*/
List<T> findAll(Specification<T> spec, Sort sort);
/**
* Returns the number of instances that the given {@link Specification} will return.
*
* @param spec the {@link Specification} to count instances for
* @return the number of instances
*/
long count(Specification<T> spec);
}
2.在service方法里面使用,我是用的Page<T> findAll(Specification<T> spec, Pageable pageable);,自定义+分页
@Override
public Page<Person> findDynamically(Person person, Pageable pageable,Map<String,Object> otherParams) {
return personRepository.findAll((root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
//如果是查名字,按照名称模糊查询
if(StringUtils.isNotBlank(person.getNickName())){
predicates.add(criteriaBuilder.like(root.get("nickName"),"%"+person.getNickName()+"%"));
}
//所在城市,精确搜索
if(StringUtils.isNotBlank(person.getCity())){
predicates.add(criteriaBuilder.equal(root.get("city"),person.getCity()));
}
//审核状态,精确搜索
if(person.getAuditStatus()!=null){
predicates.add(criteriaBuilder.equal(root.get("auditStatus"),person.getAuditStatus()));
}
//比较日期
if(otherParams.containsKey("afterEndTime")){
predicates.add(criteriaBuilder.greaterThan(root.get("authEndTime"),new Date()));
}
if(otherParams.containsKey("beforeEndTime")){
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("authEndTime"),new Date()));
}
//比较年龄范围,左闭右开
if(otherParams.get("minAge")!=null){
predicates.add(criteriaBuilder.greaterThan(root.get("age"),Integer.parseInt(otherParams.get("minAge").toString())));
}
if(otherParams.get("maxAge")!=null){
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("age"),Integer.parseInt(otherParams.get("maxAge").toString())));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
},pageable);
}
一切尽在代码中...
本示例集合了几种常见查询,模糊搜索,精确搜索,日期范围搜索等等,比较实用,可以参考。criteriaBuilder有很多查询匹配的方法,可以满足绝大部分查询需求,具体的可以在使用中看下里面的方法。
其实Person的所有属性都可以作为查询条件,可以利用java反射拿到所有属性,然后来个循环每个属性都可以加进去。
2018-08-24
6.JPA 关联表自定义动态查询
在实际业务中,可能要关联表查询,并且查询条件是动态的,这就需要在自定义查询的基础上再来一波。
1.新建一个分数类,跟student是多对一的关系
package com.just.springjpa.domain;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
@Entity
@Table(name = "course_score")
public class CourseScore {
@Id
@GeneratedValue
private Long id;
private BigDecimal score;
private Date createDate;
private String course;
@ManyToOne
@JoinColumn(name = "student_id")
private Student student;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BigDecimal getScore() {
return score;
}
public void setScore(BigDecimal score) {
this.score = score;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
2.student中可以再加个分数列表,一对多,懒加载,(这个list也可以不加,需要用到时可以再加)
@JsonIgnore
@OneToMany(fetch = FetchType.LAZY)
private List<CourseScore> courseScoreList;
3.自定义查询,我直接写在了controller里了
repository就省略了,跟PersonRepository 一个写法
/**
* 根据学生姓名和最低分数查询
* @param name 学生姓名,可为空
* @param minScore 最低分数,可为空
* @param pageable 分页参数
* @return 查询到的分数结果
*/
@GetMapping("/getScore")
public Page<CourseScore> getScore(@RequestParam(required = false) String name,
@RequestParam(required = false) BigDecimal minScore,
@PageableDefault(size = 20) Pageable pageable){
//关联动态查询
Page<CourseScore> courseScores=courseScoreRepository.findAll(new Specification<CourseScore>() {
@Override
public Predicate toPredicate(Root<CourseScore> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicatesList = new ArrayList<>();
//join里面的参数写法有好几种,这里采用最简单的一种,直接写属性名,关联的Student,内联
Join<Student,CourseScore> join=root.join("student",JoinType.INNER);
//注意name属性是在Student里面的,而join里面比较的属性都是关联的那张表里的字段,用join来获取
if(!StringUtils.isEmpty(name)){
// predicatesList.add(
// criteriaBuilder.equal(
// join.get("name"), name
// )
// );
predicatesList.add(
criteriaBuilder.like(
join.get("name"), name
)
);
}
//分数是CourseScore里的属性,用root获取
if(minScore!=null){
predicatesList.add(
criteriaBuilder.greaterThanOrEqualTo(
root.get("score"), minScore
)
);
}
return criteriaBuilder.and(predicatesList.toArray(new Predicate[0]));
}
},pageable);
return courseScores;
}
4.测试结果
后台打印的sql:
Hibernate: select coursescor0_.id as id1_0_, coursescor0_.course as course2_0_, coursescor0_.create_date as create_d3_0_, coursescor0_.score as score4_0_, coursescor0_.student_id as student_5_0_ from course_score coursescor0_ inner join t_student student1_ on coursescor0_.student_id=student1_.id where (student1_.s_name like ?) and coursescor0_.score>=60 limit ?
Hibernate: select student0_.id as id1_2_0_, student0_.s_age as s_age2_2_0_, student0_.created_time as created_3_2_0_, student0_.money as money4_2_0_, student0_.s_name as s_name5_2_0_ from t_student student0_ where student0_.id=?
由此看出是inner join,并且join表的顺序也是对的,参数也是对的。当然这只是测试,不合理的地方请见谅。
2018-09-11