文章目录
1. JPA自定义
具体的关键字,使用方法和生产成SQL如下表:
KeyWord | Sample | JPQL Snippet |
---|---|---|
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 |
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 with appended %) |
EndingWith | findByFirstnameEndingWith | where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | where x.firstname like ?1 (parameter bound wrapped 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 = true |
FALSE | findByActiveFalse() | where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | where UPPER(x.firstame) = UPPER(?1) |
2. 动态SQL
Spring-data-jpa已经很方便了,但是有时候我们有的参数为空,这时我们不想让这些参数作为条件查询,笨办法就是去写n个方法,当然也有好办法了,那就是JpaSpecificationExecutor:
继承JpaSpecificationExecutor
package com.yb.dao;
import com.yb.domain.HiTaskinst;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
* @author liangjie@itcast.cn
* @version 1.0
* @date 2020/7/31
*/
public interface HiTaskinstRepository extends JpaRepository<HiTaskinst,String>
, JpaSpecificationExecutor<HiTaskinst> {
/**
* 通过名称查询
* @param name
* @return
*/
List<HiTaskinst> findByNameLike(String name);
/**
* 原生SQL查询
* @param name
* @return
*/
@Query(value = "select ht.* from `act_hi_taskinst` ht WHERE ht.`NAME_` = ?1",
nativeQuery = true)
@Modifying
List<HiTaskinst> findHiTaskinstByName(String name);
/**
* 分页查询
* @param pageable
* @return
*/
Page<HiTaskinst> findAll(Pageable pageable);
/**
* 分页查询+条件(根据名称查询)
* @param name
* @param pageable
* @return
*/
Page<HiTaskinst> findByNameLike(String name,Pageable pageable);
}
JpaController
/**
* 分页查询+条件+动态
* @return
*/
@PostMapping("/test07")
private List<HiTaskinst> test07(@RequestBody HiTaskinstVo hiTaskinstVo) {
System.out.println("条件分页+动态SQL:"+hiTaskinstVo);
Page<HiTaskinst> pages = jpaService.test07(hiTaskinstVo);
List<HiTaskinst> content = pages.getContent();
System.out.println(content.size());
return content;
}
vo
package com.yb.vo;
import lombok.Data;
/**
* @author liangjie@itcast.cn
* @version 1.0
* @date 2020/8/3
*/
@Data
public class HiTaskinstVo {
private String name;
private Integer pageNum;
private Integer pageSize;
}
JpaServiceImpl
/**
* 动态Sql
* @param hiTaskinstVo
* @return
*/
@Override
public Page<HiTaskinst> test07(HiTaskinstVo hiTaskinstVo) {
//动态查询
Specification<HiTaskinst> specification = new Specification<HiTaskinst>() {
@Override
public Predicate toPredicate(Root<HiTaskinst> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate p = null;
//判断是否为空或者null,进行动态的查询
if(!hiTaskinstVo.getName().equals("")&&hiTaskinstVo.getName()!=null){
//不为空或者null就进行模糊查询条件设置
p = criteriaBuilder.like(root.get("name"),"%"+hiTaskinstVo.getName()+"%");
}else {
//空或者null就进行全部查询条件设置
p = criteriaBuilder.like(root.get("name"),"%%");
}
criteriaQuery.where(p);
return null;
}
};
//分页,从0开始
PageRequest of = PageRequest.of(hiTaskinstVo.getPageNum()-1, hiTaskinstVo.getPageSize());
//查询
Page<HiTaskinst> all = hiTaskinstRepository.findAll(specification, of);
return all;
}
测试
查询条件为 “班”,进行模糊查询
查询条件为空,进行全部查询,第一页,一页三条数据。
3. JPA中的一对多(多对一)
3.1 示例分析
我们采用的示例为客户和联系人。
客户:指的是一家公司,我们记为A。
联系人:指的是A公司中的员工。
在不考虑兼职的情况下,公司和员工的关系即为一对多,反之就是多对一。
3.2 表关系建立
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
什么是外键?
指的是从表中有一列,取值参照主表的主键,这一列就是外键。
一对多数据库关系的建立,如下图所示:
/*创建客户表*/
CREATE TABLE cst_customer (
cust_id BIGINT (32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
cust_name VARCHAR (32) NOT NULL COMMENT '客户名称(公司名称)',
cust_source VARCHAR (32) DEFAULT NULL COMMENT '客户信息来源',
cust_industry VARCHAR (32) DEFAULT NULL COMMENT '客户所属行业',
cust_level VARCHAR (32) DEFAULT NULL COMMENT '客户级别',
cust_address VARCHAR (128) DEFAULT NULL COMMENT '客户联系地址',
cust_phone VARCHAR (64) DEFAULT NULL COMMENT '客户联系电话',
PRIMARY KEY (`cust_id`)
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
/*创建联系人*/
CREATE TABLE `cst_linkman` (
`lkm_id` BIGINT (32) NOT NULL AUTO_INCREMENT,
`lkm_name` VARCHAR (32) DEFAULT NULL,
`lkm_gender` VARCHAR (32) DEFAULT NULL,
`lkm_phone` VARCHAR (32) DEFAULT NULL,
`lkm_mobile` VARCHAR (32) DEFAULT NULL,
`lkm_email` VARCHAR (32) DEFAULT NULL,
`lkm_position` VARCHAR (32) DEFAULT NULL,
`lkm_memo` VARCHAR (32) DEFAULT NULL,
`lkm_cust_id` BIGINT (32) DEFAULT NULL,
PRIMARY KEY (`lkm_id`),
FOREIGN KEY (`lkm_cust_id`) REFERENCES cst_customer(`cust_id`) ON DELETE SET NULL
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
3.3 实体类关系建立以及映射配置
在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息,代码如下:
package com.yb.domain;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;
/**
* @author liangjie@itcast.cn
* @version 1.0
* @date 2020/8/3
*/
@Data
@Entity
@Table(name = "cst_customer")
public class Customer implements Serializable {
@Id//表明当前私有属性是主键
@GeneratedValue(strategy= GenerationType.IDENTITY)//指定主键的生成策略
@Column(name="cust_id")//指定和数据库表中的cust_id列对应
private Long custId;
@Column(name="cust_name")//指定和数据库表中的cust_name列对应
private String custName;
@Column(name="cust_source")//指定和数据库表中的cust_source列对应
private String custSource;
@Column(name="cust_industry")//指定和数据库表中的cust_industry列对应
private String custIndustry;
@Column(name="cust_level")//指定和数据库表中的cust_level列对应
private String custLevel;
@Column(name="cust_address")//指定和数据库表中的cust_address列对应
private String custAddress;
@Column(name="cust_phone")//指定和数据库表中的cust_phone列对应
private String custPhone; //配置客户和联系人的一对多关系
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
private Set<LinkMan> linkmans;
@Override
public String toString() {
return "Customer{" +
"custId=" + custId +
", custName='" + custName + '\'' +
", custSource='" + custSource + '\'' +
", custIndustry='" + custIndustry + '\'' +
", custLevel='" + custLevel + '\'' +
", custAddress='" + custAddress + '\'' +
", custPhone='" + custPhone + '\'' +
", linkmans=" + linkmans +
'}';
}
}
}
由于联系人是多的一方,在实体类中要体现出,每个联系人只能对应一个客户,代码如下:
package com.yb.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author liangjie@itcast.cn
* @version 1.0
* @date 2020/8/3
*/
@Entity
@Table(name = "cst_linkman")
public class LinkMan implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="lkm_id")
private Long lkmId;
@Column(name="lkm_name")
private String lkmName;
@Column(name="lkm_gender")
private String lkmGender;
@Column(name="lkm_phone")
private String lkmPhone;
@Column(name="lkm_mobile")
private String lkmMobile;
@Column(name="lkm_email")
private String lkmEmail;
@Column(name="lkm_position")
private String lkmPosition;
@Column(name="lkm_memo")
private String lkmMemo;
//多对一关系映射:多个联系人对应客户
@ManyToOne
@JoinColumn(name="lkm_cust_id")
private Customer customer;
public Long getLkmId() {
return lkmId;
}
public void setLkmId(Long lkmId) {
this.lkmId = lkmId;
}
public String getLkmName() {
return lkmName;
}
public void setLkmName(String lkmName) {
this.lkmName = lkmName;
}
public String getLkmGender() {
return lkmGender;
}
public void setLkmGender(String lkmGender) {
this.lkmGender = lkmGender;
}
public String getLkmPhone() {
return lkmPhone;
}
public void setLkmPhone(String lkmPhone) {
this.lkmPhone = lkmPhone;
}
public String getLkmMobile() {
return lkmMobile;
}
public void setLkmMobile(String lkmMobile) {
this.lkmMobile = lkmMobile;
}
public String getLkmEmail() {
return lkmEmail;
}
public void setLkmEmail(String lkmEmail) {
this.lkmEmail = lkmEmail;
}
public String getLkmPosition() {
return lkmPosition;
}
public void setLkmPosition(String lkmPosition) {
this.lkmPosition = lkmPosition;
}
public String getLkmMemo() {
return lkmMemo;
}
public void setLkmMemo(String lkmMemo) {
this.lkmMemo = lkmMemo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "LinkMan{" +
"lkmId=" + lkmId +
", lkmName='" + lkmName + '\'' +
", lkmGender='" + lkmGender + '\'' +
", lkmPhone='" + lkmPhone + '\'' +
", lkmMobile='" + lkmMobile + '\'' +
", lkmEmail='" + lkmEmail + '\'' +
", lkmPosition='" + lkmPosition + '\'' +
", lkmMemo='" + lkmMemo + '\'' +
'}';
}
}
}
3.4 映射的注解说明
3.4.1 @OneToMany
作用:
建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
3.4.2 @ManyToOne
作用:
建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
3.4.3 @JoinColumn
作用:
用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
3.5 一对多的操作
3.5.1 添加-级联添加
/**
* 添加+级联添加
*/
@Test
public void demo1(){
//创建客户和联系人对象
Customer c = new Customer();
c.setCustName("TBD云集中心");
c.setCustLevel("VIP客户");
c.setCustSource("网络");
c.setCustIndustry("商业办公");
c.setCustAddress("昌平区北七家镇");
c.setCustPhone("010-84389340");
LinkMan l = new LinkMan();
l.setLkmName("TBD联系人");
l.setLkmGender("male");
l.setLkmMobile("13811111111");
l.setLkmPhone("010-34785348");
l.setLkmEmail("98354834@qq.com");
l.setLkmPosition("老师");
l.setLkmMemo("还行吧");
l.setCustomer(c);
c.getLinkmans().add(l);
customerRepository.save(c);
}
3.5.2 查询-级联查询
查询客户的同时把属于该客户的联系人查询出来
需要在实体类中配置
Customer
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
private Set<LinkMan> linkmans;
/**
* 查询客户表(同时级联查询联系人)
*/
@Test
public void demo02(){
//通过id查询
Optional<Customer> byId = customerRepository.findById(5L);
Customer customer = byId.get();
System.out.println(customer);
}
/**
* 查询联系人(同时级联查询该联系人的客户)
*/
@Test
public void demo03(){
//通过id查询
Optional<LinkMan> byId = linkManRespository.findById(5L);
LinkMan linkMan = byId.get();
System.out.println(linkMan);
System.out.println("客户:"+linkMan.getCustomer().getCustName());
}
注意:
- 在测试级联查询时,进行展示的时候,两个实体类只能有一个使用lombok的注解,本人测试的时候,一直报错,就是因为在两个实体类中都是用了lombok的注解,例如:@Data,@ToString。
- 在展示的时候实现toString方法最好不要实现级联的属性,例如:
如果非要实现,那么只能实现这两个实体类中的其中一个类,另一个类不能实现,否则打印数据的时候报错。
3.5.3 级联删除
/**
*级联删除
*/
@Test
public void demo04(){
customerRepository.deleteById(5L);
}