ORM 概述

ORM(Object-Relational Mapping) 表示对象关系映射。在面向对象的软件开发中,通过
ORM,就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联,
操作对象就可以直接操作数据库数据,就可以说这套程序实现了 ORM 对象关系映射
简单的说:ORM 就是建立实体类和数据库表之间的关系,从而达到操作实体类就相当于操作数据库
表的目的。

为什么使用 ORM

当实现一个应用程序时(不使用 O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存数
据、修改数据、删除数据,而这些代码都是重复的。而使用 ORM 则会大大减少重复性代码。对象关系映射(Object Relational Mapping,简称 ORM),主要实现程序对象到关系数据库数据的映射。

常见 ORM

常见的 orm 框架:Mybatis(ibatis)、Hibernate、Jpa

hibernate 与 与 JPA 的概述

hibernate

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与
数据库表建立映射关系,是一个全自动的 orm 框架,hibernate 可以自动生成 SQL 语句,自动执行,使得 Java
程序员可以随心所欲的使用对象编程思维来操纵数据库。

JPA 概述

JPA 的全称是 Java Persistence API, 即 Java 持久化 API,是 SUN 公司推出的一套基于 ORM 的规范,
内部是由一系列的接口和抽象类构成。
JPA 通过 JDK 5.0 注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

JPA 的优势

  1. 标准化
    JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,
    提供相同的访问 API,这保证了基于 JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
  2. 容器级特性的支持
    JPA 框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企
    业应用发挥更大的作用。
  3. 简单方便
    JPA 的主要目标之一就是提供更加简单的编程模型:在 JPA 框架下创建实体和创建 Java 类一样简单,没
    有任何的约束和限制,只需要使用 javax.persistence.Entity 进行注释,JPA 的框架和接口也都非常简单,
    没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA 基于非侵入式原则设计,因此可以很容易
    的和其它框架或者容器集成
  4. 查询能力
    JPA 的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是
    Hibernate HQL 的等价物。JPA 定义了独特的 JPQL(Java Persistence Query Language),JPQL 是 EJB
    QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新
    和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
  5. 高级特性
    JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开
    发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。

    JPA 与 与 hibernate 的关系

    JPA 规范本质上就是一种 ORM规范,注意不是 ORM框架——因为JPA 并未提供ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由服务厂商来提供实现。
    图片.png
    JPA 和 Hibernate 的关系就像 JDBC 和 JDBC 驱动的关系,JPA 是规范,Hibernate 除了作为 ORM 框架之
    外,它也是一种 JPA 实现。JPA 怎么取代 Hibernate 呢?JDBC 规范可以驱动底层数据库吗?答案是否定的,也
    就是说,如果使用 JPA 规范进行数据库操作,底层需要 hibernate 作为其实现类完成数据持久化工作。

    springData-Jpa的使用

    (1)引入依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-data-jpa</artifactId>
    4. </dependency>
    5. <!-- 同时操作数据库,必然少不了数据库驱动包 -->
    6. <dependency>
    7. <groupId>mysql</groupId>
    8. <artifactId>mysql-connector-java</artifactId>
    9. </dependency>

    (2)提供数据库配置

    spring:
     application:
         name: tensquare-user #指定服务名
    datasource:
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.108.140:3306/tensquare_user?characterEncoding=UTF8
        username: root
        password: root
    jpa:
       database: MySQL
       show-sql: true
    

    (3)创建数据访问接口

    public interface UserDao extends JpaRepository<User, String>, JpaSpecificationExecutor<User> {}
    
    这里有几个关键点:
  • 继承JpaRepository并在范型上指定实体类类型以及主键Id的数据类型
  • 如果需要用到分页,还需继承JpaSpecificationExecutor同样范型传实体类类型
  • 通过接口就能直接使用一些简单的方法了,同时如果原有方法不够用了,spring-data-jap可以通过一套通过方法名命规则来定义方法,实现一下更高级的数据操作
  • 当原生的方法,或者通过方法名命规则不能达到业务需求的时候,也可以通过编写原生sql语句的方式来实现数据的访问:如下

    通过使用@Query注解,value属性来指定要执行的Sql语句,nativeQuery属性设为true表示来支持自定义sql语    句的执行,并且需要再使用@Modiying注解,另外方法参数要与SQL语句中的占位符对应
    
    /**
    * 跟新当前用户关注数
    *
    * @param num
    * @param userId
    */
    @Modifying
    @Query(value = "UPDATE tb_user SET followcount = followcount + ? WHERE id = ?", nativeQuery = true)
    void updateFollowCount(int num, String userId);
    

    (4)提供关系映射实体类

  • 关系映射实体类实现序列化接口

  • 在实体类上使用@Entity注解,@Table注解name属性指定数据库表名
  • 域(实体类属性)主键Id也可能不叫id属性,但只要是主键就行,需使用@Id注解声明
  • 其它域(属性)如果属性名和数据库表字段一直,可以啥也不用写,如果不一致,需要使用@Column注解name属性指定数据库字段名的方式来和实体类中的属性做映射

    (5)分页查询

    /**
    * 条件分页查询
    *
    * @param label
    * @param page
    * @param size
    * @return
    */
    @RequestMapping(value = "/search/{page}/{size}")
    public Result pageQuery(@RequestBody Label label, @PathVariable int page, @PathVariable int size) {
      Page<Label> pageData = labelService.pageQuery(label, page, size);
      return new Result(true, StatusCode.OK, "查询成功!"
              , new PageResult<>(pageData.getTotalElements(), pageData.getContent()));
    }
    /**
    * 按条件分页查询
    * @param label
    * @param page
    * @param size
    * @return
    */
    public Page<Label> pageQuery(Label label, int page, int size) {
      Pageable pageable = PageRequest.of(page - 1, size);
      return labelDao.findAll((Specification<Label>) (root, criteriaQuery, criteriaBuilder) -> {
          // 定义一个集合用来封装条件
          List<Predicate> paramList = new ArrayList<>();
          // 过滤标签名称条件
          if (StringUtils.isNotBlank(label.getLabelName())) {
              Predicate predicate = criteriaBuilder.like(root.get("labelName").as(String.class), "%" + label.getLabelName() + "%");
              paramList.add(predicate);
          }
          // 过滤状态state条件
          if (StringUtils.isNotBlank(label.getState())) {
              Predicate predicate = criteriaBuilder.equal(root.get("state").as(String.class), label.getState());
              paramList.add(predicate);
          }
          // 将条件集合转为数组
          Predicate[] predicates = new Predicate[paramList.size()];
          paramList.toArray(predicates);
          return criteriaBuilder.and(predicates);
      }, pageable);
    }
    
    总结:如上代码案例可以看出,使用SpringData-JPA进行条件查询,只需要通过Specification对象进行过滤条件的封装,如果还需要分页的话,再传递一个Pageable 类型的对象,而Pageable对象可以通过PageRequest.of()方法得到,最终返回一个Page类型的对象