• SpringBoot整合Spring Data JPA


    1 Spring Data JPA

    1.1 简介

    1.1.1 JPA

    JPAJava Persistence API)即java持久化API,它的出现主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来,易于使用,伸缩性强。

    JPA诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,是JDK为了实现ORM的天下归一,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate是一支很大的部队,使用很广泛,也很方便,能力也很强,同时Hibernate也是和JPA整合的比较良好,我们可以认为JPA是标准,事实上也是,JPA几乎都是接口,实现都是Hibernate在做,宏观上面看,在JPA的统一之下Hibernate很良好的运行

    总的来说,JPA包括以下三方面的技术:

    • ORM映射元数据:支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系
    • API:操作实体对象来执行CRUD操作
    • 查询语言:通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合

    1.1.2 Spring Data JPA

    Spring Data JPA的基本介绍:
    Spring Data JPASpring Data家族的一部分,可以轻松实现基于JPA的存储库。此模块处理对基于JPA的数据访问层的增强支持。它使构建使用数据访问技术的Spring驱动应用程序变得更加容易。底层是使用hibernate实现

    在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦。必须编写太多样板代码来执行简单查询以及执行分页和审计。Spring Data JPA旨在减少实际需要的工作量来显著改善数据访问层的实现

    1.2 配置文件

    pom依赖
    SpringBoot整合Spring Data JPA
    导入依赖:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-jpaartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    数据库配置:

    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/chapter05?characterEncoding=utf8&serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=root
    #表示Jpa对应的数据库是mysql
    spring.jpa.show-sql=true
    #项目启动时根据实体类更新数据库中的表
    spring.jpa.hibernate.ddl-auto=update
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其中:spring.jpa.hibernate.ddl-auto,可选参数:

    • create:每次运行程序时,都会重新创建表,故而数据会丢失
    • create-drop:每次运行程序时会先创建表结构,然后程序结束时清空表
    • update:每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新(推荐使用)
    • validate:运行程序会校验数据于数据库的字段类型是否相同,字段不同会报错
    • none:禁用DDL处理

    1.3 操作使用JPA

    1.3.1 实体类相关

    创建实体类:

    import lombok.Data;
    import javax.persistence.*;
     
    @Data
    @Entity
    @Table(name = "t_book")
    public class Book {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
        @Column(name = "book_name")
        private String name;
        @Column(name = "book_author")
        private String author;
        private Float price;
        @Transient
        private String description;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    注解解释:

    • @Entity:注解表示该类是一个实体类,在项目启动时会根据该类自动生成一张表,表的名称即@Entity注解中name的值,如果不配置name,默认表名为类名
      指定实体名称(表名):
      • 没有指定name属性且没有使用@Table,命名为类名生成
      • 指定name属性且没有使用@Table,命名为name属性value
      • 指定name属性且使用了@Table指定name,命名以@Tablenamevalue
    • @Id:所有的实体类都要有的主键,@Id注解表示该属性是一个主键
    • @GeneratedValue:注解表示主键自动生成,strategy则表示主键的生成策略
      JPA自带的几种主键生成策略:
      • TABLE:使用一个特定的数据库表格来保存主键
      • SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要于generator一起使用,generator指定生成主键的生成器
      • IDENTITY:主键由数据库自动生成(主要支持自动增长的数据库,如mysql)
      • AUTO:主键由程序控制,也是GenerationType的默认值,mysql不支持,会报错:test.hibernate_sequence不存在
    • @Column:默认情况下,生成的表中字段的名称是实体类中属性的名称,通过@Column注解可以定制生成的字段的属性,name表示该属性对应的数据表中字段的名称,nullable表示该字段非空
    • @Transient:注解表示在生成数据库的表时,该属性被忽略,即不生成对应的字段

    1.3.2 Dao层

    1.3.2.1 基本示例

    创建Dao接口,继承JpaRepository,代码如下:

    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.data.repository.query.Param;
    import org.springframework.stereotype.Component;
     
    import java.util.List;
     
    @Component
    public interface BookDao extends JpaRepository<User,Integer> {
        //查询以某个字符开始的所有书
        List<Book> getBooksByAuthorStartingWith(String author);
        //查询单价大于某个值的所有书
        List<Book> getBooksByPriceGreaterThan(Float price);
        @Query(value = "select * from t_book where id=(select max(id) from t_book)",nativeQuery = true)
        Book getMaxIdBook();
        @Query("select b from t_book b where b.id>:id and b.author=:author")
        List<Book> getBookByIdAndAuthor(@Param("author") String author,@Param("id") Integer id);
        @Query("select b from t_book b where b.id)
        List<Book> getBookByIdAndName(String name,Integer id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    解释:

    • 自定义Dao继承自JpaRepositiory,需要两个参数T指当前需要映射得实体,ID指当前映射得实体中的主键类型。JpaRepositiory中提供了一些基本的数据操作方法,有基本的增删改查、分页查询、排序查询等
    • Spring Data JPA中,只要方法的定义符合既定规范,Spring Data就能分析出开发者意图,从而避免开发者定义SQL
    1.3.2.2 @Query注解

    @Query注解使用起来很简单,默认的属性是value,就是当前写的SQL语句,有时会用到nativeQuery属性,这个属性是用来标记当前的SQL是本地SQL,还是符合JPA语法规范的SQL
    这里需要解释一下本地SQL和JPA语法规范的SQL区别。

    • 本地SQL:根据实际使用的数据库类型写的SQL,这种SQL中使用到的一些语法格式不能被JPA解析以及可能不兼容其他数据库,这种SQL称为本地SQL,此时需要将nativeQuery属性设置为true,否则会报错。
    • JPA语法规范的SQL:往往这种SQL本身是不适用于任何数据库的,需要JPA将这种SQL转换成真正当前数据库所需要的SQL语法格式。

    注意JPA很好的一个特性就是用JPA语法规范写的SQL,会根据当前系统使用的数据库类型改变生成的SQL语法,兼容数据库类型的切换,如之前使用的是MySQL,现在换成Oracle,由于不同类型的数据库,SQL语法会有区别,如果使用的是mybatis,就需要手动去改SQL兼容Oracle,而JPA就不用,无缝对接。
    说明:很大的时候使用JPA感觉都是为了兼容后期可能会有数据库切换的问题,所以在使用JPA的时候,不要去使用本地SQL,这就违背了使用JPA的初衷,让nativeQuery属性保持默认值即可

    1.3.2.3 SQL传参

    根据这个例子再引出一些常用的东西,代码如下:

    //示例1
    @Query("select t from Device t where t.deviceSn=:deviceSn and t.deleteFlag=1")
    Device findExistDevice(@Param("deviceSn") String deviceSn);
    //示例2
    @Query("select t from Device t where t.deviceSn=:deviceSn and t.deviceType =:deviceType and t.deleteFlag=1")
    Device findExistDevice(@Param("deviceSn") String deviceSn,@Param("deviceType")Integer deviceType);
    //示例3
    @Query("select t from Device t where t.deviceSn=?1 and t.deviceType = ?2 and t.deleteFlag=1")
    Device findDevice(String deviceSn,Integer deviceType);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    SQL上使用占位符的两种方式:

    • 使用:后加变量的名称,需要使用@Param注解来指定变量名
    • 使用?后加方法参数的位置。就需要注意参数的位置。

    SQL语句中直接用实体类代表表名,因为在实体类中使用了@Table注解,将该实体类和表进行了关联

    1.3.2.4 @Modifying注解

    相信在正常的项目开发中都会涉及到修改数据信息的操作,如逻辑删除、封号、解封、修改用户名、头像等等。在使用JPA的时候,如果@Query涉及到update就必须同时加上@Modifying注解,注明当前方法是修改操作。

    如下代码:

    @Modifying
    @Query("update Device t set t.userName =:userName where t.id =:userId")
    User updateUserName(@Param("userId") Long userId,@Param("userName") String userName);
    
    • 1
    • 2
    • 3

    当在测试类中直接调用时,需要需要在测试类中添加注解@Transactional,但是@Transactional@Test一起连用会导致事务自动回滚,这时候需要指定事务不回滚@Rollback(false)

    @Test
    @Transactional
    @Rollback(false)
    public void updateById() {
        dao11.updateById("张三",1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.3.3 Service层

    创建BookService,代码如下:

    import com.example.demo.dao.BookDao;
    import com.example.demo.domain.Book;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.stereotype.Service;
     
    import java.util.List;
     
    @Service
    public class BookService {
        @Autowired
        BookDao bookDao;
     
        //save方法由JpaRepository接口提供
        public void addBook(Book book){
            bookDao.save(book);
        }
     
        //分页查询
        public Page<Book> getBookByPage(Pageable pageable){
            return bookDao.findAll(pageable);
        }
     
        public List<Book> getBooksByAuthorStartingWith(String author){
            return bookDao.getBooksByAuthorStartingWith(author);
        }
     
        public List<Book> getBooksByPriceGreaterThan(Float price){
            return bookDao.getBooksByPriceGreaterThan(price);
        }
     
        public Book getMaxIdBook(){
            return bookDao.getMaxIdBook();
        }
     
        public List<Book> getBookByIdAndName(String name,Integer id){
            return bookDao.getBookByIdAndName(name,id);
        }
     
        public List<Book> getBookByIdAndAuthor(String author,Integer id){
            return bookDao.getBookByIdAndAuthor(author,id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    1.3.4 Controller层

    创建BookContrller,实现对数据的测试,代码如下:

    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
     
    import java.util.List;
     
    @RestController
    public class BookController {
        @Autowired
        BookService bookService;
     
        @GetMapping(value = "/findAll")
        public void findAll(){
            PageRequest pageRequest = PageRequest.of(2,3);
            Page<Book> page = bookService.getBookByPage(pageRequest);
            System.out.println("总页数:"+page.getTotalPages());
            System.out.println("总记录数:"+page.getTotalElements());
            System.out.println("查询结果:"+page.getContent());
            //从0开始记,所以加上1
            System.out.println("当前页数:"+(page.getNumber()+1));
            System.out.println("当前记录数:"+page.getNumberOfElements());
            System.out.println("每页记录数:"+page.getSize());
        }
     
        @GetMapping(value = "search")
        public void search(){
            List<Book> bs1 = bookService.getBookByIdAndAuthor("鲁迅",7);
            List<Book> bs2 = bookService.getBooksByAuthorStartingWith("吴");
            List<Book> bs3 = bookService.getBookByIdAndName("西",8);
            List<Book> bs4 = bookService.getBooksByPriceGreaterThan(30F);
     
            Book b = bookService.getMaxIdBook();
            System.out.println("bs1:"+bs1);
            System.out.println("bs2:"+bs2);
            System.out.println("bs3:"+bs3);
            System.out.println("bs4:"+bs4);
            System.out.println("b:"+b);
        }
     
        @GetMapping(value = "/save")
        public void save(){
            Book book = new Book();
            book.setAuthor("鲁迅");
            book.setName("呐喊");
            book.setPrice(23F);
            bookService.addBook(book);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    代码解释:

    在findAll接口中,首先通过调用PageRequest中的of方法构造PageRequest对象。of方法接收两个参数:第一个参数是页数,从0开始计数,第二个参数是每页显示的条数

    1.4 Spring Data JPA提供的核心接口

    1.4.1 Repository接口

    Repository:其中T是要查询的表,ID是主键类型

    1.4.1.1 方法名称命名方式

    方法名称命名查询方式,hibername会根据方法名生成对应的sql语句
    注意:方法名称必须要遵循驼峰命名规则

    import cn.jzh.entity.User;
    import org.springframework.data.repository.Repository;
    
    import java.util.List;
    
    /**
     * Repository接口方法名称命名查询
     */
    public interface UserRepository extends Repository<User,Integer> {
    
        List<User> findByName (String name);
    
        List<User> findByNameOrAge(String name, Integer age);
    
        List<User> findByNameLike(String name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1.4.1.2 注解方式

    @Query注解查询方式,用这样的方式就不局限于方法名(hibernate根据方法名生成sql)

    @Query("from User u where u.name = ?1")
    List<User> queryByUseHQL (String name);
    
     @Modifying
    @Query("update User set name =?1 where id =?2")
    void updateById(String name, Integer id);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    测试

     @Test
        public void queryByUse() {
            List<User> list = dao11.queryByUseHQL("哈哈");
            System.out.println(JSON.toJSON(list));
        }
        @Test
        @Transactional
        @Rollback(false)
        public void updateById() {
            dao11.updateById("张三",1);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当在测试类中直接调用时,需要需要在测试类中添加注解@Transactional,但是@Transactional@Test一起连用会导致事务自动回滚,这时候需要指定事务不回滚@Rollback(false)

    1.4.2 CrudRepository接口

    CrudRepository:其中T是要查询的表,ID是主键类型
    CrudRepository接口,主要是完成一些增删改查的操作,封装了好多接口可以直接使用,当然依旧可以使用注解方式
    注意CrudRepository接口继承了Repository接口

    public interface UserCrudRepository extends CrudRepository<User,Integer> {
    }
    
    • 1
    • 2

    测试

     @Test
        public void testCrudAdd(){
            User u = new User();
            u.setName("张无忌");
            u.setAge(20);
            u.setAddress("冰火岛");
            u.setTime(new Date());
            User save = userCrudRepository.save(u);
            System.out.println(save);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.4.3 PagingAndSortingRepository接口

    PagingAndSortingRepository:其中T是要查询的表,ID是主键类型
    PagingAndSortingRepository主要是提供了分页与排序的操作
    注意PagingAndSortingRepository接口继承了CrudRepository接口

    import org.springframework.data.repository.PagingAndSortingRepository;
    
    public interface UserPagingAndSortingRepository  extends PagingAndSortingRepository<User,Integer> {
    }
    
    • 1
    • 2
    • 3
    • 4

    测试使用

    //排序操作
    @Test
    public void pageAndSort(){
        Sort.Order order = new Sort.Order(Sort.Direction.DESC,"id");
        Sort sort = Sort.by(order);
        List<User> all = (List<User>)dao.findAll(sort);
        System.out.println(JSON.toJSON(all));
    }
    //分页操作
    @Test
    public void page(){
        PageRequest pageParam = PageRequest.of(0, 1);
        Page<User> page = dao.findAll(pageParam);
    
        System.out.println("总页数:"+page.getTotalPages());
        System.out.println("总记录数:"+page.getTotalElements());
        System.out.println("查询结果:"+page.getContent());
        //从0开始记,所以加上1
        System.out.println("当前页数:"+(page.getNumber()+1));
        System.out.println("当前记录数:"+page.getNumberOfElements());
        System.out.println("每页记录数:"+page.getSize());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    1.4.4 JpaRepository接口

    该接口继承了PagingAndSortingRepository接口,是开发中最常用的接口
    对继承的父接口中的方法的返回值进行适配
    具体使用方法参考上面例子

    1.4.5 JpaSpecificationExecutor接口

    JpaSpecificationExecutor,其中T是要查询的表
    JpaSpecificationExecutor接口主要是提供了多条件查询的支持,并且可以在查询中添加分页与排序。
    注意JpaSpecificationExecutor是单独存在,完全独立的,一般是和上面接口联合使用

    public interface UserJPASpecificationExecutor 
    	extends JpaRepository<User, Integer>,JpaSpecificationExecutor<User> {}
    
    • 1
    • 2

    测试使用

     @Autowired
        private UserJPASpecificationExecutor executor;
        @Test
        public void jpsExecutorPage1(){
    
            Specification<User> spec = new Specification<User>(){
    
                /**
                 * 封装了查询条件
                 * root:查询对象的属性封装
                 * query:封装了要执行的查询中的各个部分信息:select from  order by 等,询哪些字段,排序是什么(主要是把多个查询的条件连系起来)
                 * criteriaBuilder:查询条件的构造器,定义不同的查询条件
                 * 字段之间是什么关系,如何生成一个查询条件,每一个查询条件都是什么方式
     *                      主要判断关系(和这个字段是相等,大于,小于like等)
                 */
                @Override
                public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
                    /**
                     * 参数一:查询属性
                     * 参数二:条件的值
                     */
                    List<Predicate> list = new ArrayList<>();
                    list.add(cb.equal(root.get("name"), "张三"));
    
                    Predicate[] arr = new Predicate[list.size()];
                    Predicate[] predicates = list.toArray(arr);
                    return cb.and(predicates);
    
    				或者这样操作
    				return cb.and(cb.equal(root.get("name"), "张三"),cb.equal(root.get("name"), "张三"));
                }
            };
    
            List<User> list = executor.findAll(spec);
            System.out.println(JSON.toJSON(list));
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  • 相关阅读:
    每天一个数据分析题(二百九十三)——数据分析思维
    Cholesterol-PEG-Maleimide,胆固醇-聚乙二醇-马来酰亚胺,修饰蛋白和多肽用
    1. 云计算简介
    问题复盘|在使用 Gson 时,报 Failed to parse date [““] 错误
    cpp-httplib 源码剖析
    解决你的R语言乱码问题
    大学生开学需要准备哪些数码产品、五款大学生必买的电子产品
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){}卡死
    毕业设计 LSTM的预测算法 - 股票预测 天气预测 房价预测
    【kubernetes】Argo Rollouts -- k8s下的自动化蓝绿部署
  • 原文地址:https://blog.csdn.net/u012060033/article/details/127870788