专栏首页Linyb极客之路springboot实战之ORM整合(JPA篇)

springboot实战之ORM整合(JPA篇)

前言

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

本文分享自微信公众号 - Linyb极客之路(gh_c420b2cf6b47),作者:linyb极客之路

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • springboot实战之总结篇

    springboot实战系列暂时先更新到这边,下边是对之前springboot实战文章做一个汇总

    lyb-geek
  • web安全浅析

    写这篇文章的初衷,主要由于自己所负责的项目有这方面的需求,就简要提一提web安全方面的一些知识 ? 一.web安全的兴起 web攻击技...

    lyb-geek
  • prometheus-spring-boot-starter一个管理异常通知的神奇starter

    对于工程的开发,必然会伴随着各种bug,工程量越大,出现bug的频率也会越高。一般对于代码量较小的工程来说,一个人可能就足够去做开发与维护;但是对于代码量较大的...

    lyb-geek
  • 基于centos的rootfs 创建自己的base image

    前面介绍了dockerfile的常用命令,以及如何构建自己的centos rootfs, 这里用前面学习的内容,来创建 自己的base image .

    qsjs
  • IDG数字化商业转型报告:为什么你的转型总失败?

    人称T客
  • 遗传算法求解混合流水车间调度问题(附C++代码)

    各位读者大家好,好久没有介绍算法的推文了,感觉愧对了读者们热爱学习的心灵。于是,今天我们带来了一个神奇的优化算法——遗传算法!

    用户1621951
  • 转载 | 遗传算法求解混合流水车间调度问题(附C++代码)

    各位读者大家好,好久没有介绍算法的推文了,感觉愧对了读者们热爱学习的心灵。于是,今天我们带来了一个神奇的优化算法——遗传算法!

    短短的路走走停停
  • 虚拟机更换JDK版本步骤(Hadoop集群)

    如果1.7版本jdk 是直接通过外部的压缩包放到虚拟机中, 则需要修改环境变量/etc/proflie

    时间静止不是简史
  • Spark踩坑记:共享变量

    如果我们想在节点之间共享一份变量,比如一份公共的配置项,该怎么办呢?Spark为我们提供了两种特定的共享变量,来完成节点间变量的共享。 本文首先简单的介绍spa...

    肖力涛
  • 所以,为什么优秀的程序员会不断离去?

    聘请一大帮初级程序员是一回事,而如何留下这些初级程序员的导师则是另外一回事。 无论是大学生还是正在找工作的有经验程序员,都一直知道要不断学习编码。尽管来自行业内...

    CSDN技术头条

扫码关注云+社区

领取腾讯云代金券