谈到JPA 首先是就jpa的基本原理
加载配置文件创建实体管理器工厂
Persisitence:静态方法(根据持久化单元名称创建实体管理器工厂)
createEntityMnagerFactory(持久化单元名称)
作用:创建实体管理器工厂
根据实体管理器工厂,创建实体管理器
EntityManagerFactory :获取EntityManager对象
方法:createEntityManager
* 内部维护的很多的内容
内部维护了数据库信息,
维护了缓存信息
维护了所有的实体管理器对象
再创建EntityManagerFactory的过程中会根据配置创建数据库表
* EntityManagerFactory的创建过程比较浪费资源
特点:线程安全的对象
多个线程访问同一个EntityManagerFactory不会有线程安全问题
* 如何解决EntityManagerFactory的创建过程浪费资源(耗时)的问题?
思路:创建一个公共的EntityManagerFactory的对象
* 静态代码块的形式创建EntityManagerFactory
创建事务对象,开启事务
EntityManager对象:实体类管理器
beginTransaction : 创建事务对象
presist : 保存
merge : 更新
remove : 删除
find/getRefrence : 根据id查询
Transaction 对象 : 事务
begin:开启事务
commit:提交事务
rollback:回滚
增删改查操作
提交事务
释放资源
了解了jpa的基本执行步骤,接下来就是我们需要了解两个重要的接口JpaRepository 和 JpaSpecificationExecutor
1.使用jpql和原生sql的基本查询方式,查询使用@Query 注解 ,注解默认使用jpql查询,添加属性nativeQuery = true 后使用sql查询
下面是我使用的实体类
package com.emon.demo.entity;
import lombok.Data;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
/**
* 1.实体类和表的映射关系
* @Entity
* @Table
* 2.类中属性和标中字段的映射关系
* @Id
* @GeneratedValue
* @Column
*/
@Entity
@Table(name="cst_customer")
public class Customer {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name="cust_id")
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
/**
* 删除主表数据:
*
* 有从表数据
* 1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
* 2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
* 3、如果还想删除,使用级联删除引用
*
* 没有从表数据引用:随便删
*
*级联操作
* cascade:配置级联操作
* CascadeType.MERGE 级联更新
* CascadeType.PERSIST 级联保存:
* CascadeType.REFRESH 级联刷新:
* CascadeType.REMOVE 级联删除:
* CascadeType.ALL 包含所有
*/
// 配置客户和联系人之间的关系 一对多关系
// 放弃外键维护,参照对方来做,mappedBy填写 linkMan 的配置的对应属性名
// fetch 配置关联对象的加载方式
// EAGER:立即加载
// Lazy :延迟加载
@OneToMany(mappedBy="customer",cascade = CascadeType.ALL)
private Set<LinkMan> linkMans = new HashSet<>();
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return "Customer{" +
"custId=" + custId +
", custName='" + custName + '\'' +
", custSource='" + custSource + '\'' +
", custIndustry='" + custIndustry + '\'' +
", custLevel='" + custLevel + '\'' +
", custAddress='" + custAddress + '\'' +
", custPhone='" + custPhone + '\'';
}
}
以下是使用jpql定义的接口
package com.emon.demo.dao;
import com.emon.demo.entity.Customer;
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 org.springframework.data.repository.query.Param;
import java.util.List;
/**
* 符合SpringDataJpa的dao层接口规范
* JpaRepository<操作的实体类类型,实体类中主键属性的类型>
* *封装了基本的crud操作
* JpaSpecificationExecutor<操作实体类类型>、
* *封装了复杂的查询(分页等)
*/
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 使用客户名称查询客户
* 使用jpql的形式查询
* jpql: from Customer where cust_name = ?
* 配置jpql语句,使用@Query注解
*/
@Query(value="from Customer where cust_name = :cust_name")
List<Customer> findJpql(@Param("cust_name") String cust_name);
/**
* 根据:客户名称和客户id查询客户
*/
@Query(value="from Customer where cust_name = :cust_name and cust_id = :id")
List<Customer> findNameAndId(@Param("cust_name") String cust_name,@Param("id") Long cust_id);
/**
* 根据:更新客户名
* @Query;代表进行查询
* @Modifying 代表的是当前执行的是一个更新操作
*/
@Query(value=" update Customer set cust_name= :cust_name where cust_id = :cust_id")
@Modifying
void updateCustomer(@Param("cust_name") String cust_name,@Param("cust_id") Long cust_id);
/**
* 使用sql的形式查询
* sql:select * from cst_customer
* 使用原生sql查询的结构都是 List
@Query(value=" select * from cst_customer",nativeQuery = true)
List<Object[]> findBysql();
@Query(value=" select * from cst_customer where cust_name like ?1%",nativeQuery = true)
List<Object[]> findBysqlName(String name);
/**
* 方法名的约定
* findBy :查询、
* 对象中的属性名(首字母大写);查询条件
* CustName
* findByCustName -- 根据客户名称查询
* 在springdataJpa的运行阶段
* 会根据方法名进行解析 findBy from xxx(实体类)
* 属性名称 where custName =
* 1.findBy + 属性名称(根据属性名查询)
* 2.findBy + 属性名称 + 查询方式(like | isnull) 如: findByCustNameLike
* 3.多条件查询
* findBy + 属性名 + “查询方式” + "多条件的连接符(and|or)" + 属性名 + “查询方式”
*/
Customer findByCustName(String custName);
/**
* 模糊查询
* @param custName
* @return
*/
List<Customer> findByCustNameLike(String custName);
/**
* 使用客户名称模糊匹配和客户所属行业精准匹配的查询
*/
Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);
}
2.根据方法名约定查询方式
/**
* 方法名的约定
* findBy :查询、
* 对象中的属性名(首字母大写);查询条件
* CustName
* findByCustName -- 根据客户名称查询
* 在springdataJpa的运行阶段
* 会根据方法名进行解析 findBy from xxx(实体类)
* 属性名称 where custName =
* 1.findBy + 属性名称(根据属性名查询)
* 2.findBy + 属性名称 + 查询方式(like | isnull) 如: findByCustNameLike
* 3.多条件查询
* findBy + 属性名 + “查询方式” + "多条件的连接符(and|or)" + 属性名 + “查询方式”
*/
Customer findByCustName(String custName);
/**
* 模糊查询
* @param custName
* @return
*/
List<Customer> findByCustNameLike(String custName);
/**
* 使用客户名称模糊匹配和客户所属行业精准匹配的查询
*/
Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);
3.对于以上DAO接口的使用,在这里我使用但愿测试的方式实现;
package com.emon.demo;
import com.emon.demo.dao.CustomerDao;
import com.emon.demo.entity.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@SpringBootTest
class JpqlTests {
@Autowired
private CustomerDao customerDao;
/**
*/
@Test
public void testGetOne(){
List<Customer> isExit = customerDao.findJpql("emon");
System.out.println("isExit = " + isExit);
}
/**
*/
@Test
public void testNameAndId(){
List<Customer> isExit = customerDao.findNameAndId("emon",2L);
System.out.println("isExit = " + isExit);
}
/**
* 更新方法需要添加
* @Transactional
* @Rollback(value=false)
*/
@Test
@Transactional
@Rollback(value=false)
public void testupdate(){
customerDao.updateCustomer("唐三",3L);
// System.out.println("isExit = " + isExit);
}
/**
*/
@Test
public void testSqlSelect(){
customerDao.findBysql().forEach(s -> System.out.println("s = " + Arrays.toString(s)));
// System.out.println("isExit = " + isExit);findBysqlName
}
/**
*/
@Test
public void findBysqlName(){
customerDao.findBysqlName("e").forEach(s -> System.out.println("s = " + Arrays.toString(s)));
// System.out.println("isExit = " + isExit);findBysqlName
}
/**
* 方法名的约定
* findBy :查询
* 对象中的属性名(首字母大写);查询条件
* CustName
* findByCustName -- 根据客户名称查询
* 在springdataJpa的运行阶段
* 会根据方法名进行解析 findBy from xxx(实体类)
* 属性名称 where custName =
*/
@Test
void findByCustName() {
System.out.println("customerDao = " + customerDao.findByCustName("唐三"));
}
@Test
void findByCustNameLink() {
System.out.println("customerDao = " + customerDao.findByCustNameLike("%三%"));
}
@Test
void findByCustNameLikeAndCustIndustry() {
Customer c = customerDao.findByCustNameLikeAndCustIndustry("唐%","诺丁城学院");
System.out.println("c = " + c);
}
/**
* Specification:查询条件
* 自定义我们自己的Specification实现类
* 实现
* root:查询的根对象
*
*/
}
4.接下来使用介绍下JpaRepository接口的的方法和简单的使用
源码中JpaRepository接口的方法有
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
5.JpaRepository接口中方法的简使用
package com.emon.demo;
import com.emon.demo.dao.CustomerDao;
import com.emon.demo.entity.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
@SpringBootTest
class JpaRepositoryTests {
@Autowired
private CustomerDao customerDao;
@Test
public void contextLoads() {
Customer customer = new Customer();
customer.setCustId(1L);
Optional<Customer> cc = customerDao.findOne(Example.of(customer));
System.out.println("c = " + cc.get());
}
@Test
public void saveTest() {
Customer customer = new Customer();
customer.setCustName("emon");
customer.setCustLevel("vip");
customer.setCustIndustry("IT教育");
customerDao.save(customer);
}
@Test
public void saveTest2() {
Customer customer = new Customer();
// customer.setCust_id(3L);
customer.setCustName("emon");
customer.setCustLevel("vip");
customer.setCustIndustry("蓝银草武魂修炼");
customerDao.save(customer);
}
// 主键已经存时,更新
@Test
public void saveTest3() {
Customer customer = new Customer();
customer.setCustId(4L);
customer.setCustName("emon");
customerDao.save(customer);
}
// 主键已经存时,更新
@Test
public void delTest() {
Customer customer = new Customer();
customer.setCustId(4L);
customerDao.delete(customer);
}
// 主键已经存时,更新
@Test
public void findAllTest() {
List<Customer> list = customerDao.findAll();
list.forEach(s -> System.out.println("s = " + s));
}
/**
* 测试统计查询,查询客户的总数量
*/
@Test
public void testCount(){
long count = customerDao.count();
System.out.println("count = " + count);
}
/**
* 查询:判断id为4的客户是否存在
* 如果为空
*/
@Test
public void testExit(){
Customer customer = new Customer();
customer.setCustId(4L);
boolean isExit = customerDao.exists(Example.of(customer));
System.out.println("isExit = " + isExit);
}
/**
* 根据id从数据库查询
* findOne: em.findOne 立即加载
* getOne: em.getReference 延迟加载
* @Transactional
*/
@Test
@Transactional
public void testGetOne(){
// Customer customer = new Customer();
// customer.setCust_id(4L);
Customer isExit = customerDao.getOne(1L);
System.out.println("isExit = " + isExit);
}
}
6.接着再来看看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);
7.对于JpaSpecificationExecutor中的方法就是要通过构造查询条件来查询,以下是基本使用方式
package com.emon.demo;
import com.emon.demo.dao.SpecificationTest;
import com.emon.demo.entity.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
import java.util.List;
@SpringBootTest
public class SpecificationTestDemo {
@Autowired
private SpecificationTest specificationTest;
/**
* 根据条件查询单个对象
* 自定义查询条件
* 1.实现Specification接口(提供泛型,查询的条件对象类型)
* 2.实现toPredicate方法(构造查询条件)
* 3.借助方法参数中的两个参数(
* root:获取需要查询的对象属性
* CriteriaBuilder:构造查询条件,内部封装了很多的查询条件(模糊匹配,精准匹配)
* )
* 根据客户名称查询
*/
@Test
public void test1(){
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
// 1.获取比较属性
Path<Object> custName = root.get("custName");
//2.构造查询条件
Predicate predicate = criteriaBuilder.equal(custName,"唐三"); // 进行精准匹配(属性,属性的值)
return predicate;
}
};
Customer customer = specificationTest.findOne(spec).get();
System.out.println("customer = " + customer);
}
// jdk8改写
@Test
public void test1_jdk8(){
Customer customer = specificationTest.findOne((root,criteriaQuery,criteriaBuilder)
->criteriaBuilder.equal(root.get("custName"),"唐三"))
.get();
System.out.println("customer = " + customer);
}
/**
* 多条件查询
* 案例:根据客户名和客户所属行业查询
*
*/
@Test
public void testSpec1(){
/**
* root:获取属性
* 客户名
* 所属行业
* criteriaBuilder:构造查询
* 1.构造客户名的精准查询
* 2.构造所属行业的精准匹配查询
* 3.将以上两个查询联合起来
*/
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Path<Object> custIndustry = root.get("custIndustry");
// 构造查询
// 1.构造客户名的精准匹配查询
Predicate p1 = criteriaBuilder.equal(custName,"唐三");
Predicate p2 = criteriaBuilder.equal(custIndustry,"诺丁城学院");
// 将多个查询条件组合到一起,组合(满足条件1并且满足条件二,)
Predicate predicate = criteriaBuilder.and(p1,p2); // 以于的方式拼接多个查询条件
// criteriaBuilder.or(p1,p2); // 以或的方式拼接多个查询条件
return predicate;
}
};
Customer customer = specificationTest.findOne(spec).get();
System.out.println("customer = " + customer);
}
// jdk8改写 testSpec1
@Test
public void testSpec1_jdk8(){
Customer customer = specificationTest.findOne((root,cquery,cb)
-> cb.and(cb.equal(root.get("custName"),"唐三")
,cb.equal(root.get("custIndustry"),"诺丁城学院")
)).get();
System.out.println("customer = " + customer);
}
/**
* 模糊查询
* equal:直接到path对象(属性),然后比较即可
* gt,lt,ge,le,like:得到path对象,根据path 指定path 指定比较的参数类型,再去进行比较
* 指定参数类型:path.as(类型的字节码对象)
*/
@Test
public void testSpec2(){
/**
*构造查询条件
*/
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<String> custName = root.get("custName");
Path<String> custIndustry = root.get("custIndustry");
// 构造查询
// 1.构造客户名的精准匹配查询
Predicate p1 = criteriaBuilder.like(custName,"_三");
Predicate p2 = criteriaBuilder.like(custIndustry,"%学院");
// 将多个查询条件组合到一起,组合(满足条件1并且满足条件二,)
// Predicate predicate = criteriaBuilder.and(p1,p2); // 以于的方式拼接多个查询条件
Predicate predicate = criteriaBuilder.or(p1,p2); // 以或的方式拼接多个查询条件
return predicate;
}
};
List<Customer> customer = specificationTest.findAll(spec);
System.out.println("customer = " + customer);
}
// jdk8改写 testSpec2
@Test
public void testSpec2_jdk8(){
Customer customer = specificationTest.findOne((root,cquery,cb)
-> cb.or(cb.like(root.get("custName"),"唐三")
,cb.like(root.get("custIndustry"),"诺丁城学院")
)).get();
System.out.println("customer = " + customer);
}
/**
* 模糊查询 排序
* equal:直接到path对象(属性),然后比较即可
* gt,lt,ge,le,like:得到path对象,根据path 指定path 指定比较的参数类型,再去进行比较
* 指定参数类型:path.as(类型的字节码对象)
*/
@Test
public void testSpec3(){
/**
*构造查询条件
*/
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Path<Object> custIndustry = root.get("custIndustry");
// 构造查询
// 1.构造客户名的精准匹配查询
Predicate p1 = criteriaBuilder.like(custName.as(String.class),"_三");
Predicate p2 = criteriaBuilder.like(custIndustry.as(String.class),"%学院");
// 将多个查询条件组合到一起,组合(满足条件1并且满足条件二,)
// Predicate predicate = criteriaBuilder.and(p1,p2); // 以于的方式拼接多个查询条件
Predicate predicate = criteriaBuilder.or(p1,p2); // 以或的方式拼接多个查询条件
return predicate;
}
};
// List list = new ArrayList<>();
// list.add("custId");
List<Customer> customer = specificationTest.findAll(spec, Sort.by(Sort.Direction.DESC,"custId"));
System.out.println("customer = " + customer);
}
// jdk8改写 testSpec3
@Test
public void testSpec3_jdk8(){
List<Customer> customers = specificationTest.findAll((root,cquery,cb) ->
cb.or(cb.like(root.get("custName"),"_三")
,cb.like(root.get("custIndustry"),"%学院")
),Sort.by(Sort.Direction.DESC,"custId"));
System.out.println("customer = " + customers);
}
/**
* // 分页查询
* findAll(Specification,Pageable)
* Pageable:分页参数,查询的页码,每页查询的条件
* 返回 Page(springDataJpa为我们封装好的pageBean对象,数据列表,工条件)
*/
@Test
public void testSpec4(){
Specification spec = null;
/**
* pageRequest对象是Pageable接口的实现类
* 创建pageRequest的过程中,需要调用他的构造方法传入两个参数
* 第一个参数:当前查询的页数(从0开始)
* 第二个参数:每页查询的数量
*/
Pageable pageable = PageRequest.of(0,2, Sort.by(Sort.Direction.DESC,"custId"));
Page<Customer> cs = specificationTest.findAll(spec,pageable);
long eles = cs.getTotalElements();
System.out.println("eles = " + eles);
int pages = cs.getTotalPages();
System.out.println("pages = " + pages);
List<Customer> c = cs.getContent();
System.out.println("c = " + c);
}
// jdk8改写 testSpec4
@Test
public void testSpec4_jdk8(){
Page<Customer> pages = specificationTest.findAll((root,cquery,cb) ->
cb.or(cb.like(root.get("custName"),"_三")
,cb.like(root.get("custIndustry"),"%学院")
),
PageRequest.of(0,2,Sort.by(Sort.Direction.DESC,"custId")));
System.out.println("customer = " + pages.getContent()); // 内容
System.out.println("pages.getTotalElements() = " + pages.getTotalElements()); // 共多少条
System.out.println("pages.getTotalPages() = " + pages.getTotalPages()); // 有多少页
System.out.println("pages.getNumberOfElements() = " + pages.getNumberOfElements());
}
/**
* 根据条件统计个数
*/
@Test
public void testCount(){
/**
*构造查询条件
*/
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Path<Object> custIndustry = root.get("custIndustry");
// 构造查询
// 1.构造客户名的精准匹配查询
Predicate p1 = criteriaBuilder.like(custName.as(String.class),"_三");
Predicate p2 = criteriaBuilder.like(custIndustry.as(String.class),"%学院");
// 将多个查询条件组合到一起,组合(满足条件1并且满足条件二,)
// Predicate predicate = criteriaBuilder.and(p1,p2); // 以于的方式拼接多个查询条件
Predicate predicate = criteriaBuilder.or(p1,p2); // 以或的方式拼接多个查询条件
return predicate;
}
};
long count = specificationTest.count(spec);
System.out.println("count = " + count);
}
// jdk8改写 testCount
@Test
public void testCount_jdk8(){
long count = specificationTest.count((root,cquery,cb) ->
cb.or(
cb.like(root.get("custName"),"_三")
,cb.like(root.get("custIndustry"),"%学院")
));
System.out.println("count = " + count); //
}
}
点击链接查看源码