springboot實戰之ORM整合(JPA篇)

  • 2019 年 10 月 4 日
  • 筆記

前言

1、什麼是ORM

對象關係映射(Object Relational Mapping,簡稱ORM)是通過使用描述對象和資料庫之間映射的元數據,將面向對象語言程式中的對象自動持久化到關係資料庫中。簡單來說就是將資料庫表與java實體對象做一個映射

2、ORM的優缺點

優點:符合面向對象編程;技術與業務解耦,開發時不需要關注資料庫的連接與釋放;

缺點:orm會犧牲程式的執行效率和會固定思維模式

3、orm主流框架

hibernate(jpa)、mybatis/mybatis-plus(半自動orm)。今天主要基於jpa規範再次封裝抽象實現的SpringData JPA。在介紹SpringData JPA之前,先介紹一下jpa

什麼是jpa

JPA是Java Persistence API的簡稱,中文名為Java持久層API,是JDK 5.0註解或XML描述對象-關係表的映射關係,並將運行期的實體對象持久化到資料庫中。

JPA包括以下3方面的內容:

(1)一套API標準。在javax.persistence的包下面,用來操作實體對象,執行CRUD操作,框架在後台替代我們完成所有的事情,開發者從煩瑣的JDBC和SQL程式碼中解脫出來。

(2)面向對象的查詢語言:Java Persistence QueryLanguage(JPQL)。這是持久化操作中很重要的一個方面,通過面向對象而非面向資料庫的查詢語言查詢數據,避免程式的SQL語句緊密耦合。

(3)ORM(object/relational metadata)元數據的映射。JPA支援XML和JDK5.0註解兩種元數據的形式,元數據描述對象和表之間的映射關係,框架據此將實體對象持久化到資料庫表中。

demo實戰

通過demo示例可以了解或者掌握以下內容

  • 自動建表
  • 建表引擎改為InnoDB
  • 利用JpaSpecificationExecutor、JpaRepository來實現帶複雜查詢分頁,以及常規增刪改查
  • 重寫SimpleJpaRepository的save方法,使其按需更新空值屬性

1、pom.xml引入相應包

<dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-data-jpa</artifactId>      </dependency>     <dependency>        <groupId>com.alibaba</groupId>        <artifactId>druid-spring-boot-starter</artifactId>      </dependency>      <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>      </dependency>

2、application.yml配置

spring:    datasource:      name: druidDataSource      type: com.alibaba.druid.pool.DruidDataSource      druid:        driver-class-name: com.mysql.cj.jdbc.Driver        url: jdbc:mysql://localhost:3306/springboot-learning?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&serverTimezone=UTC        username: root        password: config.file=classpath:druid.properties        filters: stat,log4j,config        max-active: 100        initial-size: 1        max-wait: 60000        min-idle: 1        db-type: mysql        time-between-eviction-runs-millis: 60000        min-evictable-idle-time-millis: 300000        validation-query: select 'x'        test-while-idle: true        test-on-borrow: false        test-on-return: false        pool-prepared-statements: true        max-open-prepared-statements: 50        max-pool-prepared-statement-per-connection-size: 20        connection-properties: config.file=classpath:druid.properties   # 啟用加密,配置公鑰。        filter:          config:            enabled: true      jpa:      show-sql: true      hibernate:        ddl-auto: update      properties:        hibernate.format_sql: true      database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

jpa一些比較核心配置屬性介紹

jpa.hibernate.ddl-auto參數的作用主要用於:自動創建|更新|驗證資料庫表結構。如果不是此方面的需求建議取值設為none

可選參數

  • create 啟動時刪資料庫中的表,然後創建,退出時不刪除數據表
  • create-drop 啟動時刪資料庫中的表,然後創建,退出時刪除數據表,如果表不存在報錯
  • update 如果啟動時表格式不一致則更新表,原有數據保留
  • validate 項目啟動表結構進行校驗 如果不一致則報錯

spring.jpa.database-platform這個參數的主要用於指定默認的資料庫存儲引擎,在springboot2版本中,默認mysql資料庫存儲引擎的是MyISAM,通過把取值設置為org.hibernate.dialect.MySQL5InnoDBDialect,就可以把默認的存儲引擎切換為InnoDB

3、創建entity

@Entity  @Table(name="order_log")  @AllArgsConstructor  @NoArgsConstructor  @Data  @Builder  @ToString(callSuper = true)  @EqualsAndHashCode(callSuper = true)  @IgnoreNullValue  public class OrderLog extends BaseEntity {      @Column(name="order_id")    private Long orderId;      @Column(name="order_content",length = 2000)    private String orderContent;      @Column(name="order_name")    private String orderName;      }    父類  @Data  @AllArgsConstructor  @NoArgsConstructor  @MappedSuperclass  public class BaseEntity {      @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private Long id;      @CreationTimestamp    @Column(name="create_date",updatable = false)    private Date createDate;      @UpdateTimestamp    @Column(name="update_date")    private Date updateDate;    }

entity註解解釋

  • @Entity 聲明類為實體或表。
  • @Table 聲明表名
  • @Id 指定的類的屬性,用於識別(一個表中的主鍵)。
  • @GeneratedValue 指定如何標識屬性可以被初始化,例如自動、手動、或從序列表中獲得的值
  • @Column 指定持久屬性欄屬性。
  • @MappedSupperclass 用來申明一個超類,繼承這個類的子類映射時要映射此類中的欄位
  • @CreationTimestamp 資料庫做插入時,自動填充時間
  • @UpdateTimestamp 資料庫有更新時,自動更新時間

本例只用一個entity來演示,因此沒有涉及到表與表的關聯,常用表與表之間的關聯註解如下

  • @JoinColumn 指定一個實體組織或實體的集合。這是用在多對一和一對多關聯。
  • @ManyToMany 定義了連接表之間的多對多一對多的關係。
  • @ManyToOne 定義了連接表之間的多對一的關係。
  • @OneToMany 定義了連接表之間存在一個一對多的關係。
  • @OneToOne 定義了連接表之間有一個一對一的關係。

4、創建Repository

通過繼承JpaRepository可以實現增刪改查,包括簡單分頁,通過繼承JpaSpecificationExecutor可以實現複雜查詢

public interface OrderLogRepository extends JpaSpecificationExecutor<OrderLog>,JpaRepository<OrderLog,Long> {    }

在使用Repository存在一個坑點,更新時,調用其提供的save方法會導致null屬性覆蓋到資料庫。即如果要更新的bean中的欄位,存在null值,原生的SimpleJpaRepository進行更新操作時,會把null值更新進資料庫,而有時候業務上我們不需要這樣,因此可以重寫SimpleJpaRepository方法,其核心程式碼如下

/**       * 通用save方法 :新增/選擇性更新       */      @Override      @Transactional      public <S extends T> S save(S entity) {          //獲取ID          ID entityId = (ID) entityInformation.getId(entity);          Optional<T> optionalT;          if (entityId == null) {                //標記為新增數據              optionalT = Optional.empty();          } else {              //若ID非空 則查詢最新數據              optionalT = findById(entityId);          }          //若根據ID查詢結果為空          if (!optionalT.isPresent()) {              em.persist(entity);//新增              return entity;          } else {              if(entity.getClass().isAnnotationPresent(IgnoreNullValue.class)){                  IgnoreNullValue nullValue = entity.getClass().getAnnotation(IgnoreNullValue.class);                  if(nullValue.value()){                      //1.獲取最新對象                      T target = optionalT.get();                      //2.將非空屬性覆蓋到最新對象                      BeanUtil.copyNotNUllProperties(entity,target);                      //3.更新非空屬性                      em.merge(target);                      return (S) target;                  }else{                      em.merge(entity);                  }              }else{                  em.merge(entity);              }                return entity;          }      }

@IgnoreNullValue這個註解是用來指定是否要忽略空值欄位。

在啟動類上指定@EnableJpaRepositories註解,並將repositoryBaseClass設置為CustomSimpleJpaRepository,改成我們重寫後的Repository,默認repositoryBaseClass為SimpleJpaRepository

@SpringBootApplication  @EnableJpaRepositories(basePackages = {"com.github.lybgeek.orm.jpa.repository"},repositoryBaseClass = CustomSimpleJpaRepository.class)  public class OrmApplication {      public static void main( String[] args ) {            SpringApplication.run(OrmApplication.class,args);      }  }

5、分頁查詢程式碼示例

public PageResult<OrderLog> pageOrderLogs(PageQuery<OrderLog> pageQuery) {        Sort sort = new Sort(Direction.DESC,"createDate");      Pageable pageable = PageRequest.of(pageQuery.getPageNo() - 1,pageQuery.getPageSize(),sort);        Specification<OrderLog> specification = (Specification<OrderLog>) (root, criteriaQuery, criteriaBuilder) -> {          OrderLog queryParams = pageQuery.getQueryParams();        if(queryParams != null){          List<Predicate> predicates = new ArrayList<>();          if(ObjectUtils.isNotEmpty(queryParams.getOrderId())){            Path<Long> orderId = root.get("orderId");            Predicate orderIdPredicate = criteriaBuilder.equal(orderId,queryParams.getOrderId());            predicates.add(orderIdPredicate);          }            if(StringUtils.isNotBlank(queryParams.getOrderName())){            Path<String> orderName = root.get("orderName");            Predicate orderNamePredicate = criteriaBuilder.like(orderName,"%"+queryParams.getOrderName()+"%");            predicates.add(orderNamePredicate);          }            if(CollectionUtils.isNotEmpty(predicates)){            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));          }          }            return null;      };        Page<OrderLog> orderLogPage = orderLogRepository.findAll(specification,pageable);      if(ObjectUtils.isNotEmpty(orderLogPage)){        return PageUtil.INSTANCE.getPage(orderLogPage);      }        return null;    }

文中相關概念引用文檔

https://blog.csdn.net/u014131617/article/details/85813091 https://blog.csdn.net/xudailong_blog/article/details/84336629

總結

本文主要是介紹springdata jpa一些常規基本用法,只做入門,其具體更詳細的內容,可以查看官網介紹

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/

另外由於篇幅原因,其項目中包含的一些雜項諸如druid加密,flyway資料庫版本管理,do和dto的相互轉換,本文就不再論述,感興趣的朋友可以查看我下邊貼出來的demo鏈接。下篇會繼續介紹mybatis、mybatisplus的基本使用

demo鏈接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-orm