• 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重新复习

    呆更新

  • 相关阅读:
    【23设计模式】总结
    【JAVA】内部类,内部接口
    【JAVA】接口
    【JAVA】抽象类,抽象方法
    【JAVA】类加载器
    【JAVA】枚举
    【JAVA】序列化
    【JAVA】异常笔记
    汇编笔记_第十一章
    汇编笔记_第十章
  • 原文地址:https://www.cnblogs.com/zhaoyuan72/p/14452351.html
Copyright © 2020-2023  润新知