SpringDataJPA学习记录(干货较多)

SpringDataJPA学习笔记

0. 第零部分 (前言)

全文基于哔站图灵教育的SpringDataJpa课程的学习笔记,同时文章末尾会附有github上传的基于SpringBoot+SpringDataJPA的小Demo。欢迎大家批评指正

1.第一部分(简介)

(简单)介绍SpringDataJPA

1.1概念
  • JPA

    JPA全称Java Persistence API(2019年重新命名为 Jakarta Persistence API ),是Sun官方提出的一种ORM规范,属于接口,需要具体的ORM框架来实现。

    ​ 1)ORM映射元数据:JPA支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对 象持久化到数据库表中; 如:@Entity 、 @Table 、@Id 与 @Column等注解。

    2)JPA 的API:用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者从繁琐的JDBC和 SQL代码中解脱出来。 如:entityManager.merge(T t);

    3)JPQL查询语言:通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。 如:from Student s where s.name = ?

  • ORM框架

    主要解决实体对象与数据库中的表进行字段和属性的映射,通俗讲就是将实体对象抽象成数据库的一张张,从而我们可以不用sql直接编码,能够操作对象一样从数据库获取数据。常见的ORM框架就有Mybatis,Hibernate。

  • SpringDataJPA

    SpirngDataJPA是Spring提供的一套简化JPA开发的框架,它集成了实现JPA接口规范的Hibernate框架,同时提出按照约定好的规则进行【方法命名】去写Dao层接口,从而达到简化开发的效果(省去了简单的CRUD),另外还提供了分页、查询、排序等方法。
    Hibernate官方文档

1.2 作用
  • 简化Dao层接口的实现
  • 降低程序与SQL语句的耦合度
1.3 组成框架图

在这里插入图片描述

2. 第二部分(前期准备)

说明:因为之前写了简单的Book的Controller层,因此就在Demo项目中添加了Swagger配置,有使用过的大伙可以直接略过~

2.1 依赖和Properties设置
  • Swagger依赖配置

    • 引入依赖
    <!--  图像化依赖UI -->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>2.9.2</version>
            </dependency>
            <!--引入swagger2依赖 -->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>2.9.2</version>
            </dependency>
            <dependency>
                <groupId>io.swagger</groupId>
                <artifactId>swagger-annotations</artifactId>
                <version>1.5.20</version>
            </dependency>
    
            <dependency>
                <groupId>io.swagger</groupId>
                <artifactId>swagger-models</artifactId>
                <version>1.5.21</version>
            </dependency>
            <!-- swagger2 增强UI ,拥有好看的界面, 和接口分组,排序等功能,如不引用可自行删除-->
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>swagger-bootstrap-ui</artifactId>
                <version>1.9.6</version>
            </dependency
    
    • properties文件设置

      tips:之前我就是遇到过这个版本问题,很坑!

    #springboot与swagger2版本冲突问题(需要配置)
    spring.mvc.pathmatch.matching-strategy = ant_path_matcher
    
    • 编写SwaggerConfig文件
    package com.example.jpa_demo.Config;
    import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
    import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration//该注解标记为spring管理的配置类文件
    @EnableSwagger2//注解对应上的pom.xml配置的依赖swagger2
    @EnableSwaggerBootstrapUI
    public class SwaggerConfig  {
        //模板swaggerConfig(C/V就完事了以后就可以安装这个模板就行了~)
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .groupName("图书管理接口")
                    .apiInfo(apiInfo())
                    .host("localhost:8080")
                    .select()
                    //要扫描标记过注解的api接口(Controller)
                    .apis(RequestHandlerSelectors.basePackage("com.example.jpa_demo.Controller"))
                    .paths(PathSelectors.any())
                    .build();
        }
    	//可自定义说明文档信息
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("JpaTestDemo")
                    .description("Test")
                    .contact(new Contact("louis", "", ""))//作者
                    .termsOfServiceUrl("")
                    .version("1.0")
                    .build();
        }
    }
    
    • 效果图

      是不是感觉很简洁好看呢(嘻嘻~)

    在这里插入图片描述

  • 安装lombok和JPABuddy

    tips:简化实体类开发和dao层接口的规定方法提示

    • 引入依赖
    <!--安装lombok依赖 配置相关依赖 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    • 安装插件
      在这里插入图片描述
  • 数据库连接驱动(Mysql)

    • 引入依赖
    <!-- 数据库连接驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    • properties设置数据库信息
    #指定库名testbook
    spring.datasource.url = jdbc:mysql://localhost:3306/testbook?serverTimezone=Asia/Shanghai
    #mysql数据库的用户名和密码
    spring.datasource.username = root
    spring.datasource.password = xxx
    #数据库连接驱动
    spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
    
  • springdataJpa连接配置

    • 引入依赖
    <!--jpa依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    • properties文件设置
    spring.jpa.hibernate.ddl-auto= update 
    #控制台显示sql语句
    spring.jpa.show-sql = true
    #其他常见配置信息
    	#jpa 配置
        	#spring.jpa.database-platform=mysql
        # 是否开启生成表结构
        	#spring.jpa.generate-ddl=false
        # 通过实体类生成表的策略
        	#spring.jpa.hibernate.ddl-auto=none
        # 在日志中打印SQL语句
        	#spring.jpa.show-sql=true
        # 格式化日志中的SQL语句
        	#spring.jpa.properties.hibernate.format_sql=true
        # 日志级别
        	#spring.jpa.properties.hibernate.type=trace
        # 是否在SQL语句中输出便于调试的注释信息,show_sql=true时有效
        	#spring.jpa.properties.hibernate.use_sql_comments=true
        # 在一个session中累计多少条SQL之后批量提交,提交后session会清除缓存并关闭
        	#spring.jpa.properties.hibernate.jdbc.batch_size=50
    

3.第三部分(测试实例)

3.0 pojo实体类对象
package com.example.jpa_demo.Pojo.Entity;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * @author LouisBrilliant
 * @version 1.0
 */
@Entity//spring_data_jpa注解
@Data //lombok的注解
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer bookID;
    private String bookName;
}
3.1 SpringDataJpa的自定义操作
  • 原生Sql(Jpql)

    • 注解@Query

      1)索引 =?索引值

      2)具名 =:参数名 (方法中需结合@Param注解指定参数名)

    • 注解@Transactional @Modifying

      用于增删改的方法

    package com.example.jpa_demo.Repository;
    import com.example.jpa_demo.Pojo.Entity.Book;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Modifying;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.data.repository.query.Param;
    import javax.transaction.Transactional;
    import java.util.List;
    
    /**
     * @author LouisBrilliant
     * @version 1.0
     */
    //Repository数据访问层
    public interface TestBookRepository0 extends JpaRepository<Book,Integer> {//俩参数 实体类 和 ID类型
        //如若不写 直接调用jpa框架实现好的crud的API
        //自定义原生SQL方法即JPQL 无需理会JPA自定义命名规则 注解@Query
        //索引 格式 =?数字
        //具名 格式 =:参数名 结合@Param注解指定参数名
        @Query("FROM Book where bookName=?1")//将对象Book转化成数据表 即表名等于类名
        Book findBookByBookName(String name);
    
        @Query("update Book b set  b.bookName=:bookName where b.bookID=:bookId")
        //增删改 需要下面两个注解 !!!
        @Transactional
        @Modifying
        //类型是int或void 修改指定书籍id更改对应书籍的书名
        int updateBookByBookNameAndId(@Param("bookName")String bookName,@Param("bookId")Integer bookId);
        //纯sql 将nativeQuery设置成true value后跟的是sql语句
        //    @Query(value = "select * from  book where bookName=:bookName",nativeQuery = true)
        //    Book queryBookByName(@Param("bookName") String bookName);
    
    }
    
    
  • 规定方法名

    ps:一定要按照规定写方法名,要不然就会报错,之前我就遇到过这样的问题(捂脸)

    springboot与jpa集成后Bean始终无法注入的问题_jpa无法注入bean_Brilliant.Louis的博客-CSDN博客

    • 命名规则

      1)主题关键词

      决定对数据库的操作(只有查询和删除)

在这里插入图片描述

2)谓语关键词和修饰符

决定查询条件

在这里插入图片描述

  • Query by Example(接口库QueryByExampleExecutor)

    • 优缺点

      1)实现动态查询代码过于冗长

      2)只能匹配字符串类型,无法比较大小

      3)不支持嵌套使用 如 firstname = ?0 or (firstname = ?1 and lastname = ?2)

      4)可以精确匹配和模糊匹配

    • 使用步骤

      1. 实现Dao接口
      package com.example.jpa_demo.Repository;
      import com.example.jpa_demo.Pojo.Entity.Book;
      import org.springframework.data.repository.PagingAndSortingRepository;
      import org.springframework.data.repository.query.QueryByExampleExecutor;
      import java.util.List;
      public interface TestBookRepository2 extends PagingAndSortingRepository<Book,Integer> , QueryByExampleExecutor<Book> {
      }
      
      1. 设置匹配器 自定义查询条件ExampleMatcher
      2. 整合条件和实例对象

      基于Test进行测试,并没有在Service进行演示

      package com.example.jpa_demo.RepositoryTest;
      
      import com.example.jpa_demo.Pojo.Entity.Book;
      import com.example.jpa_demo.Repository.TestBookRepository2;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      import org.springframework.data.domain.Example;
      import org.springframework.data.domain.ExampleMatcher;
      
      import java.util.List;
      
      @SpringBootTest
      public class RepositoryTest2 {
          @Autowired
          TestBookRepository2 testBookRepository2;//继承了QueryByExampleExecutor(查询对象加匹配器(内嵌筛选条件))
          @Test
          void findUDomesticBook(){//静态查询
              Book book = new Book();
              book.setBookName("屠龙记");//1.这都可以是前端传过来的数据 这里直接创一个
              //2.通过匹配器 对条件进行自定义设置(可以不写)
              ExampleMatcher matching = ExampleMatcher.matching()
                      .withIgnorePaths("bookName")//设置忽略属性
                      .withIgnoreCase("bookName")//设置忽略大小写 要对应该类的属性名(要保持一致)
                      .withStringMatcher(ExampleMatcher.StringMatcher.ENDING)//对所有条件字符串进行结尾匹配(模糊查询)
                      .withMatcher("bookName",m-> m.endsWith());//针对单个条件进行锁定查询(模糊查询)
              //3.通过Example整合查询条件和持久化对象
              Example<Book> example=Example.of(book,matching);//俩参数 类对象Object和匹配器ExampleMatcher
              //4.强制类型转换
              List<Book> list=(List<Book>)testBookRepository2.findAll(example);
              System.out.println(list);
          }
      }
      
      
  • Specifiaction(多条件查询)

    • 优缺点

      1. 代码冗长
      2. Specification可以任意类型都可以进行查询
    • 使用说明

      1. 实现Dao接口
      public interface TestBookRepository4 extends PagingAndSortingRepository<Book,Integer>, JpaSpecificationExecutor<Book> {
      //Specifications 代码冗长 有点烦
      //对于query for example(QueryByExampleExecutor接口) 只能针对字符串模糊查询起作用 而Specification可以任意类型都可以进行查询
      }
      
      1. 参数

        • root

          通过root动态获取持久化对象表中的列 (获取字段)

        • criteriaBuilder

          通过CriteriaBuilder设置各种条件 (大于(gt) 小于(lessthan) 范围(in) ) 多种类型进行大小比较

          返回类型为Predicate

          通过CriteriaBuilder设置各种条件 (降升序(desc/asc))

          返回类型为Order

        • query

          通过query 组合多条件(order by,where)

        package com.example.jpa_demo.RepositoryTest;
        import com.example.jpa_demo.Pojo.Entity.Book;
        import com.example.jpa_demo.Pojo.Entity.QBook;
        import com.example.jpa_demo.Repository.TestBookRepository4;
        import com.querydsl.core.Tuple;
        import com.querydsl.jpa.impl.JPAQuery;
        import com.querydsl.jpa.impl.JPAQueryFactory;
        import io.swagger.models.auth.In;
        import org.junit.jupiter.api.Test;
        import org.junit.platform.commons.util.StringUtils;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.data.jpa.domain.Specification;
        
        import javax.persistence.EntityManager;
        import javax.persistence.PersistenceContext;
        import javax.persistence.Table;
        import javax.persistence.criteria.*;
        import java.util.ArrayList;
        import java.util.List;
        
        @SpringBootTest
        public class RepositoryTest4 {
            @Autowired
            TestBookRepository4 testBookRepository4;
        
            @Test
            void test04(){
                Book book = new Book();
                book.setBookName("降龙十八掌");
                System.out.println(testBookRepository4.findAll(new Specification<Book>() {// 匿名内部类 函数接口!(就涉及到了类Predicate)
                    @Override
                    public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                        //root from Book(实体类对象表) 1.通过root动态获取持久化对象表中的列 (获取字段)
                        Path<Integer> bookID = root.get("bookID");//泛型类型改成对应对象的属性类型
                        Path<String> bookName = root.get("bookName");
                        // CriteriaBuilder where 2.通过CriteriaBuilder设置各种条件 (大于小于 in) 多种类型进行大小比较
                        //参数一 为哪个字段设置条件 参数二 value值类型是Object
                        //动态查询 逻辑判断查询 原生sql语句
                        //使用list集合存储Predicate(条件)类型
                        List<Predicate> list = new ArrayList<>();//Predicate数组 存储多个过滤条件
                        if (book.getBookName() != null) {
                            //添加一个equal的lambda表达式
                            list.add(cb.equal(bookName, "倚天屠龙记"));//符合不为空的条件就把equal的条件添加到泛型为Predicate(函数式编程接口) 的集合list中
                        }
                        Predicate and = cb.and(list.toArray(new Predicate[list.size()]));//合并(&&)多条件 需要传进去的参数是Predicate数组 通过toarray将集合list转换为数组 同时list赋值给数组Predicate
                        Predicate数组 因为需要的参数是一个可变的数组Predicate...arr 低层实现就是一个数组Predicate ....predicate
                        //非动态查询(无判断进行筛选)
                        //Predicate bookNameP = cb.equal(bookName, "倚天屠龙记");简化lambda表达式 用Predicate类型接收lambda表达式
                        //Predicate bookIdP = cb.greaterThan(bookID,0);
                        //Predicate and = cb.and(bookIdP, bookNameP);多条件(n个)合并&&
                        //query 组合多条件(order by,where)
        
                        Order desc = cb.desc(bookName);
                        return query.
                                where(and).
                                orderBy(desc).
                                getRestriction();
                    }
                }));
        
            }
        }
        
        
      2. Predicate类

        为函数式编程而生,简化代码 (介绍Predicate)

      Java8之Predicate函数_好一个迷途小书童的博客-CSDN博客

  • Qdsl(通用的查询框架 生成Q类对象)

    • 配置Qdsl框架依赖配置和插件Plugin

      然后用maven去compile!!!要不然就会狠狠地报错!(doge)

    <!-- querydsl依赖 生成Q类对象-->
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
    </dependency>
    
    <!--加入插件,让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)-->
    <plugin>
        <groupId>com.mysema.maven</groupId>
        <artifactId>apt-maven-plugin</artifactId>
        <version>1.1.3</version>
        <executions>
            <execution>
                <goals>
                    <goal>process</goal>
                </goals>
                <configuration>
                    <!-- 生成文件存放的目标位置 (记得标记为Sources资源)-->
                    <outputDirectory>target/generated-sources/queries</outputDirectory>
                    <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                </configuration>
            </execution>
        </executions>
    </plugin>
    
    • 生成Q类型确定对象资源

    在这里插入图片描述

    • 优缺点

      1. 需要配置依赖和Plugin(有点麻烦)
      2. 代码简洁
    • 使用说明

      1. Dao层接口继承
      package com.example.jpa_demo.Repository;
      import com.example.jpa_demo.Pojo.Entity.Book;
      import org.springframework.data.querydsl.QuerydslPredicateExecutor;
      import org.springframework.data.repository.PagingAndSortingRepository;
      
      public interface TestBookRepository3 extends PagingAndSortingRepository<Book,Integer>, QuerydslPredicateExecutor<Book>{
      }
      
      1. 动态查询

      2. 静态查询

      package com.example.jpa_demo.RepositoryTest;
      
      import com.example.jpa_demo.Pojo.Entity.Book;
      import com.example.jpa_demo.Pojo.Entity.QBook;
      import com.example.jpa_demo.Repository.TestBookRepository3;
      import com.querydsl.core.Tuple;
      import com.querydsl.core.types.dsl.BooleanExpression;
      import com.querydsl.jpa.impl.JPAQuery;
      import com.querydsl.jpa.impl.JPAQueryFactory;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      import java.util.List;
      
      @SpringBootTest
      public class RepositoryTest3 {
          @Autowired
          TestBookRepository3 testBookRepository3;//继承QuerydslPredicateExecutor接口 (该接口为Qdsl通用查询框架)
          @PersistenceContext
          EntityManager em;
          @Test
          public void test01(){
              QBook book = QBook.book;//以静态方式引用形式进行实例化Q类
              //通过id查找 静态查询
              //eq 相等 in 加多个参数(String类型)and连接多个条件
              //查询单个进行多条件查询 id和bookName
              BooleanExpression eq = book.bookID.eq(1).and(book.bookName.in("天龙八部","仙剑奇侠传","天外飞仙"));
              System.out.println(testBookRepository3.findOne(eq));//参数类型是Predicate 而BooleanExpression是Predicate的子类
          }
          @Test
          public void test02(){
              //动态查询querydsl
              //同样需要对传过来的对象数据进行逻辑判断 然后进行查询
              Book book1 = new Book();//相当于前端传来的数据 这里做测试直接实例一个对象
              book1.setBookName("平凡的世界");
              QBook book = QBook.book;//从数据库获取持久化数据对象(Q类)
              //初始化Qdsl的查询条件
              BooleanExpression exp = book.isNotNull().or(book.isNull());
              exp=book1.getBookID()>-1?exp.and(book.bookID.gt(book1.getBookID())):exp;//前端传来的book1与后端获取的持久化数据进行比较
          }
      
          @Test
          void test03(){
              JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(em);//基于接口Querydsl动态查询(推荐)
              //面向querydsl的api编程
              QBook book = QBook.book;//持久化对象表
              //类似原生的sql语句查询 简洁易读易上手 动态改变Repository中的表 原生语句是不基于Repository
              JPAQuery<Tuple> limit = jpaQueryFactory
                      .select(book.bookID, book.bookName)//自定义查询Q类中某一属性 如id和name 如果可以通过select判断返回的类型泛型类型就可以确定 如若不能则Tuple或者?
                      .from(book)
                      .where()
                      .orderBy(book.bookID.desc())//降序
                      .limit(4);//故返回一个tuple类型的数组
              List<Tuple> fetch = limit.fetch();//转换成一个list数组
              for (Tuple tuple : fetch) {
                  System.out.println(tuple.get(book.bookID));
                  System.out.println(tuple.get(book.bookName));
              }
      
          }
      }
      
      
  • Q类对象常用方法

    1. 等于 EQ : equal .eq

    2. 不等于 NE : not equal .ne

    3. 小于 LT : less than .lt

    4. 大于 GT : greater than .gt

    5. 小于等于 LE : less than or equal .loe

    6. 大于等于 GE : greater than or equal .goe

3.2 多表查询

补充额外的pojo实体类,为后面多表查询做实例用

package com.example.jpa_demo.Pojo.Entity;

import lombok.Data;

import javax.persistence.*;
import java.util.List;

@Entity
@Data
@Table(name="tb_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    //具体岗位
    private String position;
    //可双向可单向连接 这里我就单向了 不想多一张表出来
//    @ManyToMany(cascade = CascadeType.ALL)
//    @JoinColumn(name = "customer_id")
//    private List<Customer> customerList;

}

package com.example.jpa_demo.Pojo.Entity;

import jdk.nashorn.internal.runtime.logging.Logger;
import lombok.Data;
import lombok.Value;

import javax.persistence.*;

@Data
@Entity
@Table(name = "tb_account")//对象映射表名
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String username;
    private String password;
    @OneToOne
    //双向关联 设置外键
    @JoinColumn(name = "customer_id")
    private Customer customer;
}

  • 一对一(@OneToOne)

    采用的是Qdsl通用框架实现多表查询

    1. 注解

      • @OneToOne(标记多表的关联关系是一对一)

      • @JoinColumn (设置外键增加字段 将两张表连接起来)

      • @Entity(标记这是Entity实体)

      • @Table(可以自定义设置数据库中表名)

      • @Id、@GeneratedValue(strategy = GenerationType.IDENTITY)

        主键标记 其生成策略为自增(相当于sql语句中的auto increment)

        JPA之@GeneratedValue注解_generated value_Eleazar的博客-CSDN博客

    2. Dao接口和Test测试代码和Pojo类

    package com.example.jpa_demo.Pojo.Entity;
    
    import lombok.Data;
    
    import javax.persistence.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @Data
    @Entity
    @Table(name = "tb_customer")//对象映射表名
    //@RequiredArgsConstructor//有final的属性有加上构造器 没有则加上无参构造器 @Data含有这个注解
    public class Customer {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)//相当于mysql中id自增(auto increment)
        private Integer id;
        private String name;
        private Integer age;
    
        //单向关联一对一
        
        @OneToOne(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.LAZY)//需要在在配置文件设置开启懒加载
        //设置外键id字段名
        @JoinColumn(name = "account_id")
        private  Account account;
    
        //用户可以有多条信息List集合 需要容器!
        //一对多默认懒加载 且customer表是被维护端
        @OneToMany(orphanRemoval = true,cascade = CascadeType.ALL,fetch = FetchType.LAZY/*,mappedBy = "customer"*/)
        //添加外键字段名 因为一个用户就有n条信息 如果外键字段名是message_id 那每找一条信息就要查询一次customer就很没有必要啊
        //如果外键字段名是customer_id,则查一次customer_id就可以找到多条信息 这不很好?
        //此外建是建立在tb_message表中的
        //一对多,两张表,多的表加主表外键
        @JoinColumn(name = "customer_id")
        private List<Message> message;
    
        @ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
        //需要容器 可以List或者是Set集合
        //下面代码可写可不写(要想自定义那就没关系 可以写) 因为JPA会自动生成一张中间表
        /*@JoinTable(
                name = "tb_customer_role",//设置中间表名
                joinColumns = {@JoinColumn(name = "c_id")},//设置本表的外键名称
                inverseJoinColumns = {@JoinColumn(name = "r_id")}//设置关联表的名称
        )*///多行注释 Ctrl+shift+/
        private List<Role> roleList=new ArrayList<>();
    
        private @Version Long version;//原理乐观锁 防止对数据库进行并发操作 通过改变版本号 使得紧接着的修改无法进行对数据进行修改
    }
    
    package com.example.jpa_demo.Repository;
    
    import com.example.jpa_demo.Pojo.Entity.Customer;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.querydsl.QuerydslPredicateExecutor;
    import org.springframework.data.repository.PagingAndSortingRepository;
    
    public interface CustomerRepository extends JpaRepository<Customer,Integer>,QuerydslPredicateExecutor<Customer{
    }
    
    package com.example.jpa_demo.RepositoryTest;
    
    import com.example.jpa_demo.Pojo.Entity.Account;
    import com.example.jpa_demo.Pojo.Entity.Customer;
    import com.example.jpa_demo.Pojo.Entity.QCustomer;
    import com.example.jpa_demo.Repository.CustomerRepository;
    import com.querydsl.core.types.dsl.BooleanExpression;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    
    public class OneToOneTest {
        @Autowired
        CustomerRepository customerRepository;
        @Test
        public void test06(){
            Account account = new Account();
            account.setPassword("123456");
            account.setUsername("hjy");
    //        account.setId(1);
            Customer customer = new Customer();
    //        customer.setId(1);
            customer.setName("hjy");
            customer.setAge(88);
            customer.setAccount(account);
            customerRepository.save(customer);
            QCustomer customer1 = QCustomer.customer;//qdsl是通用的查询框架 拿来添加插入肯定是行不通的!
            BooleanExpression id = customer1.id.eq(1);
            System.out.println(customerRepository.findOne(id));
    
        }
        @Test
        public void test07(){
            QCustomer customer = QCustomer.customer;
            System.out.println(customerRepository.findOne(customer.id.eq(3)));
        }
    }
    
    
    • @OneToOne注解参数

      • 配置关联操作(一定要配置!)
        1. cascade
        所有关联操作(入门无脑选这个)
        PERSIST 插入
        MERGE 修改
        REMOVE 删除
        2. fetch 设置是否懒加载
        EARGE 默认立即加载
        LAZY 只有用到对象才开始加载(挺好的这个 提高多表查询的效率)
        3. mappedBy 当一对一双向关联,需要有一方放弃关联 要不然会造成闭环 使得出现一些多余的多表关联操作(update更新) 从而影响效率
    • 单双向连接一对一

      说白了就是连接的两张表都有@OneToOne 则就表示双向连接 否则就是单向连接

  • 一对多

    • Dao接口和测试代码和Pojo类
    package com.example.jpa_demo.Repository;
    
    import com.example.jpa_demo.Pojo.Entity.Message;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface MessageRepository extends JpaRepository<Message,Integer> {
    }
    
    
    package com.example.jpa_demo.RepositoryTest;
    
    import com.example.jpa_demo.Pojo.Entity.Customer;
    import com.example.jpa_demo.Pojo.Entity.Message;
    import com.example.jpa_demo.Repository.CustomerRepository;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.rmi.MarshalException;
    import java.util.ArrayList;
    
    @SpringBootTest
    
    public class OneToManyTest {
        @Autowired
        CustomerRepository customerRepository;
        @Test
        public void test08(){
            //插入
            Customer customer = new Customer();
            customer.setAge(18);
            ArrayList<Message> msgList = new ArrayList<>();
            msgList.add(new Message("八嘎呀路"));
            customer.setMessage(msgList);
            customerRepository.save(customer);
            System.out.println(customer);
        }
    }
    
    
    • @OneToMany(一个用户可以有多个Message)

      1. Many指的对象类型是通常是Set或者是List(需要容器,不是容器怎么可以装多个(Many)呢?)
       //用户可以有多条信息List集合 需要容器!
          //一对多默认懒加载 且customer表是被维护端
          @OneToMany(orphanRemoval = true,cascade = CascadeType.ALL,fetch = FetchType.LAZY/*,mappedBy = "customer"*/)
          //添加外键字段名 因为一个用户就有n条信息 如果外键字段名是message_id 那每找一条信息就要查询一次customer就很没有必要啊
          //如果外键字段名是customer_id,则查一次customer_id就可以找到多条信息 这不很好?
          //此外建是建立在tb_message表中的
          //一对多,两张表,多的表加主表外键
          @JoinColumn(name = "customer_id")
          private List<Message> message;
      

    在这里插入图片描述

  • 多对一(结合一对多一起讲)

    • Pojo类和测试
    package com.example.jpa_demo.Pojo.Entity;
    
    import lombok.Data;
    
    import javax.persistence.*;
    
    @Data
    @Entity
    @Table(name = "tb_message")
    public class Message {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer messageId;
        private String content;
        @ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.REMOVE},fetch = FetchType.LAZY)//REMOVE删除子表信息同时也会将关联的父表的数据删除
        //外键为主表(customer)的id
        @JoinColumn(name = "customer_id")
        private Customer customer;
        //要添加无参构造器!
        public Message() {
        }
    
        public Message(String content) {
            this.content = content;
        }
    
        public Message(String content, Customer customer) {
            this.content = content;
            this.customer = customer;
        }
    
        @Override
        public String toString() {
            return "Message{" +
                    "messageId=" + messageId +
                    ", content='" + content + '\'' +
                    ", customerId=" + customer.getId() +
                    ", customerName=" + customer.getName() +
                    '}';
        }
    }
    
    package com.example.jpa_demo.RepositoryTest;
    
    import com.example.jpa_demo.Pojo.Entity.Customer;
    import com.example.jpa_demo.Pojo.Entity.Message;
    import com.example.jpa_demo.Repository.CustomerRepository;
    import com.example.jpa_demo.Repository.MessageRepository;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.annotation.Commit;
    
    import javax.transaction.Transactional;
    import java.util.ArrayList;
    import java.util.Optional;
    
    @SpringBootTest
    public class ManyToOneTest {
        @Autowired
        MessageRepository messageRepository;
        @Autowired
        CustomerRepository customerRepository;
    
        @Test
        @Transactional
        public void test09(){
            ArrayList<Message> msgList = new ArrayList<>();
            Customer customer = new Customer();
            //一个用户
            customer.setName("黄景瑜");
            //多条信息
            msgList.add(new Message("加油",customer));
            msgList.add(new Message("你可以的",customer));
            //多个持久化操作saveAll和findAll 需要加 @Transactional!!!
            messageRepository.saveAll(msgList);
            System.out.println(messageRepository.findAll());
        }
        @Test
        @Transactional
        @Commit//(单元测试会认为你的事务方法@Transactional,只是测试而已,它不会为你提交事务,需要单独加上@Commit)
        public  void test10(){
            Optional<Customer> byId = customerRepository.findById(9);
            customerRepository.delete(byId.get());
        }
    }
    
    
    • 注解

      1. @ManyToOne(双向连接)

      2. @Transactional

        在进行多个持久化操作 是需要@Transactional 目的就是让方法中的Session囊括一整个方法在内

      3. @Commit

        使用了@Transactional注解 使得单元测试会认为你的事务方法是测试,因此它不会为你提交事务,需要单独加上@Commit

  • 多对多

    • Pojo类和测试

      package com.example.jpa_demo.Pojo.Entity;
      
      import lombok.Data;
      
      import javax.persistence.*;
      import java.util.List;
      
      @Entity
      @Data
      @Table(name="tb_role")
      public class Role {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Integer id;
          //具体岗位
          private String position;
          //可双向可单向连接 这里我就单向了 不想多一张表出来
      //    @ManyToMany(cascade = CascadeType.ALL)
      //    @JoinColumn(name = "customer_id")
      //    private List<Customer> customerList;
      }
      
      
      package com.example.jpa_demo.RepositoryTest;
      
      import com.example.jpa_demo.Pojo.Entity.Customer;
      import com.example.jpa_demo.Pojo.Entity.Role;
      import com.example.jpa_demo.Repository.CustomerRepository;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Optional;
      
      @SpringBootTest
      public class ManyToManyTest {
          @Autowired
          private CustomerRepository customerRepository;
      
          @Test
          public void test13(){
              //在多对多的表关系中 不适合删除(REMOVE)
              Role role = new Role();
              Role role1 = new Role();
              role.setPosition("总监");
              role.setPosition("CEO");
              //当前单个用户
              Customer customer = new Customer();
              //对应多个身份
              customer.getRoleList().add(role);
              customer.getRoleList().add(role1);
              //保存当前用户的多个身份信息(持久化操作)
              customerRepository.save(customer);
          }
      
      }
      
      

在这里插入图片描述

  • 注解

    @JoinTable//可以不写 因为会自动生成表 要是自定义就是可以使用@JoinTable
    
@JoinTable(
    name = "tb_customer_role",//设置中间表名
    joinColumns = {@JoinColumn(name = "c_id")},//设置本表的外键名称
    inverseJoinColumns = {@JoinColumn(name = "r_id")}//设置关联表的名称
)
3.3 乐观锁

为了处理对数据库数据的并发操作的问题 引出乐观锁 (通过对数据库的操作进行版本改变 使得同一时间同一动作的操作不能生效 因为对象的版本号改变了)

private @Version Long version;//原理乐观锁 防止对数据库进行并发操作 通过改变版本号 使得紧接着的修改无法进行对数据进行修改

最后附上我的github上传源码,有兴趣可看看源码(估计不会看doge)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Brilliant.Louis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值