spring data jpa个人笔记

导包

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

1、使用

先配置yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/jpa2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update #无表就创建,有表就不创建
    show-sql: true

使用很简单写一个实体类,就能创建出一个数据库

@Entity//声明实体类
@Table(name = "cst_customer")//配置数据库表的名字
@Data
public class Customer {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "cust_id")
    private Long custId;

    @Column(name = "cust_name")
    private String custName;

    @Column(name = "cust_source")
    private String custSource;

    @Column(name = "cust_level")
    private String custLevel;

    @Column(name = "cust_industry")
    private String custIndustry;

    @Column(name = "cust_phone")
    private String custPhone;

    @Column(name = "cust_address")
    private String custAddress;
}

  • @Entity:声明实体类
  • @Table(name = "cst_customer"):配置数据库表的名字
  • @Id:主键
  • @GeneratedValue(strategy = GenerationType.IDENTITY):自增
  • @Column(name = "cust_id"):对应的列名

在这里插入图片描述

2、dao、service、controller

dao:2行就行

//JpaRepository中第一个泛型写实体类,第二个写实体类主键,用于简单的CRUD代码
//JpaSpecificationExecutor泛型写实体类对象,封装了复杂查询
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    
}

  • JpaRepository中第一个泛型写实体类,第二个写实体类主键,用于简单的CRUD代码
  • JpaSpecificationExecutor泛型写实体类对象,封装了复杂查询

service:

@Service
public class CustomerService {
    
    
    @Autowired
    CustomerDao customerDao;

    public List<Customer> findAll(){
    
    
        return customerDao.findAll();
    }

    public Customer findByI(Long id){
    
    
        return customerDao.findById(id).get();
    }
}

controller


@RestController
public class CustomerController {
    
    
    @Autowired
    CustomerService customerService;

    @GetMapping("/")
    public List<Customer> findAll(){
    
    
        return customerService.findAll();
    }

    @GetMapping("/{id}")
    public Customer findById(@PathVariable("id") Long id){
    
    
        return customerService.findByI(id);
    }
}

现在只实现了查全部和查单个
在这里插入图片描述

3、CRUD

简单的有,dao层写好的,
xxxDao.findAll();:查询所有数据,返回List<实体类>
xxxDao.findById(id).get();:查询ID的数据,返回单个数据
xxxDao.save(实体类对象):①当没ID时,保存数据,返回对象;②当有ID,更新数据,返回对象
xxxDao.deleteById(id);:根据ID删除数据
xxxDao.count();:查询总数是多少
xxxDao.exists(Id):查询Id数据是否存在,返回boolean

4、JQPL

这是一个半自动化的sql语句功能,就是在DAO层写底层代码,但是也不用你写完整的语句,只需要写一半的sql语句就可以

一、比如我们要根据name查找对象

1)dao:

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    
	//自定义dao层代码
    @Query(value = "from Customer where custName = ?1")
    public Customer findJpql(String custName);

}
  • @Query里面写sql语句,但也不用写完整,里面的值默认是select *的,我们只要补全后面就行。
  • 但是这里补全的很神奇,sql表中数据库名是cst_customer,列是cust_name,但是这个不看数据库的,只看你的实体类名字,所以就是from 实体类 where 实体类属性 = ?1,后面为什么加个1下面再讲

2)service:

    public Customer findJpql(String cust_name){
    
    
        return customerDao.findJpql(cust_name);
    }

3)controller

    @GetMapping("/test")
    public Customer findJpql(){
    
    
        String custName = "php";
        return customerService.findJpql(custName);
    }

查看执行的语句就是:

select customer0_.cust_id as cust_id1_0_, 
	customer0_.cust_address as cust_add2_0_, 
	customer0_.cust_industry as cust_ind3_0_, 
	customer0_.cust_level as cust_lev4_0_,
	 customer0_.cust_name as cust_nam5_0_, 
	 customer0_.cust_phone as cust_pho6_0_, 
	 customer0_.cust_source as cust_sou7_0_ 
 from cst_customer customer0_ where customer0_.cust_name=?

在这里插入图片描述

二、更深入地了解JPQL

dao:

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    

    @Query(value = "from Customer where custName = ?2 and custId = ?1")
    public Customer findCustNameAndCustId(Long id,String name);

}
  • 首先,得先知道,@Query里面的值与参数名字可以不一样,是根据占位符后面的数字相关的。比如custName = ?2,在运行的时候,就是把该方法的第二个参数载入custNamecustId = ?1就是把第一个参数载入,所以理解就是from Customer where custName = 方法中的name and custId = 方法中的id

service

    public Customer findCustNameAndCustId(String name,Long id){
    
    
        return customerDao.findCustNameAndCustId(id,name);
    }

controller

    @GetMapping("/test2")
    public Customer findCustNameAndCustId(){
    
    
        String name = "java";
        Long id = 1l;
        return customerService.findCustNameAndCustId(name,id);
    }

在这里插入图片描述

三、Jpql的更新操作

dao:

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    

    @Query(value = "update Customer set custName = ?1 where custId = ?2")
    @Modifying
    public void updateJpql(String name,Long id);
}
  • 更新操作呢就要写完整的sql代码了,其中,还要添加@Modifying注解,因为@Query默认是查询的,加了@Modifying才识别出来是要更新的操作

service

    public void updateJpql(String name,Long id){
    
    
        customerDao.updateJpql(name,id);
    }

controller

    @GetMapping("/test3")
    @Transactional//添加事务性,我也不懂,反正Jpql的添加就是要加这个注解
    public Customer updateJpql(){
    
    
        String name = "Java真好玩";
        Long id = 3l;
        customerService.updateJpql(name,id);
        return customerService.findByI(id);
    }
  • @Transactional:添加事务性,我也不懂,反正Jpql的添加就是要加这个注解
    在这里插入图片描述
    更新的操作i:①写完整sql语句②dao层添加@Modifying注解③controller层添加@Transactional注解

5、sql语句的查询

sql大家懂的都懂,但是怎么在jpa中使用纯净的sql语句查询?
就是@Query中有个参数nativeQuery,nativeQuery: false(使用jpa查询) | true(使用本地查询:sql查询)

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    

    @Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery=true)
    public List<Object[]> findSql(String name);
}
    public List<Object[]> findSql(String name){
    
    
        return customerDao.findSql(name);
    }
    @GetMapping("/test4")
    public List<Object[]> findSql(){
    
    
        String name = "%av%";
        return customerService.findSql(name);
    }

在这里插入图片描述

6、方法命名规则查询

这个东西啊,就更厉害了,都不用写代码了,什么基本查询,条件查询,模糊查询,你按规则写个方法名,也不用加什么注解,他自动就会补全了,真实非常神奇呢

1)基本查询

为了简便,以下我就不写service层了,controller直接调用dao层
dao层直接创建方法:findBy + 属性名字:根据属性名字查询对象

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    
	
    public Customer findByCustName(String name);
}
    @GetMapping("/test5")
    public Customer findByCustName(){
    
    
        return customerDao.findByCustName("java");
    }

在这里插入图片描述
我的上帝啊,就写个方法名就好了,啥都不用做直接调用就可以用,被震惊到了

2)模糊查询

findBy + 属性名 +查询方式(Like | Isnull ),如果不写查询方式,默认是=

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    

    public List<Customer> findByCustNameLike(String name);
}
    @GetMapping("/test6")
    public List<Customer> findByCustNameLike(){
    
    
        return customerDao.findByCustNameLike("%av%");
    }

在这里插入图片描述

3)多条件查询

findBy + 属性名 + 查询方式 + 多条件连接符(And | Or) + 属性名 + 查询方式

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    
//这里的参数顺序一定要写好
public Customer findByCustNameLikeAndCustAddress(String custName,String custAddress);
}
    @GetMapping("/test7")
    public Customer findByCustNameLikeAndCustAddress(){
    
    
        return customerDao.findByCustNameLikeAndCustAddress("%av%","123");
    }

在这里插入图片描述

4)前几个

findTopNxxx:N为查找的前几个的数量

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    
//这里的参数顺序一定要写好
public Customer findTop6ByCustName(String custName);
}

5)不等于

findByxxxNot(ccc)
相当于where xx != ccc

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    
//这里的参数顺序一定要写好
public Customer findTop6ByCustNameNot(String custName);
}

6)order by 排序

findByAOrderByB(Asc||Desc)

public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
    
    
//这里的参数顺序一定要写好
public Customer findByCustNameOrderByIdAsc(String custName);
}

7、JpaSpecificationExecutor

在这里插入图片描述

底层的dao我们继承了JpaSpecificationExecutor,这个东西只有几个方法

    Optional<T> findOne(@Nullable Specification<T> var1);

    List<T> findAll(@Nullable Specification<T> var1);

    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

    List<T> findAll(@Nullable Specification<T> var1, Sort var2);

    long count(@Nullable Specification<T> var1);

很明显,这些都是查找的东西,只是要我们往里面添加东西。

  • Pageable :分页的类
  • Sort :排序
  • Specification:我们自定义条件的类,一般重点都是这个里面的

1)Specification 查询条件

service调用dao层嘛,JpaSpecificationExecutor都是dao层的代码,就有

    public Customer testSpec(){
    
    

        Customer customer = customerDao.findOne(new Specification<Customer>() {
    
    
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
    
    
                return null;
            }
        }).get();

        return customer;
    }

dao.findOne(Specification<T>)方法,我们就要创建一个Specification类进行条件查询,其主要的一个方法toPredicate,在这里构造条件,其有三个参数:

  • Root<Customer> root:查询的根对象,也就是Customer,查询的任何属性都可以从根对象获得

  • CriteriaQuery<?> criteriaQuery:顶层查询对象,自定义查询方式,但是这个一般都不用!!,了解就行

  • CriteriaBuilder criteriaBuilder:查询构造器,封装查询条件,一般都是用该参数进行条件构造的,这个是重点!!展示一些查询条件:
    在这里插入图片描述

  • 所以,自定义查询条件步骤是

    1. 实现Specification 接口
    2. 实现toPredicate方法
    3. 借助参数构造条件,root 获取对象属性,criteriaBuilder 构造查询条件,内部封装了很多查询条件(模糊查询,精准查询等等)

①用Spce达到findBycustName的效果

从简单入手吧,根据名字查找,虽然有了但是先学嘛
dao层就不用写了,因为dao层的就那几个方法,需要写的代码应该是在service层的

    public Customer specFindByCustName(String name){
    
    

        Customer customer = customerDao.findOne(new Specification<Customer>() {
    
    
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
    
    
                //获取属性,返回一个我不知道的对象。。
                Path<Object> custName = root.get("custName");
                /*构造语句:  select * from cst_customer where cust_name = ?
                第一个参数:要被比较的参数
                第二个参数:想要拿去比较的值
                 */
                Predicate predicate = criteriaBuilder.equal(custName, name);
                return predicate;
            }
        }).get();

        return customer;
    }
    @GetMapping("/test8")
    public Customer specFindByCustName(){
    
    
        return customerService.specFindByCustName("java");
    }

在这里插入图片描述
达到了根据名字查询的功能,下面来点复杂的

②模糊查找与多条件查询

service


/*
custName模糊查找与(and)custAddress精准查找,两个条件是与的关系
 */
public Customer specFindByCustNameLikeAndCustAddress(String name,String address){
    
    
    Customer customer = customerDao.findOne(new Specification<Customer>() {
    
    
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
    
    
            //两个属性
            Path<Object> custName = root.get("custName");
            Path<Object> custAddress = root.get("custAddress");

            //where custName like '%av%' and custAddress = '123'
            //其中,这个like的第一个参数,不像精准查询的那样简单,要添加个.as(类型	.class)
            Predicate p1 = criteriaBuilder.like(custName.as(String.class), name);
            Predicate p2 = criteriaBuilder.equal(custAddress, address);

            //and连接起来,返回来也是一个对象
            return criteriaBuilder.and(p1, p2);//如果是 .or() 就是或
        }
    }).get();
    return customer;
}
    @GetMapping("/test9")
    public Customer specFindByCustNameLikeAndCustAddress(){
    
    
        return customerService.specFindByCustNameLikeAndCustAddress("%av%","123");
    }

在这里插入图片描述

③排序

List<T> findAll(@Nullable Specification<T> var1, Sort var2);,第一个就是Specification,第二个就是一个Sort类,其创建不是new而是对象.by(排序类型,排序属性)
降序类型:Sort.Direction.DESC降序,Sort.Direction.ASC升序

//查询名字不为空的数据,并且降序排序
    public List<Customer> specSort(){
    
    
        //查询用户名字不为空的
        Specification specification = new Specification() {
    
    
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
    
    
                //查询用户名字不为空的
                Path custName = root.get("custName");
                Predicate notNull = criteriaBuilder.isNotNull(custName);
                return notNull;
            }
        };
        //desc降序排序
        Sort sort = Sort.by(Sort.Direction.DESC, "custId");
        //List<T> findAll(@Nullable Specification<T> var1, Sort var2);
        List<Customer> all = customerDao.findAll(specification, sort);
        return all;
    }

在这里插入图片描述

④排序+分页

Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
返回Page对象,其有主要3个方法
在这里插入图片描述
所以,返回来的东西是个对象还挺好的

下面来说Pageable,在findAll(spec,page)中,第一个参数是spec查询,第二个是分页类,分页类创建用PageRequest而不是Pageable,通过of创建而不是new

Pageable pageable = PageRequest.of(参数);
在这里插入图片描述
又要分页又要排序就用第二个

	//index,当前查询页数
	//num,每页查询的数量
    public Page findAllPage(int index,int num){
    
    
        Specification specification = null;//暂时不写查的
        Sort sort = Sort.by(Sort.Direction.DESC,"custId");//ID降序
        //当前查询页数,每页查询数量,排序方式
        Pageable pageable = PageRequest.of(index, num,sort);
        //    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
        Page page = customerDao.findAll(specification, pageable);
        //返回Page类,里面可以获得页数,数据集合,总条数等东西
        return page;
    }
    @GetMapping("/test11/{index}/{num}")
    public List<Customer> findAllPage(@PathVariable("index")int index,@PathVariable("num")int num){
    
    
        Page allPage = customerService.findAllPage(index, num);
        System.out.println("总页数:"+allPage.getTotalPages());
        System.out.println("总条数:"+allPage.getTotalElements());
        return allPage.getContent();
    }

在这里插入图片描述
控制台:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ order by customer0_.cust_id desc limit ?, ?
Hibernate: select count(customer0_.cust_id) as col_0_0_ from cst_customer customer0_
总页数:3
总条数:5

8、一对多

1)准备工作

众所周知,表和表之间有1对1,1对多,多对多的关系,先来讲经典的1对多,公司对员工
这里的东西也是不用写sql就可以完成的了
公司Company:

@Entity
@Table(name = "company")
@Data
public class Company {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "company_id")
    private Long companyId;

    @Column(name = "company_name")
    private String companyName;

    @Column(name = "company_num")
    private Long companyNum;

    /*
    虽然说主表不用写外键这个东西,但是说写了这个可以双向选择什么的,我也不懂
    @OneToMany:配置1对多,用于主表类中,但是这个数据库不会显示这个列
        targetEntity:对方对象的字节码对象
    @JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
        name:从表外键(即使类中没有这个属性)
        referencedColumnName:主表主键
     */
    @OneToMany(targetEntity = Employee.class)
    @JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
    private Set<Employee> employees = new HashSet<>();
    
}

@Entity
@Table(name = "employee")
@Data
public class Employee {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "employee_id")
    private Long employeeId;

    @Column(name = "employee_name")
    private String employeeName;

    @Column(name = "employee_job")
    private String employeeJob;

    /*
        @ManyToOne:配置多对1,用于从表类中外键属性前
            targetEntity:对方对象的字节码对象
        @JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
            name:从表外键(即使类中没有这个属性)
            referencedColumnName:主表主键
         */
    @ManyToOne(targetEntity = Company.class)
    @JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
    private Company company;
}
  • 一个@OneToMany一个@ManyToOne,两者的属性选项targetEntity 都是写本类.class,一个用于主表一个用于从表
  • @JoinColumn的name都是从表外键(即使数据库表中没有这个列),referencedColumnName 是主表主键
  • @JoinColumn的参数属性都是数据库列中的名字而不是类中的属性
public interface CompanyDao extends JpaRepository<Company,Long>, JpaSpecificationExecutor<Company> {
    
    
}
public interface EmployeeDao extends JpaRepository<Employee,Long>, JpaSpecificationExecutor<Employee> {
    
    
}

运行的时候控制台创建表:

Hibernate: create table company (company_id bigint not null auto_increment, company_name varchar(255), company_num bigint, primary key (company_id)) engine=InnoDB
Hibernate: create table employee (employee_id bigint not null auto_increment, employee_job varchar(255), employee_name varchar(255), employee_company_id bigint, primary key (employee_id)) engine=InnoDB
Hibernate: alter table employee add constraint FKhl48rixcnan3anhl6itgwheuc foreign key (employee_company_id) references company (company_id)

2)测试

@SpringBootTest
class JpaApplicationTests {
    
    

    @Autowired
    CompanyDao companyDao;

    @Autowired
    EmployeeDao employeeDao;

    @Test
    void contextLoads() {
    
    
        Company company = new Company();
        company.setCompanyName("小强公司");
        company.setCompanyNum(444l);

        Employee employee = new Employee();
        employee.setEmployeeJob("程序猿");
        employee.setCompany(company);//添加外键的值

        companyDao.save(company);
        employeeDao.save(employee);
    }

}

控制台:

Hibernate: insert into company (company_name, company_num) values (?, ?)
Hibernate: insert into employee (employee_company_id, employee_job, employee_name) values (?, ?, ?)

在这里插入图片描述
在这里插入图片描述

3)更加优化的方法

那个教程是真的坑,那个主表的类,直接改成这样更好,这种叫做主表放弃维护权

@Entity
@Table(name = "company")
@Data
public class Company {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "company_id")
    private Long companyId;

    @Column(name = "company_name")
    private String companyName;

    @Column(name = "company_num")
    private Long companyNum;

    /*
    虽然说主表不用写外键这个东西,但是说写了这个可以双向选择什么的,我也不懂
    @OneToMany:配置1对多,用于主表类中,但是这个数据库不会显示这个列
        targetEntity:对方对象的字节码对象
    @JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
        name:从表外键(即使类中没有这个属性)
        referencedColumnName:主表主键
     */
//    @OneToMany(targetEntity = Employee.class)
//    @JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
    //或者写以下的方式
    @OneToMany(mappedBy = "company")//mappedBy参数写从表中外键的属性名
    private Set<Employee> employees = new HashSet<>();

}
  • @OneToMany改@OneToMany(mappedBy =从表中外键的属性名) ,不要@JoinColumn,就可以了,这样更简洁,前面的都是弟弟。。。。

4)级联查询

人话讲就是你这个表更新,相对应的表也更新,有点类似触发器

注意!!级联查询不能用@Data,否则hashCode会无限栈溢出!!
@Data包括@Getter、 @Setter、 @RequiredArgsConstructor 、@ToString 、@EqualsAndHashCode,因为我们不吭重写hashCode,所以只能拆开一个个写了

用法:在@OneToMany(一般用在此处)或者@ManyToOne中添加参数cascade,其有几个值是值得讲的
在这里插入图片描述

  • cascade = CascadeType.ALL :所有
  • xx.MERGE:更新
  • xx.REMOVE:删除
  • xx.PERSIST:保存
@Entity
@Table(name = "company")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class Company {
    
    
	//xxx
    @OneToMany(mappedBy = "company",cascade = CascadeType.PERSIST)//mappedBy参数写从表中外键的属性名
    private Set<Employee> employees = new HashSet<>();
}
    @Test
    void test(){
    
    
        //删除3号公司,因为使用了级连查询,所以3号公司的员工都会被删除
        Company company = companyDao.findById(3l).get();
        companyDao.delete(company);
    }

在这里插入图片描述

9、多对多

1)准备

众所周知多对多是要建立中间表的。比如角色和用户之间的关系就是多对多的关系。
User:

@Entity
@Table(name = "user")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class User {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "user_id")
    private Long userId;

    @Column(name = "user_name")
    private String userName;

    @Column(name = "user_age")
    private int userAge;

    /*
    @ManyToMany
        targetEntity = 对方表的类
    @JoinTable
        name = 中间表的名字(自己起)

        joinColumns 配置当前用户在中间表的外键
            @JoinColumn 数组
                name 外键名(自己起)
                referencedColumnName 参照主表中的主键名

        inverseJoinColumns 配置对方表在中间表的外键,
            @JoinColumn 数组
                name 外键名(自己起)
                referencedColumnName 参照主表中的主键名
     */

//    @ManyToMany(targetEntity = Role.class)
//    @JoinTable(name = "sys_user_role",
//        joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
//        inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
//    )
    @ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    @JoinTable(name = "sys_user_role",joinColumns = @JoinColumn(name = "sys_user_id"),inverseJoinColumns = @JoinColumn(name = "sys_role_id"))
    private Set<Role> roles = new HashSet<>();
}

  • 要说的很多点都在注释里面说了。
  • 注意,多对多一定要用到cascade = CascadeType.xxx,没有就报错。至于后面的fetch = FetchType.LAZY,我也不知道是什么。。
@Entity
@Table(name = "role")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class Role {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "role_id")
    private Long roleId;

    @Column(name = "role_name")
    private String roleName;

    @ManyToMany(mappedBy = "roles",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    private Set<User>  users = new HashSet<>();
}

  • @ManyToMany(mappedBy = "roles"),mappedBy 参照另一个表的类属性名

继承JPA的DAO层就不写了

2)测试


@SpringBootTest
class JpaApplicationTests2 {
    
    

    @Autowired
    UserDao userDao;

    @Autowired
    RoleDao roleDao;

    @Test
    void test1(){
    
    
        User user = new User();
        user.setUserName("小明");
        user.setUserAge(23);

        Role role = new Role();
        role.setRoleName("程序猿");

        user.getRoles().add(role);

        userDao.save(user);
        roleDao.save(role);

    }

}
  • 这里有一个很重要的点,就是添加中间表数据一定要在两表保存数据之前,代码就是 user.getRoles().add(role);一定要在xx.save()之前

10、对象导航查询

看下之前的一对多:公司对员工,这里给出简洁属性

public class Company {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "company_id")
    private Long companyId;

    @Column(name = "company_name")
    private String companyName;

    @Column(name = "company_num")
    private Long companyNum;

    private Set<Employee> employees = new HashSet<>();

}


public class Employee {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "employee_id")
    private Long employeeId;

    @Column(name = "employee_name")
    private String employeeName;

    @Column(name = "employee_job")
    private String employeeJob;

    private Company company;
}

可以看到公司类中Set就是为了保存员工,才有这个属性的。然后员工类中有Company就是为了显示出其是哪个公司的

对象导航查询人话模式:根据ID找到该公司,然后公司.get员工,就可以得到所有在这个公司工作的员工数据
在这里插入图片描述
在这里插入图片描述

重大坑

这个B有个重大的坑,就是一切都很正常地理所应当嘛
service

@Service
public class CompanyAndEmployeeService {
    
    
    @Autowired
    CompanyDao companyDao;
    @Autowired
    EmployeeDao employeeDao;

    //输入公司ID,显示该公司所有员工
    public Set<Employee> findEmployeeByCompany(Long id){
    
    
        Company company = companyDao.findById(id).get();
        Set<Employee> employees = company.getEmployees();
        return employees;
    }
}

controller

@RestController
public class CompanyAndEmployeeController {
    
    

    @Autowired
    CompanyAndEmployeeService companyAndEmployeeService;

    @GetMapping("test12/{id}")
    public Set<Employee> findEmployeeByCompany(@PathVariable("id") Long id){
    
    
        Set<Employee> employeeByCompany = companyAndEmployeeService.findEmployeeByCompany(id);

        return employeeByCompany;
    }

}

在service层根据ID找到公司,并且返回这个公司的员工Set集合。然后controller直接打印输出。
但是!!!就会发生这样的错误
在这里插入图片描述
在这里插入图片描述
可以看到,重复的数据在不停地查询

原因

当使用JPA查询Company,会自动查询Employee,同理当查询Employee时,会自动查询Company。当出现上述问题中的两种情况时,会导致toString()或序列化的实体对象一直相互调用,进入死循环。所以,不能重写toString()。不能序列化实体类。

解决办法

对实体类中的级联对象加入@JsonIgnoreProperties注解,序列化时就会忽略对级联对象的序列化,从而中断死循环。
重写实体类

@Entity
@Table(name = "employee")
@Getter
@Setter
@RequiredArgsConstructor
public class Employee {
    
    

    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "employee_id")
    private Long employeeId;

    @Column(name = "employee_name")
    private String employeeName;

    @Column(name = "employee_job")
    private String employeeJob;

    /*
        @ManyToOne:配置多对1,用于从表类中外键属性前
            targetEntity:对方对象的字节码对象
        @JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
            name:从表外键(即使类中没有这个属性)
            referencedColumnName:主表主键
         */
    @ManyToOne(targetEntity = Company.class,fetch = FetchType.LAZY)
    @JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
    @JsonIgnoreProperties(value = {
    
    "employees"})
    private Company company;
}

@Entity
@Table(name = "company")
@Getter
@Setter
@RequiredArgsConstructor
public class Company {
    
    


    @Id//主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增
    @Column(name = "company_id")
    private Long companyId;

    @Column(name = "company_name")
    private String companyName;

    @Column(name = "company_num")
    private Long companyNum;

    /*
    虽然说主表不用写外键这个东西,但是说写了这个可以双向选择什么的,我也不懂
    @OneToMany:配置1对多,用于主表类中,但是这个数据库不会显示这个列
        targetEntity:对方对象的字节码对象
    @JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
        name:从表外键(即使类中没有这个属性)
        referencedColumnName:主表主键
     */
//    @OneToMany(targetEntity = Employee.class)
//    @JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
    //或者写以下的方式
    @OneToMany(mappedBy = "company",cascade = CascadeType.ALL,fetch = FetchType.LAZY)//mappedBy参数写从表中外键的属性名
    @JsonIgnoreProperties(value = {
    
    "company"})
    private Set<Employee> employees = new HashSet<>();

}

这里@JsonIgnoreProperties的值结合 Spring 补充知识 的13点一起看
在这里插入图片描述
这样就变正常了

2、延迟加载

一般都是延迟加载,要调成立马加载也可以
用在@ManyToOne、@OneToMany、@ManyToMany之中
@ManyToMany(fetch = FetchType.xx)xx有LAZY延迟加载和EAGER立即加载

代码:
https://github.com/E-10000/spring-data-jpa-demo

11、总结

来总结一下吧,用了jpa就最好不要用lombok,如果要用,就尽量只用

@Getter
@Setter
@RequiredArgsConstructor

不要重写tostring和hashcode。

使用步骤就参照1.2那里,建立实体类和继承jpa,然后查询就继续往下看,标语表之间的联系先做好

猜你喜欢

转载自blog.csdn.net/yi742891270/article/details/109314231