zoukankan      html  css  js  c++  java
  • Spring Data 、Spring Data JPA 、Hibernate之间的关系及SpringDataJPA简单使用

    该博客内容多为自己学习的记录

    本文转载自:https://www.jianshu.com/p/c23c82a8fcfc

    1.SpringData Jap,Hibernate,Jpa三者之间的关系

    1.1 JPA和ORM框架(如Hibernate)之间的关系

    Jpa是sun公司定义的一种ORM(Object relational mapping)规范, sun公司定义了一些编程的接口,由服务厂商来提供实现,常见ORM框架由Hibernate,TopLink等。

    他们之间的关系:

    image-20191230134322982

    JPA和Hibernate的关系如JDBC和JDBC驱动一样,JPA是规范,Hibernate除了做了ORM框架之外,也是一种JPA的实现。

    1.2 JPA概述

    JPA是Java Persistence API的简称,中文名为Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

    JPA规范包含以下三个方面的内容:

    1. 一套API标准。在javax.persistence的包下面,用来操作实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开发者从烦琐的JDBC和SQL代码中解脱出来。
    2. 面向对象的查询语言:Java Persistence QueryLanguage(JPQL)。这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合
    3. ORM(object/relational metadata)元数据的映射。JPA支持XML和JDK5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。

    1.3 Spring Data JPA与JPA规范的关系

    SpringData JPA是在JPA的基础提供了Repository层(dao层)的实现,可以自己选择使用什么ORM框架

    好处:不同ORM框架之间切换需要编写的代码都是有差异的,使用SpringData JPA能使使用不同的ORM框架之间切换时不需要再更改代码。

    可以理解为:Spring Data JPA基于JPA规范再次封装,

    image-20191230134923179

    2. Springboot整合SpringDataJpa

    2.1 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    

    2.2 相关配置

    server:
      port: 8080
      servlet:
        context-path: /
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
        username: root
        password: mysql123
        driver-class-name: com.mysql.jdbc.Driver
        
      jpa:
        database: MySQL
        database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
        show-sql: true
        hibernate:
          ddl-auto: update
    

    ddl-auto:

    • create:每次运行程序时,都会重新创建表,故而数据会丢失

    • create-drop:每次运行程序时会先创建表结构,然后待程序结束时清空表

    • upadte:每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新(推荐使用)

    • validate:运行程序会校验数据与数据库的字段类型是否相同,字段不同会报错

    • none: 禁用DDL处理

    3. 简单的REST CRUD实例

    3.1 实体类

    package com.example.springbootjpa.entity;
    
    @Entity
    @Table(name = "tb_user")
    @Data
    public class User {
    
        @Id
        @GenericGenerator(name = "idGenerator", strategy = "uuid")
        @GeneratedValue(generator = "idGenerator")
        private String id;
    
        @Column(name = "username", unique = true, nullable = false, length = 64)
        private String username;
    
        @Column(name = "password", nullable = false, length = 64)
        private String password;
    
        @Column(name = "email", length = 64)
        private String email;
    
    }
    
    

    主键采用UUID策略
    @GenericGenerator是Hibernate提供的主键生成策略注解,注意下面的@GeneratedValue(JPA注解)使用generator = "idGenerator"引用了上面的name = "idGenerator"主键生成策略

    一般简单的Demo示例中只会使用@GeneratedValue(strategy = GenerationType.IDENTITY)这种主键自增的策略,而实际数据库中表字段主键类型很少是int型的

    JPA自带的几种主键生成策略

    • TABLE: 使用一个特定的数据库表格来保存主键
    • SEQUENCE: 根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要与generator一起使用,generator 指定生成主键使用的生成器(可能是orcale中自己编写的序列)
    • IDENTITY: 主键由数据库自动生成(主要是支持自动增长的数据库,如mysql)
    • AUTO: 主键由程序控制,也是GenerationType的默认值

    3.2Dao层

    package com.example.springbootjpa.repository;
    
    public interface UserRepository extends JpaRepository<User, String> {
    }
    

    3.3 Controller层

    在这个demo中省略Service层

    package com.example.springbootjpa.controller;
    
    @RestController
    @RequestMapping("/users")
    public class UserController {
    
        @Autowired
        private UserRepository userRepository;
    
        @PostMapping()
        public User saveUser(@RequestBody User user) {
            return userRepository.save(user);
        }
    
        @DeleteMapping("/{id}")
        public void deleteUser(@PathVariable("id") String userId) {
            userRepository.deleteById(userId);
        }
    
        @PutMapping("/{id}")
        public User updateUser(@PathVariable("id") String userId, @RequestBody User user) {
            user.setId(userId);
            return userRepository.saveAndFlush(user);
        }
    
        @GetMapping("/{id}")
        public User getUserInfo(@PathVariable("id") String userId) {
            Optional<User> optional = userRepository.findById(userId);
            return optional.orElseGet(User::new);
        }
    
        @GetMapping("/list")
        public Page<User> pageQuery(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
                                    @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
            return userRepository.findAll(PageRequest.of(pageNum - 1, pageSize));
        }
    
    }
    

    4. SpringData Jpa 使用详解

    4.1 SpringData查询方法

    步骤:

    1. 声明一个接口继承自Repository或者Repository的一个子接口,对于SpringData Jpa通常使用JpaRepository,如:

      interface PersonRepository extends Repository<Person, Long> { … }
      
    2. 在接口给中声明查询方法

      interface PersonRepository extends Repository<Person, Long> {
        List<Person> findByLastname(String lastname);
      }
      

    4.2 定义Repository接口

    4.2.1 选择性暴露CRUD方法

    4.2.1.方法1

    一种方法是定义一个BaseRepository接口继承Repository接口,并从CrudRepository中copy你想暴露的CRUD方法

    //注意:MyBaseRepository上面加了@NoRepositoryBean注解
    @NoRepositoryBean
    public interface MyBaseRepository<T, ID> extends Repository<T, ID> {
    
        Optional<T> findById(ID id);
    
        <S extends T> S save(S entity);
    
    }
    
    public interface UserRepository2 extends MyBaseRepository<User, String> {
    }
    

    4.2.2 方法二:

    另一种方法是使用@RepositoryDefinition注解,并从CrudRepository中copy你想暴露的CRUD方法

    @RepositoryDefinition(domainClass = User.class, idClass = String.class)
    public interface UserRepository3 {
    
        Optional<User> findById(String id);
    
        User save(User user);
    
    }
    

    4.3 Repository方法的Null值的处理

    从Spring Data2.0开始对于返回单个聚合实例的CRUD方法可以使用java8 Optional接口作为方法返回值来表明可能存在的缺省值,典型示例为CrudRepository的findById方法
    另外Spring也提供了几个注解来处理Null值

    • @NonNullApi: 在包级别使用来声明参数和返回值不能为Null
    • @NonNull: 在参数或返回值上使用,当它们不能为Null时(如果在包级别上使用了@NonNullApi注解则没有必要再使用@NonNull注解了)
    • @Nullable: 在参数或返回值上使用,当它们可以为Null时

    5. 查询方法

    5.1 查询创建Query Creation

    Spring Data Jpa通过解析方法名创建查询,框架在进行方法名解析时,会先把方法名多余的前缀find…By, read…By, query…By, count…By以及get…By截取掉,然后对剩下部分进行解析,第一个By会被用作分隔符来指示实际查询条件的开始。 我们可以在实体属性上定义条件,并将它们与And和Or连接起来,从而创建大量查询:

    User findByUsername(String username);
    
    List<User> findByUsernameIgnoreCase(String username);
    
    List<User> findByUsernameLike(String username);
    
    User findByUsernameAndPassword(String username, String password);
    
    User findByEmail(String email);
    
    List<User> findByEmailLike(String email);
    
    List<User> findByIdIn(List<String> ids);
    
    List<User> findByIdInOrderByUsername(List<String> ids);
    
    void deleteByIdIn(List<String> ids);
    
    Long countByUsernameLike(String username);
    
    Keyword Sample JPQL snippet
    And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
    Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
    Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
    Between findByStartDateBetween … where x.startDate between ?1 and ?2
    LessThan findByAgeLessThan … where x.age < ?1
    LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
    GreaterThan findByAgeGreaterThan … where x.age > ?1
    GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
    After findByStartDateAfter … where x.startDate > ?1
    Before findByStartDateBefore … where x.startDate < ?1
    IsNull findByAgeIsNull … where x.age is null
    IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
    Like findByFirstnameLike … where x.firstname like ?1
    NotLike findByFirstnameNotLike ... findByFirstnameNotLike
    StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
    EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
    Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
    OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
    Not findByLastnameNot … where x.lastname <> ?1
    In findByAgeIn(Collection ages) … where x.age in ?1
    NotIn findByAgeNotIn(Collection ages) … where x.age not in ?1
    True findByActiveTrue() … where x.active = true
    False findByActiveFalse() … where x.active = false
    IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

    具体Spring Data Jpa对方法名的解析规则可参看官方文档

    5.2 限制查询结果

    Spring Data Jpa支持使用firsttop以及Distinct 关键字来限制查询结果,如:

    User findFirstByUsernameOrderByUsernameAsc(String username);
    
    List<User> findTop10ByUsername(String username, Sort sort);
        
    List<User> findTop10ByUsername(String username, Pageable pageable);
    

    5.3 自定义查询Using@Query

    @Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JPQL 查询语句即可

    @Query("select u from User u where u.email = ?1")
    User getByEmail(String eamil);
    
    @Query("select u from User u where u.username = ?1 and u.password = ?2")
    User getByUsernameAndPassword(String username, String password);
    
    @Query("select u from User u where u.username like %?1%")
    List<User> getByUsernameLike(String username);
    

    5.4 使用命名参数 Using Named Parameters

    默认情况下,Spring Data JPA使用基于位置的参数绑定,如前面所有示例中所述。 这使得查询方法在重构参数位置时容易出错。 要解决此问题,可以使用@Param注解为方法参数指定具体名称并在查询中绑定名称,如以下示例所示:

    @Query("select u from User u where u.id = :id")
    User getById(@Param("id") String userId);
    
    @Query("select u from User u where u.username = :username or u.email = :email")
    User getByUsernameOrEmail(@Param("username") String username, @Param("email") String email);
    

    5.5 Using SpEL Expressions

    从Spring Data JPA release 1.4开始,Spring Data JPA支持名为entityName的变量。 它的用法是select x from #{#entityName} x。 entityName的解析方式如下:如果实体类在@Entity注解上设置了name属性,则使用它。 否则,使用实体类的简单类名。为避免在@Query注解使用实际的实体类名,就可以使用#{#entityName}进行代替。如以上示例中,@Query注解的查询字符串里的User都可替换为#{#entityName}

    @Query("select u from #{#entityName} u where u.email = ?1")
    User getByEmail(String eamil);
    

    5.6 原生查询 Native Queries

    @Query注解还支持通过将nativeQuery标志设置为true来执行原生查询,同样支持基于位置的参数绑定及命名参数,如:

    @Query(value = "select * from tb_user u where u.email = ?1", nativeQuery = true)
    User queryByEmail(String email);
    
    @Query(value = "select * from tb_user u where u.email = :email", nativeQuery = true)
    User queryByEmail(@Param("email") String email);
    

    注意:Spring Data Jpa目前不支持对原生查询进行动态排序,但可以通过自己指定计数查询countQuery来使用原生查询进行分页、排序,如:

    @Query(value = "select * from tb_user u where u.username like %?1%",
                countQuery = "select count(1) from tb_user u where u.username = %?1%",
                nativeQuery = true)
    Page<User> queryByUsernameLike(String username, Pageable pageable);
    

    原生查询就是可以直接在数据库中执行的sql语句,如果不加nativeQuery=ture,则sql语句中对应的可能不是数据库表中的字段名,而是对应的实体名的字段名。

    5.7 分页查询与排序

    Spring Data Jpa可以在方法参数中直接传入PageableSort来完成动态分页或排序,通常Pageable或Sort会是方法的最后一个参数,如:

    @Query("select u from User u where u.username like %?1%")
    Page<User> findByUsernameLike(String username, Pageable pageable);
    
    @Query("select u from User u where u.username like %?1%")
    List<User> findByUsernameAndSort(String username, Sort sort);
    

    那调用repository方法时传入什么参数呢?
    对于Pageable参数,在Spring Data 2.0之前我们可以new一个org.springframework.data.domain.PageRequest对象,现在这些构造方法已经废弃,取而代之Spring推荐我们使用PageRequest的of方法

    new PageRequest(0, 5);
    new PageRequest(0, 5, Sort.Direction.ASC, "username");
    new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "username"));
            
    PageRequest.of(0, 5);
    PageRequest.of(0, 5, Sort.Direction.ASC, "username");
    PageRequest.of(0, 5, Sort.by(Sort.Direction.ASC, "username"));
    

    注意:*Spring Data PageRequest的page参数是从0开始的 zero-based page index*

    对于Sort参数,同样可以new一个org.springframework.data.domain.Sort,但推荐使用Sort.by方法

    6 .自定义修改,删除Modifying Queries

    单独使用@Query注解只是查询,如涉及到修改、删除则需要再加上@Modifying注解,如:

    @Transactional()
    @Modifying
    @Query("update User u set u.password = ?2 where u.username = ?1")
    int updatePasswordByUsername(String username, String password);
    
    @Transactional()
    @Modifying
    @Query("delete from User where username = ?1")
    void deleteByUsername(String username);
    

    注意:Modifying queries can only use void or int/Integer as return type!

    7. 多表查询

    级联查询,结合mybatis重新复习

    呆更新

  • 相关阅读:
    LeetCode 40. 组合总和 II(Combination Sum II)
    LeetCode 129. 求根到叶子节点数字之和(Sum Root to Leaf Numbers)
    LeetCode 60. 第k个排列(Permutation Sequence)
    LeetCode 47. 全排列 II(Permutations II)
    LeetCode 46. 全排列(Permutations)
    LeetCode 93. 复原IP地址(Restore IP Addresses)
    LeetCode 98. 验证二叉搜索树(Validate Binary Search Tree)
    LeetCode 59. 螺旋矩阵 II(Spiral Matrix II)
    一重指针和二重指针
    指针的意义
  • 原文地址:https://www.cnblogs.com/zhaoyuan72/p/14452351.html
Copyright © 2011-2022 走看看