zoukankan      html  css  js  c++  java
  • Jpa 笔记

    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 延迟加载
  • 相关阅读:
    linux下so动态库一些不为人知的秘密(中二)
    linux下so动态库一些不为人知的秘密(中)
    linux下so动态库一些不为人知的秘密(上)
    Linux下gcc编译控制动态库导出函数小结
    解决Linux动态库版本兼容问题
    MySQL按天,按周,按月,按时间段统计【转载】
    MySQL统计函数记录——按月、按季度、按日、时间段统计以及MySQL日期时间函数大全
    RequestMapping中produces属性作用
    出现 java.net.ConnectException: Connection refused 异常的原因及解决方法
    Springboot应用中@EntityScan和@EnableJpaRepositories的用法
  • 原文地址:https://www.cnblogs.com/ZhuChangwu/p/11178879.html
Copyright © 2011-2022 走看看