Spring Data JPA

构建内容

构建一个应用程序,该应用程序将POJO(普通旧Java对象)存储在基于内存的数据库中。

一.Spring Initializr 预初始化项目

手动初始化项目:

  1. 导航到https://start.spring.io.此服务提取应用程序所需的所有依赖项,并为您完成大部分设置。

  2. 选择 Gradle 或 Maven 以及您要使用的语言。本指南假定您选择了 Java。

  3. 单击依赖关系,然后选择 Spring Data JPA,然后选择 H2 数据库。

  4. 单击"生成"。

下载生成的 ZIP 文件,该文件是使用您的选择配置的 Web 应用程序的存档。

如果您的 IDE 具有 Spring Initializr 集成,则可以从 IDE 完成此过程。
您还可以从 Github 分叉项目,然后在 IDE 或其他编辑器中打开它。

二.定义简单实体

在此示例中,将存储对象,每个对象都注释为一个JPA实体
以下清单显示了“客户”类

package com.example.accessingdatajpa;

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

@Entity //代表它是一个JPA实体
public class Customer {

  @Id//将此属性用@Id标注,以便JPA其识别为对象的ID
  @GeneratedValue(strategy=GenerationType.AUTO)  //指示自动生成ID
  /*有三个属性,分别为id,firstName,lastName*/
  private Long id;
  private String firstName;
  private String lastName;

  /*两个构造函数*/
  //缺省构造函数只是为了JPA,不直接使用它
  protected Customer() {}
	//另一个构造函数,用于创建要保存到数据库的实例
  public Customer(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return String.format(
        "Customer[id=%d, firstName='%s', lastName='%s']",
        id, firstName, lastName);
  }

  public Long getId() {
    return id;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }
}

三.创建简单查询

Spring Data JPA专注于使用JPA在关系数据库中存储数据。
JPA最引人注目的功能时能够在运行时从存储库界面自动创建存储库实现。

eg:
创建一个与实体一起使用的存储库界面

package com.example.accessingdatajpa;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {//CustomerRepository扩展接口。
//:<实体的类型,使用的ID的参数类型> 
//extends CrudRepository作用:通过扩展,继承了几种用于处理持久性的方法,包括用于保存、删除和查找实体的方法。

  List<Customer> findByLastName(String lastName);//Spring Data JPA还允许通过声明其他查询方法来定义其他查询方法。

  Customer findById(long id);
}

四.实例:SpringBoot使用SpringDataJPA完成CRUD

1.构建项目
勾选web,Mysql,JPA组件

2.配置数据源以及JPA
我们修改application.properties文件配置为application.yml配置。.yml比.properties配置要更清晰更有层次感,可以很明了的看懂配置信息。
在resources目录下创建application.yml文件,并且配置DataSource以及JPA

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1.3306/exam?charaterEncoding=utf8
    username: root
    password: 123456

  jpa:
    database: mysql
    show-sql: true
    hibernate:
      naming_strategy: org.hibernate.cfg.ImprovedNamingStrategy


3.编写一个控制器
----UserController

package com.huangjuan.demo;


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/user")
public class UserController {
}

4.创建实体

根据数据库中的字段常见一个对应的StudentEntity来作为操作的对象。
注意:
@Table
当实体类与其映射的数据库表名不同名时,需要使用@Table标注说明,该标注与@Entity标注并列使用,置于实体类声明语句之前。
属性
name-----指明数据库的表名
name属性用于指定数据库表名称
若不指定则以实体类名称作为表名

catalog/schema------用于设置表所属的数据库目录或者模式,通常为数据库名
catalog属性用于指定数据库实例名,一般来说persistence.xml文件中必须指定数据库url,url中将包含数据库实例

uniqueConstraints-----用于设置约束条件,通常不需要设置。

@GenerateValue-----ID自增

@Column()-----字段名

代码:

package com.huangjuan.demo.pojo;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "myemp")
public class Myemp implements Serializable {
    @Id
    @GeneratedValue
    @Column(name="id")
    private Long id;

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

    @Column(name = "sal")
    private Long sal;

    @Column(name = "job")
    private String job;
    

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getSal() {
        return sal;
    }

    public void setSal(Long sal) {
        this.sal = sal;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }
}

5.创建JPA
创建MyempJPA接口并且继承SpringDataJPA内的接口作为父类
UserJPA继承了JPARepository接口(SpringDataJPA提供的简单数据操作接口)、JpaSpecificationExecutor(SpringDataJPA提供的复杂查询接口)、Serializable(序列化接口)。

package com.huangjuan.demo.jpa;

import com.huangjuan.demo.pojo.Myemp;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import java.io.Serializable;

public interface MyempJPA extends JpaRepository<Myemp,Long>, JpaSpecificationExecutor<Myemp>, Serializable {
}

SpringDataJPA内部使用了类代理的方式让继承了它接口的子接口都以spring管理的Bean的形式存在,也就是说我们可以直接使用@Autowired注解在spring管理bean使用。像以前调用Dao层一样,在service层调用。

eg:

@RestController
@RequestMapping(value = "/user")
public class MyempController {
    @Autowired
    private MyempJPA myempJPA;

    public List<Myemp> list(){
        return null;
    }
}

6.编写查询方法

  • 创建一个查询员工列表的方法
package com.huangjuan.demo.controller;


import com.huangjuan.demo.jpa.MyempJPA;
import com.huangjuan.demo.pojo.Myemp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping(value = "/user",method = RequestMethod.GET)
public class MyempController {
    @Autowired
    private MyempJPA myempJPA;

    public List<Myemp> list(){
       return myempJPA.findAll();
    }
}

其中myempJps.findAll()方法就是SpringDataJPA为我们提供的内置方法,它可以查询表内所有的数据,除了findAll还有很多有用的方法

  • 编写添加方法—save()
  @RequestMapping(value = "/save",method = RequestMethod.GET)
    public Myemp save(Myemp emp){
        return myempJPA.save(emp);
    }
  • 编写删除方法—delete()
 @RequestMapping(value = "/delete",method = RequestMethod.GET)
    public List<Myemp> delete(Long id){
        myempJPA.deleteById(id);
        return myempJPA.findAll();
    }

实验
访问用户列表路径:http://127.0.0.1:8080/myemp/list
添加一条用户信息到数据库,请求地址:http://127.0.0.1:8080/myemp/save?name=admin&sal=9000&job=“项目经理”
删除数据库数据,127.0.0.1:8080/myemp/delete?id=1

五.Spring Data JPA使用@Query与@Modifying注解自定义修改和删除操作

JPQL是面向对象进行查询的语言,还可以通过自定义的JPQL完成
内容

六.系统学习JPA

(1)SpringDataJPA搭建xml的配置方式

我们使用自行配置项目环境,不使用SpringBoot初始化(有利于熟悉SpringBoot的原码)
1.找到Spring Data JPA的依赖
版本兼容

  • 兼容版本的最简单方法是依靠我们随定义的兼容版本一起提供的Spring Data Release Train BOM。Spring Data
    Release Train BOM----统一版本管理,有效的避免版本冲突
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-bom</artifactId>
      <version>2021.1.3</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

用标签:dependencyManagement 会继承 spring-data-bom 下面的所有依赖

  • 声明springdatajpa的依赖
<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>
  • 声明与hibernate相关的依赖
  <!--junit4测试单元的工具-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <!--hibernate 对jpa的支持包-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.32.Final</version>
        </dependency>

        <!--openjpa-->
        <dependency>
            <groupId>org.apache.openjpa</groupId>
            <artifactId>openjpa-all</artifactId>
            <version>3.2.0</version>
        </dependency>

        <!--Mysql and MariaDB-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.22</version>
        </dependency>
  • 连接池
 		<!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
  • Spring Test
  <!--SpringTest-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.10</version>
            <scope>test</scope>
        </dependency>

2.持久化配置----【先使用xml,再用javaConfig(配置文件)】
persistence.xml(源头)

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">

    <!--需要配置persistence-unit结点
        持久化单元:
            name:持久化单元名称(JPA的实现对象)
            transaction-type:事务管理的方式
                JTA:分布式事务管理
                RESOURCE_LOCAL:本地事务管理
    -->
    <persistence-unit name="hibernateJPA" transaction-type="RESOURCE_LOCAL">
        <!--JPA的实现方式-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <!--需要进行ORM的POJO类-->
        <class>com.huangjuan.pojo.Student</class>

        <!--可选配置:配置jpa实现方的配置信息====数据库连接信息-->
        <properties>
            <!--数据库信息
                用户名:javax.persistence.jdbc.user
                密码:javax.persistence.jdbc.password
                驱动:javax.persistence.jdbc.driver
                数据库地址:javax.persistence.jdbc.url
            -->
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="123456"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/exam?characterEncoding=utf8"/>



            <!--配置jpa实现方(hibernate)的配置信息
                显示sql             :    false|true
                自动创建数据库表      :    hibernate.hbm2ddl.auto
                        create     :    程序运行时创建数据库表(如果有表,先删除后创建)
                        update     :    程序运行时创建表(如果有表,不会创建表)
                        none       :    不会创建表

            -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
        </properties>
    </persistence-unit>
</persistence>

注意:ORM----对象映射关系

spring.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    https://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">


    <!--用于整合jpa
        @EnableJpaRepositories

        数据访问层包的位置:com.acme.repositories(base-package)

        指定
        entity-manager-factory-ref="entityManagetFactory"
        transaction-manager-ref="transactionManager"
    -->
    <jpa:repositories base-package="com.huangjuan.repositories"
                      entity-manager-factory-ref="entityManagetFactory"
                      transaction-manager-ref="transactionManager"
    />

    <!--配置EntityManagerFactory的bean
    bean
        name===方法名
        class===bean对应的类
    -->
    <bean name="entityManagetFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!--配置持久化单元的内容-->
            <!--配置JPA的实现方式-->
            <property name="jpaVendorAdapter">
                <!--Hibernate实现
                    persistence.xml
                    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
                -->
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                    <!--属性设置-->
                    <!--配置jpa实现方(hibernate)的配置信息
                        自动创建数据库表      :    hibernate.hbm2ddl.auto
                                ture     :    程序运行时创建表(如果有表,不会创建表)
                                false       :    不会创建表
                     -->
                    <property name="generateDdl" value="true"></property>
                    <!--数据库显示配置-->
                    <property name="showSql" value="true"></property>
                </bean>
            </property>

        <!--扫描实体类的包-->
        <property name="packagesToScan" value="com.huangjuan.pojo"></property>


        <!--数据源/连接池
        将配置指定上====>ref
        -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>


   <!--配置datasource-->
    <bean class="com.alibaba.druid.pool.DruidDataSource" name="dataSource">

        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/exam?characterEncoding=UTF-8"></property>

    </bean>


    <!--声明事务-->
    <bean class="org.springframework.orm.jpa.JpaTransactionManager" name="transactionManager">
        <!--属性指定===引用entityManagetFactory-->
        <property name="entityManagerFactory" ref="entityManagetFactory"></property>
    </bean>

    <!--声明事务的使用方式====》事务基于注解的方式-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

3.SpringDataJpa的CRUD测试
准备:
实体类—Student

package com.huangjuan.pojo;


import javax.persistence.*;

@Entity
@Table(name = "myemp")
public class Student {

    @Id
    @Column(name = "id")
    private int id;

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

    @Column(name = "sal")
    private String sal;

    @Column(name = "job")
    private String job;

    public long getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSal() {
        return sal;
    }

    public void setSal(String sal) {
        this.sal = sal;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sal='" + sal + '\'' +
                ", job='" + job + '\'' +
                '}';
    }
}

(1)查找数据
测试

package com.huangjuan;

/*
* 通过junit,结合springboot进行测试
*
* @ContextConfiguration(classes="指定的配置类")----javaConfig
* @ContextConfiguration(classes="指定的配置文件")----xml
*
* */

import com.huangjuan.pojo.Student;
import com.huangjuan.repositories.CustomerRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Optional;

@ContextConfiguration("/spring.xml")
@RunWith(SpringJUnit4ClassRunner.class)  //基于junit4 spring单元测试
public class SpringdatajpaTest {


    @Autowired
    CustomerRepository repository;

    //查找数据
    @Test
    public void testR(){
        Optional<Student> byId = repository.findById(3);
        System.out.println(byId.get());
    }

(2)插入数据
测试

@Test
    //插入数据
    public void testS(){
        Student student = new Student();
        student.setJob("舞蹈老师");
        student.setName("李玺");
        student.setSal("50000");

        repository.save(student);
    }

(3)删除
测试

//删除数据----可以删除游离状态的数据
    @Test
    public void testD(){
        Student student = new Student();
        student.setId(3L);
        repository.delete(student);
    }

(2)JavaConfig的配置方式

package com.huangjuan.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

@Configuration //标记当前类为配置类  ====xml配置文件

/*@EnableJpaRepositories 用于整合jpa

*/
@EnableJpaRepositories(basePackages = "com.huangjuan.repositories")

@EnableTransactionManagement   //开启事物
public class SpringDataJPAConfig {

    /*配置数据源
    * 
    

        
        
        
        

    
    * */
    @Bean
    public DataSource dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername("root");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/exam?characterEncoding=UTF-8");
        druidDataSource.setPassword("123456");
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        return druidDataSource;
    }


    /*
    *  
    
        
            
            
                
                
                    
                    
                    
                    
                    
                
            

              
        


        
        
    
     */
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        //实体类的包
        factory.setPackagesToScan("com.huangjuan.pojo");
        factory.setDataSource(dataSource());
        return factory;
    }
    
    
    //事物
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {

        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

}

将测试类的配置导入修改

@ContextConfiguration(classes = SpringDataJPAConfig.class)

(3)Repository

Repository位于Spring Data Common 的lib里面,是Spring Data里面做数据库操作最底层的抽象接口、最顶级的父类,但仅仅做到一个标识的作用。
作用:
主要作为标记接口捕获要使用的类型,并帮助发现拓展此接口的接口、Spring Data JPA_第1张图片

(1)简介
Spring Data Repository 抽象的目标是为了减少各种持久性存储实现数据访问层所需的样板代码量。

(2)七个Repository接口
使用方法:直接继承

1.CrudRepository
提供了公共通用的
CRUD方法

//用来插入和修改  
//返回插入/修改的ID
/*
先检查传进去的实体是不是存在,然后判断是新增还是更新。
判断的两种机制
一种是根据Version判断,一种是根据主键判断
*/
<S extends T> S save(S entity);

//通过集合保存多个实体
/*
批量保存。
原理和上面一样,实现方法就是for循环调用上面的save方法
*/
<S extends T> Iterable<S> saveAll(Iterable<S> entities);

//通主键查询实体
Optional<T> findById(ID id);

//通过主键查询实体是否存在
boolean existsById(ID id);

//查询所有的内容
Iterable<T> findAll();

//通过集合的主键,查询多个实体,返回集合
Iterable<T> findAllById(Iterable<ID> ids);

//查询数据总量
long count();

//通过id删除此实体
void deleteById(ID id);

//Deletes a given entity.根据实体进行删除
void delete(T entity);

//删除多个,传入多个ID
void deleteAllById(Iterable<? extends ID> ids);

//删除所有传入的实体
void deleteAll(Iterable<? extends T> entities);

//删除所有实体
void deleteAll();

设置
显示sql

 vendorAdapter.setShowSql(true);

(2)PagingAndSortingRepositoy
继承了CrudRepository所有的基本方法
还增加了分页和排序等对查询结果进行限制的分页方法。

public interface CustomerRepository extends PagingAndSortingRepository<Student, Long> {

}

内容:

/*
 * Copyright 2008-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

/**
 * Extension of {@link CrudRepository} to provide additional methods to retrieve entities using the pagination and
 * sorting abstraction.
 *
 * @author Oliver Gierke
 * @see Sort
 * @see Pageable
 * @see Page
 */
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

	/**
	 根据排序,取得所有对象的集合
	 */
	Iterable<T> findAll(Sort sort);

	/**
	 根据分页和排序进行查询,并用Page对象进行封装。
	 Pageable对象包含分页和Sort对象。
	 */
	Page<T> findAll(Pageable pageable);
}

eg:test
页面分页查询和排序


	/**
	 * Creates a new {@link PageRequest} with sort parameters applied.
	 *
	 * @param page zero-based page index, must not be negative.
	 * @param size the size of the page to be returned, must be greater than 0.
	 * @param sort must not be {@literal null}, use {@link Sort#unsorted()} instead.
	 */
	protected PageRequest(int page, int size, Sort sort) {
//page:页码  size:每页显示多少数据
		super(page, size);

		Assert.notNull(sort, "Sort must not be null!");

		this.sort = sort;
	}
package com.huangjuan;

import com.huangjuan.config.SpringDataJPAConfig;
import com.huangjuan.pojo.Student;
import com.huangjuan.repositories.CustomerRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

//配置
@ContextConfiguration(classes = SpringDataJPAConfig.class)
//基于Junit测试单元
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringDatajpaPagingAndSortTest {
    //jdk动态代理的实例
    @Autowired
    CustomerRepository repository;


    //进行页面的分页
    @Test
    public void testPaging(){
        //查出的页面数据存入集合
        Page<Student> all = repository.findAll(PageRequest.of(0,2));
        //Page是一个类,我们可以通过Page类中的方法,来调用变量的属性
        //获取所有的页数
        System.out.println(all.getTotalPages());
        //获取所有元素的总数量
        System.out.println(all.getTotalElements());
        //获取所有的数据
        System.out.println(all.getContent());
        //结果
    /*    1
        1
                [Student{id=4, name='李玺', sal='50000', job='舞蹈老师'}]*/
    }
}

页面排序

//进行页面的排序
    @Test
    public void testSort(){
        //通过eid进行降序排序
        Sort sort = Sort.by("eid").descending();

        //Iterable---可迭代对象
        Iterable<Student> all = repository.findAll(sort);

        System.out.println(all);
    }

设置Sort
方式一:定义排序列表

Sort.by("").descending()-----通过什么进行排序【.by("")】以及设置排序的方式【.descending()---降序】,并和排序【.and(“”)】

eg:
Sort.by("sId").descending()----通过sid进行降序排序

方式二:以安全的方式构建sort
typesort

TypedSort<实体类名> 变量名 = Sort.sort(实体类名.class);

Sort sort = 变量名.by(实体类名::获得属性名的方法).ascending()
  .and(变量名.by(实体类名::获得属性名的方法).descending());

TypedSort<Person> person = Sort.sort(Person.class);

Sort sort = person.by(Person::getFirstname).ascending()
  .and(person.by(Person::getLastname).descending());

eg:
测试


    //进行页面排序,方式二:以安全的方式构建sort
    @Test
    public void testSortTypeSafe(){
        Sort.TypedSort<Student> student = Sort.sort(Student.class);

        Sort sort = student.by(Student::getId).ascending();

    }

(3)JpaRepository
上面那些都是Spring Data 为了兼容NoSQL而进行的一些抽象封装,从JpaRepository开始是对关系型数据库进行抽象封装。
继承PagingAndSortingRepository类,也就继承了其所有的方法;拥有CRUD,分页,批量删除等方法,并将默认实现的查询结果变化了List

a.JpaRepository的使用方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.data.jpa.repository;

import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort sort);

    List<T> findAllById(Iterable<ID> ids);

    <S extends T> List<S> saveAll(Iterable<S> entities);

    void flush();

    <S extends T> S saveAndFlush(S entity);

    <S extends T> List<S> saveAllAndFlush(Iterable<S> entities);

    /** @deprecated */
    @Deprecated
    default void deleteInBatch(Iterable<T> entities) {
        this.deleteAllInBatch(entities);
    }

    void deleteAllInBatch(Iterable<T> entities);

    void deleteAllByIdInBatch(Iterable<ID> ids);

    void deleteAllInBatch();

    /** @deprecated */
    @Deprecated
    T getOne(ID id);

    T getById(ID id);

    <S extends T> List<S> findAll(Example<S> example);

    <S extends T> List<S> findAll(Example<S> example, Sort sort);
}

(4)SimleJpaRepository
SimleJpaRepository是JPA整个关联数据库的所有Repository的接口实现类,如果想进行扩展,可以继承此类。

自定义操作

对于一些复杂的语句,进行自定义操作
1、JPQL 和SQL
a.@Query进行配合使用
在@Query标签中使用SQL语句
(1)查询

  • 使用JPQL语句
    查询:
@Query("select u from User u where u.userName = ?1")
    User findByUserName(String userName);

@Test
    public void TestQ(){
        User user = userRepository.findByUserName("龙梓琦");
        System.out.println("===========================================================================");
        System.out.println("===========================================================================");
        System.out.println(user.toString());
    }

模糊查询:

//模糊查询
    @Query("SELECT u from User u where u.userName like %?1")
    User findByFirstNameEndsWith(String firstName);

	 @Test
    public void TestI(){
        User user = userRepository.findByFirstNameEndsWith("琦");
        System.out.println("====================");
        System.out.println(user.toString());
    }

注意:
报空指针:用本地数据库,要把Column那些注解都写上

@Query排序
@Query在JPQL下想实现排序,直接用PageRequest或者直接用Sort参数都可以。
在排序实例中实际使用的属性需要与实体模型里面的字段相匹配
Sort和JpaSort的使用

//Sort和JpaSort的使用
    @Query("select u from User u where u.nike like ?1%")
    List<User> findByAndSort(String nike, Sort sort);

//模糊查询+排序---JPQL
    @Test
    public void TestT(){
        Sort sort = Sort.by("password").descending();
        List<User> list = userRepository.findByAndSort("哈哈哈",sort);
        System.out.println("============================");
        System.out.println(list);
    }

@Query分页
直接用Pagr对象接受接口,参数直接用Pageable的实现类即可。

//Query分页
    @Query(value = "select u from User u where u.nike = ?1")
    Page<User> findByPage(String nike, Pageable pageable);


 //分页----JPQL,显示第一页的,每一页三个数据
    @Test
    public void TestW(){
        Page<User> list  = userRepository.findByPage("哈哈哈",PageRequest.of(1,3));
        //获取所有的内容
        System.out.println(list.getContent());
    }
  • 使用原生SQL语句
    nativeQuery排序
    nativeQuery排序不支持直接的参数查询
    正确写法:
 //原生SQL,排序
    @Query(value = "select * from user where nick=?1 order by ?2",nativeQuery = true)
    List<User> findByNike(String nike,String sort);

 //排序
    @Test
    public void TestU(){
        //调用的时候,"password"位置填入数据库的字段名,不是对象的字段名
        List<User> list = userRepository.findByNike("哈哈哈","phone");
        System.out.println("====================");
        System.out.println(list);
    }

第一步,在仓库文件中进行CDUI的操作
eg:
通过员工的名字,查询到员工的所有信息
@Query(value="select * FROM myemp where name=?1 ",nativeQuery = true)—不加nativeQuery = true会报错,有nativeQuery = true时,是可以执行原生SQL语句,所谓原生SQL,也就是说这段SQL拷贝到数据库中,然后把参数值给OK了,
查找对应的是数据库而不是实体类

package com.huangjuan.repositories;

import com.huangjuan.pojo.Student;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
//CrudRepository<持久化操作的实体类,主键类型>----增删改查
//CrudRepository中有增删改查的方法
/*public interface CustomerRepository extends CrudRepository {

}*/

public interface CustomerRepository extends PagingAndSortingRepository<Student, Long> {
    //输入员工的姓名,查询到员工的所有信息
    //不用List,如果有多条名字相同的数据,会报错
   @Query(value="select * FROM myemp where name=?1 ",nativeQuery = true)
    Student findEmpByEmpname(String name);
    //输入员工的姓名,查询到员工的所有信息
    //使用List
	@Query(value="select * FROM myemp where name=?1 ",nativeQuery = true)
    List<Studen> findEmpByEmpname(String name);
}

第二步,进行测试

package com.huangjuan;

import com.huangjuan.config.SpringDataJPAConfig;
import com.huangjuan.pojo.Student;
import com.huangjuan.repositories.CustomerRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringDataJPAConfig.class)
public class SpringDatajpaFreeTest {
    @Autowired
    CustomerRepository customerRepository;

    @Test
    public void TestR(){
        Student student = customerRepository.findEmpByEmpname("实训");
        System.out.println(student);
    }
}

注意:
查询如果返回的那个实体,就用pojo接收,如果是多个需要通过集合接收。

参数的设置方式

@Query语句与参数的绑定方法
1.索引
@Query(value="select * FROM myemp where name=?1 ",nativeQuery = true)
Student findEmpByEmpname(String name);
?1代表是第一个参数

2.通过具名参数
需要和@Param配合使用

@Query(value="select * FROM myemp where name=:ename ",nativeQuery = true)
Student findEmpByEmpname(@Param(“ename”)String name);

(2)修改
注意:采用增删改操作的时候,要使用事务,这两个注释一定要记得加
@Transactiona //通常会放在业务逻辑层上面去声明

@Modifying //通知Springdatajpa 这是增删改的操作

(3)增加
增加的Qery语句和普通的sql语句不同
给myemp表内的name字段增加查找到的数据
注意:如果是插入方法,一定只能在hibernate下才支持(insert into …select)

 //新增信息
    @Transactional
    @Modifying
    @Query(value = "INSERT myemp(name) select e.name from myemp e where e.id = ?1",nativeQuery = true)
    int insertStudentByID(Long id);

测试

//给myemp表内的name字段增加查找到的数据
@Test
public void InsertTest(){
customerRepository.insertStudentByID(2L);
}

(4)删除

//通过员工的ID,将员工的信息删除
    @Transactional
    @Modifying
    @Query(value = "delete from myemp where id=?1",nativeQuery = true)
    int deleteStudentById(@Param("id") long eid);

测试

  //删除ID对应的员工
    @Test
    public void DeletTest(){
        customerRepository.deleteStudentById(1);
    }

2.规定方法名
通过不同的方法名,JPA会自动进行生成SQL语句

支持的查询方法主题关键字(前缀)
决定当前方法的作用
只支持查询和删除
Spring Data JPA_第2张图片后两个关键字是放在前几个关键词之间的

支持的查询方法谓语关键字
Spring Data JPA_第3张图片Spring Data JPA_第4张图片模糊查询
Spring Data JPA_第5张图片Spring Data JPA_第6张图片例子:
注意:使用增删改都要加两个注解
@Transactional
@Modifying

实体类

package com.huangjuan.pojo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "dept")
public class Dept {
    @Id
    @Column(name = "id")
    private long id;

    @Column(name = "dname")
    private String dname;

    @Column(name = "phone")
    private String phone;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "id=" + id +
                ", dname='" + dname + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

仓库

package com.huangjuan.repositories;

import com.huangjuan.pojo.Dept;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.repository.CrudRepository;

import javax.transaction.Transactional;
import java.util.List;

public interface DeptMethodNameRepository extends CrudRepository<Dept,Long> {

    //找到名字对应的科目信息
    List<Dept> findByDname(String dname);

    //判断名字对应的实体是否存在
    Boolean existsByDname(String dname);

    @Modifying
    @Transactional
    //通过ID,删除相应对象
    int deleteByDname(String dname);

    //模糊查询
    Dept findByDnameLike(String dname);
}

测试

package com.huangjuan;

import com.huangjuan.config.SpringDataJPAConfig;
import com.huangjuan.pojo.Dept;
import com.huangjuan.repositories.CustomerRepository;
import com.huangjuan.repositories.DeptMethodNameRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringDataJPAConfig.class)
public class MethodNameTest {
    @Autowired
    DeptMethodNameRepository repository;

    //查询名字对应的所有科目信息
    @Test
    public void TestR(){
        List<Dept> list = repository.findByDname("会计");
        System.out.println(list);
    }

    //判断名字对应的实体是否存在
    @Test
    public void TestE(){
        Boolean re = repository.existsByDname("会计");
        System.out.println(re);
    }

    //通过ID,删除相应对象----但是删除不了,因为存在外键
   /* @Test
    public void TestD(){
        int delete = repository.deleteByDname("信息工程");
        System.out.println(delete);
    }*/

    //模糊查询
    @Test
    public void TestL(){
        //要自己配通配符
        Dept find = repository.findByDnameLike("信%");
        System.out.println(find);
    }
}

3.实现动态的SQL
(1)Query by Example
官网位置:5.1.6
a.只支持查询
i.不支持嵌套或分组的属性约束,如firstname=?0 or (firstname=?1 and lastname=?2)
ii.只支持字符串
extends QueryByExampleExecutor<实体类>
了解example类
通过example类可以构造你想到的任何筛选条件
通过匹配器(ExampleMatcher)对条件行为进行设置

注意:
如果不继承 PagingAndSortingRepository,
只继承QueryByExampleExecutor,会报错。
Eg:
仓库

package com.huangjuan.repositories;

import com.huangjuan.pojo.Dept;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;


public interface DeptQBENameRepository extends PagingAndSortingRepository<Dept,Long>,QueryByExampleExecutor<Dept> {
}

测试

package com.huangjuan;

import com.huangjuan.config.SpringDataJPAConfig;
import com.huangjuan.pojo.Dept;
import com.huangjuan.repositories.DeptQBENameRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringDataJPAConfig.class)
public class QBETest {

    @Autowired
    DeptQBENameRepository repository;

    @Test
    public void TestQ(){
        Dept dept = new Dept();
        dept.setDname("computer");
        dept.setPhone("1234567");

        //通过匹配器,对条件行为进行配置
        ExampleMatcher matcher = ExampleMatcher.matching()
                //.withIgnorePaths("dname");//设置忽略的属性
                //.withIgnoreCase("dname");//设置忽略大小写
                .withMatcher("dname",matcher1 -> matcher1.endsWith().ignoreCase());//针对单个条件进行设置,会使.withIgnoreCase("dname")失效,需要单独设置

        //通过Example创建查询条件
        Example<Dept> example = Example.of(dept,matcher);

        //执行操作
        List<Dept> list = (List<Dept>) repository.findAll(example);

        System.out.println(list);
    }
}

(2)通过Quertdsl
是一个通用的查询框架

注解:
1.具名参数 @Param
@Param----指定方法参数的具体名称,通过绑定的参数名字做查询条件
需要和@Param配合使用

@Query(value="select * FROM myemp where name=:ename ",nativeQuery = true)
Student findEmpByEmpname(@Param(“ename”)String name);
2.@Modifying修改查询
实现只需要参数绑定的增删改的执行

3.@Entity
定义对象将会称为被JPA管理的实体,将映射到指定的数据库表

4、@Table
指定数据库的表名
如果不写,系统默认为与实体类名一样的表名

5.@Id
定义属性为数据库的主键,一个实体里面必须有一个

6.@IdClass
联合主键就是说,当一个字段可能存在重复值,无法确定这条数据的唯一性时,再加上一个字,两个字段联合起来确定这条数据的唯一性。比如你提到的id和name为联合主键,在插入数据时,当id相同,name不同,或者id不同,name相同时数据是允许被插入的,但是当id和name都相同时,数据是不允许被插入的。
利用外部类的联合主键
作为符合主键类,满足的几点要求:
(1)必须实现Serialiable接口
(2)必须有默认的public无参的构造方法
(3)必须覆盖equals和hashCode方法
用法:
//createUserId和title来确定一个唯一的内容
用作者ID和文章名才能确定一篇文章
只能用于1.5版本
UserBlogKey

package com.huangjuan.pojo;

import lombok.Data;

import java.io.Serializable;

@Data
public class UserBlogKey implements Serializable {
    private String title;
    private Integer createUserId;
    //无参构造
    public UserBlogKey(){

    }
    //有参构造
    public UserBlogKey(String title, Integer createUserId) {
        this.title = title;
        this.createUserId = createUserId;
    }

}

UserBlog

package com.huangjuan.pojo;

import javax.persistence.*;

@Entity
@Table(name ="user_blog",schema = "test")
@IdClass(value = UserBlogKey.class)
public class UserBlog{
    @Column(name = "id",nullable = false)
    private Integer id;

    @Id
    @Column(name="title",nullable = true,length = 200)
    private String title;

    @Id
    @Column(name = "create_user_id",nullable = true)
    private Integer createUserId;

    @Column(name = "blog_desc")
    private String blogDesc;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Integer getCreateUserId() {
        return createUserId;
    }

    public void setCreateUserId(Integer createUserId) {
        this.createUserId = createUserId;
    }

    public String getBlogDesc() {
        return blogDesc;
    }

    public void setBlogDesc(String blogDesc) {
        this.blogDesc = blogDesc;
    }

    @Override
    public String toString() {
        return "UserBlog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", createUserId=" + createUserId +
                ", blogDesc='" + blogDesc + '\'' +
                '}';
    }
}

    @Test
    //测IdClass
    public void TestR(){
        UserBlogKey userBlogKey = new UserBlogKey("哈哈哈哈",2);
        UserBlog blog = userBlogRepostitory.findOne(userBlogKey);

7.@GneratedValue
主键生成策略

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package javax.persistence;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GeneratedValue {
		//Id的生成策略
    GenerationType strategy() default GenerationType.AUTO;
		//通过Sequences生成Id,常见的是Orcale数据库ID生成规则,需要配合@SequenceGenerator使用
    String generator() default "";
}

GenerationType一共有4个值

/*
 * Copyright (c) 2008, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Linda DeMichiel - 2.1
//     Linda DeMichiel - 2.0

package javax.persistence;

/** 
 * Defines the types of primary key generation strategies. 
 *
 * @see GeneratedValue
 *
 * @since 1.0
 */
public enum GenerationType { 

    /**
     通过表产生主键,框架由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植
     */
    TABLE, 

    /**
     * Indicates that the persistence provider must assign 
     * primary keys for the entity using a database sequence.
     */
    SEQUENCE, 

    /**
     采用数据库ID自增长,一般用于MySQL数据库
     */
    IDENTITY, 

    /**
     * Indicates that the persistence provider should pick an 
     * appropriate strategy for the particular database. The 
     * AUTO generation strategy may expect a database 
     * resource to exist, or it may attempt to create one. A vendor 
     * may provide documentation on how to create such resources 
     * in the event that it does not support schema generation 
     * or cannot create the schema resource at runtime.
     */
    AUTO
}

8.@Transient
表示该属性并非一个到数据库表的字段的映射,表示非持久化属性,JPA影响数据库的时候忽略它。

9.@Tempporal
用来设置Data类型的属性映射到对应精度的字段
(1)@Temporal(TemporalType.DATE)映射为日期 //data (只有日期)
(2)@Temporal(TemporalType.TIME)映射为日期 // time(只有时间)
(3)@Temporal(TemporalType.TIMESTAMP)映射为日期 //data time (日期+时间)

10.@Enumerated
直接映射enum枚举类型的字段

/*
 * Copyright (c) 2008, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Linda DeMichiel - 2.1
//     Linda DeMichiel - 2.0

package javax.persistence;

/**
 * Defines mapping for enumerated types.  The constants of this
 * enumerated type specify how a persistent property or
 * field of an enumerated type should be persisted.
 * 
 * @since 1.0
 */
public enum EnumType {
		//映射枚举字段的下标
    /** Persist enumerated type property or field as an integer. */
    ORDINAL,
		//映射枚举的Name
    /** Persist enumerated type property or field as a string. */
    STRING
}

//创建枚举类,用户的性别

package com.huangjuan.config;
//枚举类
public enum  Gender {
    MALL("男性"),FMALL("女性");
    private String value;
    //有参构造
    private Gender(String value){
        this.value = value;
    }
}

//实体类
  //枚举
    @Enumerated(EnumType.STRING)
    @Column(name = "user_gender")
    private Gender gender;

关联关系,多表查询

依然是hibernate进行操作
https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#associations-many-to-one
(1)相关注解
@JoinColumn
定义外键关联的字段名称
主要配合@OneToOne,@ManyToOne,@OneToMany一起使用,单独使用没有意义
@JoinColumns定义多个字段的关联关系

package javax.persistence;

import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static javax.persistence.ConstraintMode.PROVIDER_DEFAULT;

/**
 * @see ManyToOne
 * @see OneToMany
 * @see OneToOne
 * @see JoinTable
 * @see CollectionTable
 * @see ForeignKey
 *
 * @since 1.0
 */
@Repeatable(JoinColumns.class)
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface JoinColumn {

    /** 
     目标表的字段名,必填
     */
    String name() default "";

    /**
    本实体的字段名,非必填,默认是本表ID
     */
    String referencedColumnName() default "";

    /**
   外键字段是否唯一
     */
    boolean unique() default false;

    /** 外键字段是否允许为空 */
    boolean nullable() default true;

    /**
    是否跟随一起新增
     */
    boolean insertable() default true;

    /**
    是否跟随一起更新
     */
    boolean updatable() default true;

    /**
     * (Optional) The SQL fragment that is used when
     * generating the DDL for the column.
     * 

Defaults to the generated SQL for the column. */ String columnDefinition() default ""; /** * (Optional) The name of the table that contains * the column. If a table is not specified, the column * is assumed to be in the primary table of the * applicable entity. * *

Default: *

    *
  • If the join is for a OneToOne or ManyToOne mapping * using a foreign key mapping strategy, the name of the table of * the source entity or embeddable. *
  • If the join is for a unidirectional OneToMany mapping * using a foreign key mapping strategy, the name of the table of * the target entity. *
  • If the join is for a ManyToMany mapping or * for a OneToOne or bidirectional ManyToOne/OneToMany mapping * using a join table, the name of the join table. *
  • If the join is for an element collection, the name of the collection table. *
*/
String table() default ""; /** * (Optional) Used to specify or control the generation of a * foreign key constraint when table generation is in effect. If * this element is not specified, the persistence provider's * default foreign key strategy will apply. * * @since 2.1 */ ForeignKey foreignKey() default @ForeignKey(PROVIDER_DEFAULT); }

@OneToOne 关联关系
* cascade 配置关联操作
* All 所有持久化操作
* Persist 只有插入才会执行关联操作
* Merge 只有修改才会执行关联操作
* Remove 只有删除才会执行关联操作
*
* 默认为立即查询–EAGER
* fetch()—FetchType
* 还有一个状态—LAZY 懒加载----知道用到了对象,才会进行查询(不是所以的关联对象,都需要用到
* 懒加载要配置事务(@Transactional),当通过repository调用完查询方法,session会立即关闭,一旦session关闭了,就不能进行查询了。加了事务之后,就能让session在事务都执行完才关闭
*
* orphanRemoval 关联移除(通常在修改的时候会用到)
* 一旦把关联的数据设置null,或者修改为其他的关联数据, 如果想删除关联数据,就可以设置为true
*
*
* optional()----是否可以为空
* false 设置的关联对象不能为空

通过外键关联
例子:一个客户对应一个账户
增加用户的同时增加一个账户
查客户的同时还能查到客户的用户名和密码
删除客户其账户也删除
1.配置关联关系
单向管理
客户中有账户,账户中没有客户
客户进行管理
Customer表

package com.hunagjuan.entity;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "tb_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增长
    @Column(name = "id")
    private Long id;

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

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

    //单向管理,一对一
    /*
    * cascade 配置关联操作
    * All  所有持久化操作
    * Persist   只有插入才会执行关联操作
    * Merge     只有修改才会执行关联操作
    * Remove    只有删除才会执行关联操作
    * 如果想要的操作和设置的关联操作不一致,则只能对本实体进行操作,并不能对关联实体进行操作
    *
    * 默认为立即查询--EAGER
    * fetch()---FetchType
    * 还有一个状态---LAZY 懒加载----知道用到了对象,才会进行查询
    *
    *  orphanRemoval  关联移除(通常在修改的时候会用到)
    *   一旦把关联的数据设置null,或者修改为其他的关联数据,  如果想删除关联数据,就可以设置为true
    *
    *
    * optional()----是否可以为空
    * false 设置的关联对象不能为空
    * true  默认
    * */
    @OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    //设置外键的字段名
    @JoinColumn(name= "account_id") //对外键的ID取名字
    private Account account;


}


Account表

package com.huangjuan.entity;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "tb_account")
public class Accout {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private  String username;

    private String password;


}

2.配置关联操作

package com.huangjuan;


import com.hunagjuan.config.SpringDataJPAConfig;
import com.hunagjuan.config.SpringJPAConfig;
import com.hunagjuan.entity.Account;
import com.hunagjuan.entity.Customer;
import com.hunagjuan.repositories.CustomerRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@EntityScan(basePackages = "com.zl.bean")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringDataJPAConfig.class)
public class OneToOneTest {
    @Autowired
    CustomerRepository customerRepository;

    //插入
    @Test
    public void testC(){
        //初始化数据
        Account account = new Account();
        account.setUsername("xushu");
        account.setPassword("12134");

        Customer customer = new Customer();
        customer.setCustName("徐庶");
        customer.setAccount(account);

        customerRepository.save(customer);
    }

    //查询
    @Test
    public void testQ(){
        //左关联查询
        System.out.println(customerRepository.findById(1L));
    }

    //删除
    @Test
    public void testD(){
        customerRepository.deleteById(1L);
    }

    //修改---修改一定要传入ID
    @Test
    public void testU(){
        Customer customer = new Customer();
        customer.setId(2L);
        customer.setCustAddress("吉林");
        customer.setCustName("鹿晗");
        customerRepository.save(customer);
    }
}

双向关联

在实体类中,用户有账户,账户中有客户
账户也维护客户的关系
但不能互相关联—》不能设置两个外键约束
如果删除,只有主表的数据删除,从表的数据才能删除。这样有两个外键,如果进行删除,就会出现矛盾。
解决方法:
将一方的外键约束取消
@JoinColumn—mappedBy 将外键约束执行另一方维护
值= 另一方关联属性属性名

注意:在配置外键@JoinColumn后,先把之前的外键删除,重新配
Customer

package com.hunagjuan.entity;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "tb_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增长
    @Column(name = "id")
    private Long id;

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

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

    //单向管理,一对一
    /*
    * cascade 配置关联操作
    * All  所有持久化操作
    * Persist   只有插入才会执行关联操作
    * Merge     只有修改才会执行关联操作
    * Remove    只有删除才会执行关联操作
    * 如果想要的操作和设置的关联操作不一致,则只能对本实体进行操作,并不能对关联实体进行操作
    *
    * 默认为立即查询--EAGER
    * fetch()---FetchType
    * 还有一个状态---LAZY 懒加载----知道用到了对象,才会进行查询
    *
    *  orphanRemoval  关联移除(通常在修改的时候会用到)
    *   一旦把关联的数据设置null,或者修改为其他的关联数据,  如果想删除关联,就可以设置为true
    * 	 设为true插入时,只会插入一方不会插入另一方。
    *
    *
    * optional()----是否可以为空
    * false 设置的关联对象不能为空
    * true  默认
    *
    * mappedBy 将外键约束执行另一方维护
		值= 另一方关联属性属性名
    * */
    @OneToOne(mappedBy = "customer",cascade = CascadeType.ALL,orphanRemoval = true)
    //设置外键的字段名
    @JoinColumn(name= "account_id") //对外键的ID取名字
    private Account account;


}

Account

package com.hunagjuan.entity;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "tb_account")
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增长
    private Long id;

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

    @Column(name="password")
    private String password;

    @OneToOne(cascade = CascadeType.ALL)
    //设置外键的字段名
    @JoinColumn(name= "customer_id") //对外键的ID取名字
    private Customer customer;
}

测试


    //双向关联,测试插入
    //在用户中设置账户内容,在账户设置用户内容
    @Test
    public void testT(){
        //初始化数据
        Customer customer = new Customer();
        customer.setCustName("徐庶");

        Account account = new Account();
        account.setUsername("xushu");
        account.setPassword("12134");

        account.setCustomer(customer);

        customerRepository.save(account);
    }

    //删除
    @Test
    public void testW(){
        customerRepository.deleteById(3L);
    }

一对多
@OneToMany注解
一个用户有多条信息
外键设置在多的这一端
Customer

package com.hunagjuan.entity;


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


@Entity
@Table(name = "tb_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增长
    @Column(name = "id")
    private Long id;

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

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

    //单向管理,一对一
    /*
    * cascade 配置关联操作
    * All  所有持久化操作
    * Persist   只有插入才会执行关联操作
    * Merge     只有修改才会执行关联操作
    * Remove    只有删除才会执行关联操作
    * 如果想要的操作和设置的关联操作不一致,则只能对本实体进行操作,并不能对关联实体进行操作
    *
    * 默认为立即查询--EAGER
    * fetch()---FetchType
    * 还有一个状态---LAZY 懒加载----知道用到了对象,才会进行查询
    *
    *  orphanRemoval  关联移除(通常在修改的时候会用到)
    *   一旦把关联的数据设置null,或者修改为其他的关联数据,  如果想删除关联数据,就可以设置为true
    *
    *
    * optional()----是否可以为空
    * false 设置的关联对象不能为空
    * true  默认
    *
    * mappedBy 将外键约束执行另一方维护
		值= 另一方关联属性属性名
    * */
    @OneToOne(mappedBy = "customer",cascade = CascadeType.ALL,orphanRemoval = true)
    //设置外键的字段名
    @JoinColumn(name= "account_id") //对外键的ID取名字
    private Account account;


    //一对多
    //声明在多的一方的主键字段名称 name = "customer_id"
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "customer_id")
    private List<com.hunagjuan.entity.Message> messages;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCustName() {
        return custName;
    }

    public void setCustName(String custName) {
        this.custName = custName;
    }

    public String getCustAddress() {
        return custAddress;
    }

    public void setCustAddress(String custAddress) {
        this.custAddress = custAddress;
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    public List<Message> getMessages() {
        return messages;
    }

    public void setMessages(List<Message> messages) {
        this.messages = messages;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", custName='" + custName + '\'' +
                ", custAddress='" + custAddress + '\'' +
                ", account=" + account +
                ", messages=" + messages +
                '}';
    }
}

messages

package com.hunagjuan.entity;

import lombok.Data;

import javax.persistence.*;


@Entity
@Table(name="tb_message")
@Data
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "info")
    private String info;

    public Message(String info) {
        this.info = info;
    }

    //无参构造函数一定要有,否则查询一定会出问题
    public Message(){

    }
}

MoreToOne多对一
多的一方维护关系
对应的为一,则设置的为对象,不是集合
不管是一对多,还是多对一,都只有一个关联的字段。(只用声明一个JoinColumn,且声明的字段为一的主键)

记得设置cascade
一般都是用多对一
得出:插入“多”的数据的时候,一般都用多对一的关系进行保存比较合理。
通过客户的ID查出所有的信息,通过一对多关系更合理。

插入“多”的数据的时候,一般都用多对一的关系进行保存比较合理。
Message

package com.hunagjuan.entity;

import lombok.Data;

import javax.persistence.*;


@Entity
@Table(name="tb_message")
@Data
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "info")
    private String info;

    public Message(String info) {
        this.info = info;
    }

    public Message(String info, Customer customer) {
        this.info = info;
        this.customer = customer;
    }

    //无参构造函数一定要有,否则查询一定会出问题
    public Message(){

    }

    //多对一
    @ManyToOne(cascade = CascadeType.ALL)
    private Customer customer;
}

Customer

package com.hunagjuan.entity;


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


@Entity
@Table(name = "tb_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增长
    @Column(name = "id")
    private Long id;

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

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

    //单向管理,一对一
    /*
    * cascade 配置关联操作
    * All  所有持久化操作
    * Persist   只有插入才会执行关联操作
    * Merge     只有修改才会执行关联操作
    * Remove    只有删除才会执行关联操作
    * 如果想要的操作和设置的关联操作不一致,则只能对本实体进行操作,并不能对关联实体进行操作
    *
    * 默认为立即查询--EAGER
    * fetch()---FetchType
    * 还有一个状态---LAZY 懒加载----知道用到了对象,才会进行查询
    *
    *  orphanRemoval  关联移除(通常在修改的时候会用到)
    *   一旦把关联的数据设置null,或者修改为其他的关联数据,  如果想删除关联数据,就可以设置为true
    *
    *
    * optional()----是否可以为空
    * false 设置的关联对象不能为空
    * true  默认
    *
    * mappedBy 将外键约束执行另一方维护
		值= 另一方关联属性属性名
    * */
    @OneToOne(mappedBy = "customer",cascade = CascadeType.ALL,orphanRemoval = true)
    //设置外键的字段名
    @JoinColumn(name= "account_id") //对外键的ID取名字
    private Account account;


    //一对多
    //声明在多的一方的主键字段名称 name = "customer_id"
    //一对多的情况下,默认是懒加载
    @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private List<com.hunagjuan.entity.Message> messages;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCustName() {
        return custName;
    }

    public void setCustName(String custName) {
        this.custName = custName;
    }

    public String getCustAddress() {
        return custAddress;
    }

    public void setCustAddress(String custAddress) {
        this.custAddress = custAddress;
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    public List<Message> getMessages() {
        return messages;
    }

    public void setMessages(List<Message> messages) {
        this.messages = messages;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", custName='" + custName + '\'' +
                ", custAddress='" + custAddress + '\'' +
                ", account=" + account +
                ", messages=" + messages +
                '}';
    }
}

repository

package com.hunagjuan.repositories;

import com.hunagjuan.entity.Message;
import org.springframework.data.repository.CrudRepository;

public interface MessageRepository extends CrudRepository<Message,Long> {
}

测试:

//多对一的插入
    @Test
    public void testC(){
        //传入customer和info

        //一
        Customer customer = new Customer();
        customer.setCustName("司马懿");

        //多
        List<Message> list = new ArrayList<>();
        list.add(new Message("您好",customer));
        list.add(new Message("挺好的",customer));
        messageRepository.saveAll(list);
    }

通过客户的ID查出所有的信息,用多对一的关系怎么查询呢???


    //查询
    //通过用户的ID,查询其相关信息
    @Test
    public void TestU(){
        //就算传入的Customer设置了名称,也只能通过关联属性(用户的ID)进行查询,其他属性设置了也没用
        Customer customer = new Customer();
        customer.setId(10L);
        customer.setCustName("xxxxx");
        
        List<Message> list = messageRepository.findByCustomer(customer);
        //隐式调用toString方法
        System.out.println(list);
    }

这样测试会造成死循环。
反复调用双方的toString(),进入死循环
解决:重写方法


    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", info='" + info + '\'' +
                ", customerId=" + customer.getId() +
                ", customerName=" + customer.getCustName() +
                ", customerAddress=" + customer.getCustAddress() +
                '}';
    }

删除某一个用户的所有信息
要先查找到用户的信息,再进行删除


    //删除
    @Test
    public void TestD(){
        //就算传入的Customer设置了名称,也只能通过关联属性(用户的ID)进行删除,其他属性设置了也没用
        Customer customer = new Customer();
        customer.setId(10L);

        List<Message> list = messageRepository.findByCustomer(customer);
       
        messageRepository.deleteAll(list);
    }

MoreToMore多对多
@ManyToMany
单向多对多
注意:@Commit 要提交事务

Customer

  //单向多对多
    @ManyToMany(cascade = CascadeType.ALL)
    /*
    * 多对多需要设计中间表
    * 中间表需要通过@JoinTable来维护外键:(不设置也会自动生成)
    * name 指定中间表的名称
    * joinColumns 设置本表的外键名称
    * inverseJoinColumns 设置关联表的外键名称
    * */
    @JoinTable(
            name = "tb_customer_role",
            joinColumns = {@JoinColumn(name = "id")},
            inverseJoinColumns = {@JoinColumn(name = "r_id")}
    )

Role

package com.hunagjuan.entity;

import javax.persistence.*;

@Entity
@Table(name = "tb_role")
public class Role {
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自动递增
    @Id
    private Long id;

    private String roleName;

    private String roleDesc;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleDesc() {
        return roleDesc;
    }

    public void setRoleDesc(String roleDesc) {
        this.roleDesc = roleDesc;
    }

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", roleName='" + roleName + '\'' +
                ", roleDesc='" + roleDesc + '\'' +
                '}';
    }
}

test

package com.huangjuan;

import com.hunagjuan.config.SpringDataJPAConfig;
import com.hunagjuan.entity.Customer;
import com.hunagjuan.repositories.CustomerRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Commit;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@ContextConfiguration(classes = SpringDataJPAConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class MoreToMoreTest {
    @Autowired
    CustomerRepository customerRepository;

    //查询用户信息
    @Test
    @Transactional
    @Commit
    public void testD(){
            Optional<Customer> customer = customerRepository.findById(12L);
            System.out.println(customer);
    }

    //删除用户信息
    @Test
    @Transactional
    @Commit
    public void testU(){
        Optional<Customer> customer = customerRepository.findById(12L);
        customerRepository.delete(customer.get());
    }

}

双向多对多
注意:只用一方设置中间表
多对多不适合删除,因为经常出现数据可能除了当前这端关联还会关联另一端,删除不成功。
要保证没有额外的其他另一端关联。
role

 //双向多对多
    @ManyToMany(cascade = CascadeType.ALL)
    private List<Customer> customers;

乐观锁

防并发修改

private @Version Long version

使用:
在想防止并发修改的表中,写入version字段

version 初始为0(默认)

审计

可以获取当前执行操作的人,开始时间和结束时间
配置依赖

 <!--审计-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.10</version>
            <scope>test</scope>
        </dependency>

编写AuditorAware,在javaConfig中

@EnableJpaAuditing//启用审计

  //AuditorAwore 返回当前用户
    @Bean
    public AuditorAware<String> auditorAware(){
        return new AuditorAware(){

            @Override
            public Optional getCurrentAuditor() {
                //当前用户
                return Optional.of("xishu");
            }
        };
    }

在实体类中声明@EntityLiseners和相对应的注释

@EntityListeners(AuditingEntityListener.class)

 /* 审计*/
    //创建人
    @CreatedBy
    String createdBy;

    //最后修改人
    @LastModifiedBy
    String modifiedBy;

    /*
    * 实体创建时间
    * */
    @Temporal(TemporalType.TIMESTAMP)
    @CreatedDate
    protected Date dateCreated = new Date();

    /**
     * 实体修改时间
     */
    @Temporal(TemporalType.TIMESTAMP)
    @LastModifiedDate
    protected Date dateModified = new Date();

异常

failed to lazily initialize a collection of role 异常

懒加载异常, @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
不会再报错

如果想使用懒加载,一定要加@Transactional。不然Session关闭,就不能加载了。

一对多的情况下,默认是懒加载

懒加载的优点:
提高查询性能

2.StackOverflowError----栈溢出
栈溢出的原因主要是因为进入了死循环,递归重复调用

3.插入操作一直没有作用
设置懒加载时,用了@Transactional会自动回滚事务,需要在单元测试上面加上@Rollback(false)

你可能感兴趣的:(java,spring)