ORM 思想

对象关系映射, 建立实体类和表的关系映射关系, 实体类和表中字段的映射关系,我们操作实体类底层是操作数据表, 进而自动的拼接出SQL语句

Jpa规范

Jpa(Java Persistence Api) java持久层的api,是SUN公司提出的一套规范,也就是说,是由接口和抽象类组成,jpa本身不干活,真正干活的是hibernate,toplink等等对规范具体实现的框架, 有了这套规范之后,我们是面向这套规范编程的,也就是说,当我们想把项目中的Hibernate替换成toplink,我们的java代码是不需要修改的,而仅仅修改配置文件,切换jar包

上手: jpa规范

常见的注解

我们通过注解完成两件事:

  1. 实体类和数据表之间的关系的映射
  2. 实例类属性和数据表字段之前的映射
  • 添加在类头上的注解
// 声明此类是实体类
@Entity
// 声明此类是实体类
@Table(name = "表名")
  • 标记主键
主键策略 作用
IDENTITY 自增(要求底层的数据库支持自增如mysql, Oracle就不支持)
SEQUENCE 序列(要求底层的数据库支持序列, 如Oracle)
TABLE JPA的支援, JPA会帮我们生成另一张表, 里面记载了本表的记录数
AUTO 自适应,让程序根据运行的环境自动选择策略, 我的程序选择了 TABLE策略
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
  • 实体类属性和表中的字段的映射
@Column(name = "表中的字段名")

进行CRUD的开发步骤:

  • 加载配置文件, 得到实体管理类工厂
myJpa = Persistence.createEntityManagerFactory("myJpa")
  • 通过实体管理类工厂获取实体管理器
myJpa.createEntityManager()
  • 获取事务对象, 开启事务
 EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
  • CRUD
  • 提交事务
 transaction.commit();
  • 释放资源
 entityManager.close();

注意点: 1. 如果不添加事务, 是不会持久化的 2. 获取实体管理类工厂的方法是耗时的,而且实体管理类工厂可重复使用,因此把他抽取出去, 类一加载就执行

常用方法

  • 添加 public void persist(Object entity);
  • 根据主键Id查找public <T> T getReference(Class<T> entityClass, Object primaryKey);
  • 根据主键Id查找public <T> T find(Class<T> entityClass, Object primaryKey);
  • 删除public void remove(Object entity);

find()和getReference()的区别:

find立即执行,返回实体类对象,而和getReference返回的是实体类的代理对象, 懒加载,当我使用对象的属性时才执行查询语句

jpql

jpql: Java Persistence Query Language 根据实体类和属性进行查询

其中jpql没有select * 这种写法,而是直接省去了, 因为是面向对象的查询语言, 所以它的查询语句向下面这样写

 from 带包名的类的全路径/直接写类名
  • 排序
from  类名 order by id desc/asc
  • 统计数量
select count(id) from 类名
  • 带条件的查询
EntityManager entityManager = JpaUtils.getEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
// 查询全部
String jpql = "from 类名 where name like ?";
// String jpql = "from 类名"; 可省略包名
Query query = entityManager.createQuery(jpql);
// 参数1: 占位符的位置
// 参数2: 参数的值
query.setParameter(1,"张%");
query.getResultList().forEach(System.out::println);
transaction.commit();
entityManager.close();
  • 分页查询
 EntityManager entityManager = JpaUtils.getEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
// 查询全部
String jpql = "from 类名";
Query query = entityManager.createQuery(jpql);
// 对分页的参数赋值
// 起始索引
query.setFirstResult(0);
// 分页参数, 每次查询两条
query.setMaxResults(2);
// 查询,斌封装结果集
List resultList = query.getResultList();
resultList.forEach(System.out::println);
transaction.commit();
entityManager.close();

Spring Data Jpa

SpringDataJpa是Spring对jpa的整合,封装,基于SpringDataJpa的规范我们可以更方便的进行持久层的操作, SpringDataJpa底层干活的是Hibernate框架

开发步骤

被spring整合后,相关的配置可通过spring.jpa....设置

  1. 做好实体类和数据表之间的关系的映射
  2. 面向接口编程,我们只要自己新建一个接口,并且继承JpaRepository和JpaSpecificationExecutor这两个接口就可以使用它的方法,而不需要关心实现类如何,就像下面:具体的实现类会通过JDK的动态代理为我们自动生成,
public interface CustomerRepository extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {}

其中:

  • JpaRepository(继承自CRUDRepository) 封装了基本的CRUD
  • JpaSpecificationExecutor 封装了复杂查询

简单的CRUD

当我们使用自定义的Repository点一下的时,基本的CRUD基本上打眼一看就知道怎么使用了, 下面说一下,比较相似的方法

方法名 作用
getOne() 根据Id获取单个实体类,底层使用的是Jpa的getReference() 懒加载
findOne() 同样是根据Id获取单个实体,立即加载
save() 更新 若id存在 / 新增 若id为空

支持 自定义sql / jpql / 方法命名规则 查询

使用注解@Query

例:

@Query(value = "select * from  Customer where name = ?", nativeQuery = true)
public Customer findByNameAndSQL(String name); // 查询全部
@Query(value = "select * from Customer", nativeQuery = true)
public List<Customer> findAllBySQL();

其中的@Query的第三个参数默认是false 表示不是sql查询,而是jpql查询

// jpql 查询全部
@Query(value = "from Customer where name =?1", nativeQuery = false)
public Customer findAllByNameAndJpql();

SpringDataJpa对jpql再次进行了封装,支持方法命名规则查询:

查询方式 命名规则
根据某个字段查询 find实体类名By字段名
模糊查询 find实体类名By字段名Like , 注意传参时不要忘了添加%
多条件并列查询 find实体类名By字段名And字段名 ,使用and关键字隔开
多条件或查询 find实体类名By字段名Or字段名 ,使用Or关键字隔开

复杂查询

Optional<T> findOne(@Nullable Specification<T> spec);

List<T> findAll(@Nullable Specification<T> spec);

//Page 是 SpringDataJpa提供的
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable); // 查询条件spec
// 排序条件 sort
List<T> findAll(@Nullable Specification<T> spec, Sort sort); // 按照条件统计
long count(@Nullable Specification<T> spec);

他们的公共入参都有Specification 这是个接口,我们需要自己实现, 重写它的抽象方法

 Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

其中:

  • root: 是我们查询的根对象(查询的任何属性都能从根对象中获取)
  • CriteriaQuery: 顶层的查询对象
  • CriteriaBuilder: 查询的构造器, 封装了很多查询条件

例:

分页查询

// 当前查询第几页, 每一页查询的条数
Pageable pageable = PageRequest.of(0,2); Page<Customer> page = customerRepository.findAll((root, query, criteriaBuilder)->{
return null;
}, pageable); System.out.println("page.getTotalElements(): "+ page.getTotalElements()); // 总条数
System.out.println("page.getTotalPages(): "+ page.getTotalPages()); // 总页数
page.getContent().forEach(System.out::println); // 当前页结果

排序

/**
* 参数1 ; 正序 / 倒叙
* 参数2 : 属性名
*/
Sort orders = new Sort(Sort.Direction.DESC,"id"); List<Customer> list= customerRepository.findAll((root,query,criteriaBuilder)->{
Path<Object> name = root.get("name");
Predicate like = criteriaBuilder.like(name.as(String.class), "武%");
return like;
},orders);

模糊查询

List<Customer> list=  customerRepository.findAll((root,query,criteriaBuilder)->{
Path<Object> name = root.get("name");
Predicate like = criteriaBuilder.like(name.as(String.class), "武%");
return like;
});

多条件查询

/**
* root 获取属性
* criteriaBuilder: 构造查询条件
*/
Optional<Customer> customer= customerRepository.findOne((root,query,criteriaBuilder)->{
Path<Object> name = root.get("name");
Path<Object> industry = root.get("industry");
Predicate namepre = criteriaBuilder.equal(name, "张三");
Predicate indpre = criteriaBuilder.equal(industry, "学生"); /* 组合条件
1. 满足条件1和条件2
2. 满足条件1或条件2
* */
Predicate andpre = criteriaBuilder.and(namepre, indpre);
// Predicate or = criteriaBuilder.and(namepre, indpre);
// 以 或的条件查询
return andpre;
});
 // 多条键查询尽量写成下面的样子,保证每个条件都是有效的
public void search(Label label) {
labelRepository.findAll((root, query, caiteria) -> {
ArrayList<Predicate> predicateList = new ArrayList<>();
if (label.getLabelName()!=null){
Path<Object> labelname = root.get("labelname");
Predicate like = caiteria.like(labelname.as(String.class), "%" + label.getLabelName() + "%");
predicateList.add(like);
} if (label.getRecommend()!=null){
Path<Object> rec = root.get("recommend");
Predicate like = caiteria.like(rec.as(String.class), "%" + label.getRecommend() + "%");
predicateList.add(like);
} Predicate[] predicates = new Predicate[predicateList.size()];
predicateList.toArray(predicates); // 参数位置支持可变参数, 但是我们要尽量传递进去有效的条件
return caiteria.and(predicates);
});

注意点:

  • 分页两种: 带条件的分页 findAll(Specification spec,Pageable pageable) 和不带条件的分页findAll(Pageable pageable)
  • 此外: 对于criteriaBuilder的equals方法,可以直接使用path对象,但是对于 gt lt le like 我们需要分步, 1. 得到path对象,2. 根据path对象指定比较的参数类型在进行下一步比较,因为可能比较的是字符串, 也可能是数字

多表操作的级联相关

一对多配置

数据库表之间难免会出现彼此的约束, 如商品分类表和商品表之间,就是典型的一对多的关系,同一个分类下有多种不同的商品,下面就是jpa如何通过注解控制一对多的关系

  1. 双方都有一个彼此之间的引用, 如在one的一方,维护着多的一方的一个集合,一般使用HashSet,而在many的一方维护着一的一方的引用
  2. 在一的一方使用注解@OneToMany
  3. 在多的一方使用注解@ManyToOne
  4. 维护主键的一方需要使用 @JoinColumn(name = "customer_id",referencedColumnName = "id") 注解, 作用是指明外键列名,以及引用的主键列名

关于主键的维护:

一般我们会选在让多的一方维护外键,不是因为一的一方不能维护,在一对多的关系中,双方都可以进行主键的维护,并且我们把这种关系叫做双向管理,但是双方都维护主键,就会使得多出一条update语句,产生资源的浪费,原因如下:

所谓维护主键,就比如说我们通过jpa的save方法插入主表中的实体1和从表中的实体2,如果我们没有进行双方之间的关联,两条数据会被添加进数据库,但是外键部分却为null; 因此我们可以把维护主键看作是负责更新外键字段,这时如果双方都维护的话,就会出现两次update外键字段的sql

总结: 以下是OneToMany的最终方案

one:

mappedBy通过他指明,自己放弃维护外键,而参考Many端对外键的维护的实现
@OneToMany(mappedBy= "customer")
private Set<LinkMan> linkManSet = new HashSet<>();

Many

targetEntity: 指明One的一方的字节码
name: 本表中的外键的列名, 因为在多的一方维护的外键
referencedColumnName: 外键引用的主键的列名
@ManyToOne(targetEntity: = Customer.class)
@JoinColumn(name = "customer_id",referencedColumnName = "id")
private Customer customer;

一对多的级联cascade

级联操作再One的一端进行配置

类型 作用
ALL 级联所有(推荐)
PERSIST 保存
MERGE 更新
REMOVE 删除
 @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)

级联保存: 同时存在One和Many两个对象,我们在保存One的同时级联保存Many方的对象

级联删除:

情况1: One的一方在维护主键, 这是的级联删除就会分两步走 ,首先删除外键,然后删除One的一方,同时删除One级联的去全部Many方

情况2: One的一方不再维护主键,不能级联删除

多对多配置

  • 多对多配置中,同样需要一方主动的放弃对外键维护权
  • 双方维护着代表对方的set集合

例子: User和Role 多对多的关系

在User端,主动放弃对外键的维护权

@ManyToMany(mappedBy = "users",cascade = CascadeType.ALL)
public Set<Role> roles = new HashSet<>();

在Role端,维护着外键, 负责对中间表上外键的更新的操作


/**
* 配置多对多
* 1. 声明关系的配置
* 2. 配置中间表(包含两个外键)
* targetEntity: 对方的 实体类字节码
*
*/
@ManyToMany(targetEntity =User.class)
@JoinTable(
name = "user_role",// name 中间表名称
joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}, // 当前对象,在中间表中的外键名, 以及参照的本表的哪个主键名
inverseJoinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")} // 对方对象在中间表的外键
)
public Set<User> users = new HashSet<>();

对象导航查询

所谓对象导航查询,就是首先使用jpa为我们提供的Repository查询得到结果对象,再通过该对象,使用该对象的get方法,进而查询出它关联的对象的操作

在一对多的关系中, get的属性是 Set集合, 而在多对一的关系中,get的属性是它维护的那个One端的引用

总结:

模式 作用
一查多 默认延迟加载,因为有可能一下子级联查询出成百上千的数据,但是我们却不用
多查一 默认立即查询,多查一条数据

如果想更改默认的加载模式, 就在@OneToMany(一的一方)注解上添加属性fetch = FetchType.EAGER

属性 作用
EAGER 立即加载
LAZY 延迟加载

Jpa 笔记的更多相关文章

  1. Spring Data JPA笔记

    1. Spring Data JPA是什么 Spring Data JPA是Spring Data大家族中的一员,它对对持久层做了简化,用户只需要声明方法的接口,不需要实现该接口,Spring Dat ...

  2. JPA笔记4 ManyToMany

    package many_to_many; import java.util.HashSet; import java.util.Set; import javax.persistence.Entit ...

  3. JPA笔记3 OneToOne

    package one_to_one; import javax.persistence.Entity; import javax.persistence.FetchType; import java ...

  4. JPA笔记2 OneToMany

    package one_to_many; import java.util.HashSet; import java.util.Set; import javax.persistence.Cascad ...

  5. JPA笔记1 ManyToOne

    persistence.xml <?xml version="1.0" encoding="UTF-8"?> <persistence ver ...

  6. jpa

    学习尚硅谷jpa笔记: 所依赖的jar包: 首先在META-INF下创建配置文件,persistence.xml <?xml version="1.0" encoding=& ...

  7. JPA + SpringData 操作数据库 ---- 深入了解 SpringData

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7735616.html ------------------------------------ ...

  8. JPA学习笔记(8)——映射一对多关联关系

    一对多关联关系 本文有很多和多对一是一样的,因此不会写得非常具体. 有看不懂的.能够參考JPA学习笔记(7)--映射多对一关联关系 Order实体类 package com.jpa.helloworl ...

  9. JPA学习笔记1——JPA基础

    1.JPA简介: Java持久化规范,是从EJB2.x以前的实体Bean(Entity bean)分离出来的,EJB3以后不再有实体bean,而是将实体bean放到JPA中实现.JPA是sun提出的一 ...

随机推荐

  1. 2-17-MySQL读写分离-mysql-proxy

        实验环境: mysql-proxy服务端:        xuegod1              IP:192.168.10.31 mysql服务器(主,负责写)服务端:xuegod2    ...

  2. XF 滑块和步进控件

    <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http:/ ...

  3. python 安装 win 下的exe结尾的文件操作

    1.首先下载相关的模块 2.把下载的相关模块放到python 安装目录下 3.cmd 切换到python的安装目录下 例如:{PIL-1.1.7.win32-py2.7.exe} 4.执行 pip i ...

  4. Qt4可以使用trUtf8函数,其内容可以是中文,也可以是\F硬编码

    显示在textBrowser->setText 中文乱码 转成QObject::trUtf8即可. ui->textBrowser->setText((QObject::trUtf8 ...

  5. 使用IntelliJ IDEA开发SpringMVC网站(五)博客文章管理

    原文:使用IntelliJ IDEA开发SpringMVC网站(五)博客文章管理 摘要 通过对博客文章的管理,实现外键操作. 目录[-] 八.博客文章管理 1.查看文章 2.添加博客        3 ...

  6. SQLServer 进程无法向表进行大容量复制(错误号: 22018 20253)

    原文:SQLServer 进程无法向表进行大容量复制 我的环境:SQL SERVER 2008 R2:发布者 ->SQL SERVER 2017 订阅者 进程无法向表“"dbo&quo ...

  7. 【码云周刊第 32 期】程序员眼中的 Vue 与 Angular !

    码云项目推荐 基于 Vue 的项目: 1.项目名称:基于 Vue.js 的 UI 组件库 项目简介:iView 是一套基于 Vue.js 的 UI 组件库,主要服务于 PC 界面的中后台产品. 项目地 ...

  8. 零元学Expression Blend 4 - Chapter 47 超简单!运用StackPanel配合OpacityMask做出倒影效果

    原文:零元学Expression Blend 4 - Chapter 47 超简单!运用StackPanel配合OpacityMask做出倒影效果 有网友问我如何在Blend内制作出倒影效果 我提供了 ...

  9. 批处理(bat)实现SQLServer数据库备份与还原

    原文:批处理(bat)实现SQLServer数据库备份与还原 备份数据库.bat @echo off set path=%path%;C:\Program Files (x86)\Microsoft ...

  10. 笔记:认识.NET平台

    认识.NET平台先了解一堆技术术语和缩写 http://www.cnblogs.com/dbycl/p/6419456.html 天生不跨平台的.NET Framework 2.大家都来开发新语言 3 ...