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. glibc 内存申请和释放及堆连续检查

    C语言有两种内存申请方式: 1.静态申请:当你声明全局或静态变量的时候,会用到静态申请内存.静态申请的内存有固定的空间大小.空间只在程序开始的时候申请一次,并且不再释放(除非程序结束). 2.自动申请 ...

  2. js onload事件使用

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...

  3. js如何通过变量调用函数,函数名在变量里面

    js如何通过变量调用函数,函数名在变量里面. 有时候函数名是动态定义的,这时候我们就需要用到这个方法了. //赋值函数名称 var a = "b"; //定义函数 function ...

  4. DJango xadmin 表头和标底设置,显示隐藏和显示

    xadmin文件中的xadmin.pyfrom xadmin import views class GlobalSetting(object): site_title = "zwb" ...

  5. PHP 一致性Hash

    一致性HASH 好久没有写文章了,最近忙着公司的事情,也一拖再拖.这篇一致性hash是很久之前就有的一篇算法,记录一下,这周写个基于该算法的Redis中间件. HASH算法的精髓就在于打散原本杂乱无序 ...

  6. Qt打开外部程序和文件夹需要注意的细节(注意QProcess的空格问题,以及打开本地文件时,需要QUrl::fromLocalFile才可以)

    下午写程序中遇到几个小细节,需要在这里记录一下. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 QProcess *process = new QProcess(this ...

  7. PySide——Python图形化界面入门教程(五)

    PySide——Python图形化界面入门教程(五) ——QListWidget 翻译自:http://pythoncentral.io/pyside-pyqt-tutorial-the-qlistw ...

  8. Mono.Cecil - 0.6

    原文:Mono.Cecil - 0.6 项目地址:Mono.Cecil 项目描述:In simple English, with Cecil, you can load existing manage ...

  9. Qt 5.9对Mac的图形显示有许多改进

    We have some platform specific improvements as well as support for new platforms and compilers comin ...

  10. PHP 的魔术方法及其应用

    PHP中将所有__(两个下划线)开头的类方法作为魔术方法,这方法之所以称为魔术方法是因为其实现的功能就如变魔术一样感觉很神奇.在特定的事件下触发,这真的很酷. **__construct()** 这个 ...