ORM 思想
对象关系映射, 建立实体类和表的关系映射关系, 实体类和表中字段的映射关系, 我们操作实体类底层是操作数据表, 进而自动的拼接出 SQL 语句
Jpa 规范
Jpa(Java Persistence API) java 持久层的 API, 是 SUN 公司提出的一套规范, 也就是说, 是由接口和抽象类组成, jpa 本身不干活, 真正干活的是 hibernate,toplink 等等对规范具体实现的框架, 有了这套规范之后, 我们是面向这套规范编程的, 也就是说, 当我们想把项目中的 Hibernate 替换成 toplink, 我们的 java 代码是不需要修改的, 而仅仅修改配置文件, 切换 jar 包
上手: jpa 规范
常见的注解
我们通过注解完成两件事:
实体类和数据表之间的关系的映射
实例类属性和数据表字段之前的映射
添加在类头上的注解
- // 声明此类是实体类
- @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.... 设置
做好实体类和数据表之间的关系的映射
面向接口编程, 我们只要自己新建一个接口, 并且继承 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;
- });
注意点:
分页两种: 带条件的分页
findAll(Specification spec,Pageable pageable)
和不带条件的分页
findAll(Pageable pageable)
此外: 对于 criteriaBuilder 的 equals 方法, 可以直接使用 path 对象, 但是对于 gt lt le like 我们需要分步, 1. 得到 path 对象, 2. 根据 path 对象指定比较的参数类型在进行下一步比较, 因为可能比较的是字符串, 也可能是数字
多表操作的级联相关
一对多配置
数据库表之间难免会出现彼此的约束, 如商品分类表和商品表之间, 就是典型的一对多的关系, 同一个分类下有多种不同的商品, 下面就是 jpa 如何通过注解控制一对多的关系
双方都有一个彼此之间的引用, 如在 one 的一方, 维护着多的一方的一个集合, 一般使用 HashSet, 而在 many 的一方维护着一的一方的引用
在一的一方使用注解 @OneToMany
在多的一方使用注解 @ManyToOne
维护主键的一方需要使用
@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 | 延迟加载
来源: http://www.bubuko.com/infodetail-3122137.html