spring data jpa 学习整理

 Spring Data Jpa

依赖

  • maven依赖
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

.yml文件

  • 配置mysql,jpa等
server:
  port: 8080
  context-path: /

helloWorld: spring Boot\u5927\u7237\u4F60\u597D

msyql:
   jdbcName: com.mysql.jdbc.Driver
   dbUrl: jdbc:mysql://localhost:3306/wj33
   userName: root
   password: 

spring:
   datasource:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/wj33?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: 
   jpa:
     hibernate.ddl-auto: update
     show-sql: true
     database-platform: com.lunwen.wangjie.config.MySQL5DialectUTF8

   thymeleaf:
     cache: false

实体类

  • 实体类的注解 会根据注解自动生成表字段,
  • @Table指定表名,不填,默认为类名
  • @Column指定字段名,不填,默认为属性名
  • 其他注解指定字段的规则
import javax.persistence.*;

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    /** 姓名 */
    @Column(name = "name")
    private String name;

    /** 密码 */
    @Column(name = "password")
    private String password;

    /** email */
    @Column(name = "email")
    private String email;

    /** level 用于判断学生0 教师1 管理员2*/
    @Column(name = "level")
    private int level;

    /** 学号 工号 */
    @Column(name = "number", unique = true)
    private String number;

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }
}

数据访问层

  • 操作数据访问的接口,支持三种查询,hql, sql, 方法名语义分析。
  • 下列代码是通过hql查询,也就是通过类名,属性名
import com.lunwen.wangjie.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface UserDao extends JpaRepository<User, Long>{

    /**
     * 通过名字 查找用户
     * @param name 姓名
     * @return User
     */
    @Query("from User u where u.name=:name")
    User findUser(@Param("name") String name);

    /**
     * 通过email查找用户
     * @param email
     * @return
     */
    @Query("from User u where u.email=:email")
    User findUserByEmail(@Param("email") String email);

    /**
     * 通过学号/工号查找用户
     * @param number number
     * @return User
     */
    @Query("from User u where u.number=:number")
    User findUserByNumber(@Param("number") String number);

    //User findUserByNumber(String numer);
}
  • 也可能过原生的sql查询@Query(“原生sql”)
  • 也可通过方法名拼接如下
  • findAllByWno,会语义分析为查找所有的当前类,通过wno字段,从数据库中去捞数据,一般Idea会自动告诉你如何拼接可行的方法名。
  • 还可以继承分页的数据访问Repository,现在继承的是JpaRepository,里面指定类名,就会关联的数据库中的表。
import com.lunwen.wangjie.model.StudentWork;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

/**
 * Created with IDEA
 * author: wangjie
 */
public interface StudentWorkDao extends JpaRepository<StudentWork, Long>{

    List<StudentWork> findAllByWno(Long wno);

    List<StudentWork> findAllBySno(String sno);


    StudentWork findStudentWorkBySnoAndWno(String sno, Long wno);

}
  • 高级内容,一对一,一对多,多对多,懒延迟等,后续说。

急切和延迟加载

  • JPA中最重要的概念是为了使数据库的副本在高速缓冲存储器中。虽然有一个数据库事务,但JPA首先创建一个重复的数据集,只有当它使用实体管理提交,所做的更改影响到数据库中。
  • 从数据库中获取记录有两种方式
    • 预先抓取,相关子对象获取一个特定的记录自动上传
    • 延迟加载,在延迟装载,涉及的对象不会自动上传,除非你特别要求他们。

级联

  • cascade:定义类和类之间的级联关系。级联关系定义对当前对象的操作将波及到关联类的对象,而且这个种关系是递归调用的。如School与Student有级联删除关系,那么删除School时将同时删除关联的Student对象。如果Student还与其他关联,会递归删除下去。
  • CascadeType.PERSIST 级联持久化(保存)操作(持久保存拥有方实体时,也会持久保存该实体的所有相关数据。)
  • CascadeType.REMOVE Cascade remove operation,级联删除操作。 删除当前实体时,与它有映射关系的实体也会跟着被删除。
  • CascadeType.MERGE Cascade merge operation,级联更新(合并)操作。当Student中的数据改变,会相应地更新Course中的数据
  • CascadeType.DETACH Cascade detach operation,级联脱管/游离操作。如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。
  • CascadeType.REFRESH Cascade refresh operation,级联刷新操作。假设场景 有一个订单,订单里面关联了许多商品,这个订单可以被很多人操作,那么这个时候A对此订单和关联的商品进行了修改,与此同时,B也进行了相同的操作,但是B先一步比A保存了数据,那么当A保存数据的时候,就需要先刷新订单信息及关联的商品信息后,再将订单及商品保存。(来自良心会痛的评论) CascadeType.ALL
  • Cascade all operations,清晰明确,拥有以上所有级联操作权限。

JPA实体关系

  • 实体关系是指实体与实体之间的关系,从方向上分为单向关联和双向关联,从实体数量上分为一对一,一对多,多对多等,对于任何两个实体,都要从这个两个方面区分它们之间的关系。单向关联是一个实体中引用了另外一个实体,也即通过一个实体可以获取另一个实体对象的引用,双向关联是两个实体之间可以互相获取对方对象的引用。
  • 一对一 @OneToOne
    • 学生证和身份证是一对一的关系。
      • 单向关联
        • 假设在学生实体中可以获取身份证对象的引用;反之,不能再身份证实体中获取学生对象的引用,则学生和身份证是一对一的单向关联关系
        • 这时候只需要在学生实体中的IDCrad_id加上@JoinColumn(name = “外键表字段名”),以及@OneToOne(cascade=CascadeType.All)
        • @OneToOne 只能确定实体与实体之间的关系是一对一的关系,不能指定数据库表中的保存的关联字段,所以要结合@JoinColumn来指定关联字段
        • 在默认情况下,关联实体(IDCard)的主键一般是用来做外键的,但如果此时不想将关联实体的主键作为外键,需要设置@JoinColumn的referencedColumnName属性
      • 双向关联
        • 需要在接受方的字段定义一个@OneToOne(mappedBy = “主动方一对一的属性名”)
      • 主键关联
        • 既让两个实体对象具有相同的主键值,以表明它们之间的一一对应关系;而数据库不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联,主键的值需要程序来显示维护。
        • 只要在@OneToOne的下面添加一个@PrimaryKeyJoinColumn
      • 总结
        • 确定实体与实体之间的关系,如果是一对一关系 则使用@OneToOne
        • 确定表的结构的设计
          • 如果是外键关联,在关系维护段考虑默认的实体关系映射或@JoinColumn
          • 如果表位于不同的数据中,可以采用主键关联,使用@PrimaryKeyJoinColumn
        • 确定实体关系的方向
          • 单向关系 在保存关联关系的实体中,使用@JoinColumn
          • 双向关系 则在保存关联关系(也即存在外键)的实体中,要配合@JoinColumn;在没有保存关联关系的实体中,需使用mappedBy属性明确所关联的实体
          • mappedBy用在双向关联中,mappedBy所在的实体为关系被维护端,而另一个实体为关系维护端(也即保存关联关系的一端)
  • 一对多
    • 单向关联
      • 使用@ManyToOne 比如一个学校可能有多个学生 ,那就可以在学生的schoolId字段加上@JoinColumn(name=”school_id”) @ManyToOne
    • 双向关联
      • 在学校的students字段加一个 @OneToMany(mappedBy=”school”, cascade=CascadeType.ALL) 学生表中的SchoolId字段@JoinColumn(name = “”) @ManyToOne
      • option:指定级联方是否可以为空,默认为true,允许外键字段为空,若将其设置为false,则双方必须存在,也即外键字段不能为空
    • 表关联(双向)
      • 在一对多或多对一的关联关系中,除了使用默认的外键关联外;还可以使用表关联,多的一方可以作为关系表的主键(唯一性约束),而一的一方可以作为关系表另一个字段。
      • 在多的一方作为关系表的主键 name : 中间表 joinColumns:中间表中,指向关系维护端的外键; inverseJoinColumns:与joinColumns相似,指向中间表中被维护端的外键
        java
        @ManyToOne
        @JoinTable(name="SCHOOL_STUDENT",
        joinColumns={
        @JoinColumn(name="student_id",referencedColumnName="id") //自方
        },
        inverseJoinColumns={
        @JoinColumn(name="school_id",referencedColumnName="id") //一方
        })
      • 在一的一方 @OneToMany(mappedBy=”school”)
    • 总结
      • 确定实体与实体之间的关系,如果是一对多的关系,则使用@OneToMany;如果是多对一的关系使用@ManyToOne
      • 确认表结构的设计
        • 如果是外键关联,在关系维护端考虑默认的实体关系映射或配合JoinColumn
        • 如果是表关联,则在关系维护端使用@JoinTable
      • 确定实体关系的方向
        • 单向关联 一般情况下,多的一方为关系维护端,在保存关联关系实体中使用@JoinColumn或@JoinTable
        • 若为双向关联,则在关系维护端,配合@JoinColumn或@JoinTable;在关系呗维护端,要使用mappedBy属性明确所关联的实体
  • 多对多
    • 单向关联
      • 老师和学生就是一个多对多关系 ,在老师实体类中维护
        java
        @ManyToMany(fetch=FetchType.LAZY)
        @JoinTable(name="TEACHER_STUDENT",
        joinColumns={@JoinColumn(name="teacher_id")},
        inverseJoinColumns={@JoinColumn(name="student_id")})
    • 双向关联
      • 在老师中如上维护,在学生中处理接受方 @ManyToMany(mappedBy=”students”,fetch=FetchType.LAZY)
    • 总结
      • 单向关联在维护端使用@JoinTable
      • 双向关联在维护端使用@JoinTable 在关系被维护端,要使用mappedBy属性明确所关联
      • FetchType.EAGER:代表立即加载;
      • FetchType.LAZY:代表延迟加载。

数据的级联抓取(Fetch)

  • 基础知识
    • 级联抓取(Fetch)是JPA提供的实体键的关联属性,用于执行实体的读操作,同时对其关联的其他实体进行操作
    • 如果不配置fetch属性,则缺省值为
      • 如果对端为”一”,即@OneToOne或@ManyToOne映射的属性,缺省为FetchType.EAGER,即缺省会将关联数据抓取出来。
      • 如果对端为”多”,即@OneToMany或@ManyToMany映射的属性,缺省为FetchType.LAZY,即缺省不会将关联的数据抓取出来。
    • 使用 JPA 级联抓取数据时,可通过指定 EntityGraph 配置连接关系 @EntityGraph(attributePaths = “extra”) ,EntityGraph 已经将实体本身的 fetch 属性覆盖了,即使配置了 FetchType.LAZY,只要在查询时配置了 EntityGraph,关联的对象将以最高效的方式查询出来。
    • 如果 Fetch 属性配置在实体上,那么所有对实体的查询操作都将受其影响,无论这些关联数据是否需要都会被查出来。而使用 EntityGraph,配置到数据访问方法上,则可以很精确的配置究竟需要哪些关联数据,粒度更精准。
    • 为了避免无谓的效率浪费,我们建议:所有的 @OneToOne 和 @ManyToOne 关联实体,均应配置 fetch = FetchType.LAZY,避免缺省将关联实体查询出来。当确实需要使用关联实体时,在数据访问方法中使用 EntityGraph 配置。
    • @Transactional 注解;同时由于要执行数据保存操作,故需要增加 @Commit 注解,使得事物在方法退出后提交。如果只写 @Transactional,不写 @Commit,则方法退出后事物缺省回滚
  • spring框架对Jpa提供了以下的支持
    • 它使得 JPA 配置变得更加灵活。JPA 规范要求,配置文件必须命名为 persistence.xml,并存在于类路径下的 META-INF 目录中。该文件通常包含了初始化 JPA 引擎所需的全部信息。Spring 提供的 LocalContainerEntityManagerFactoryBean 提供了非常灵活的配置,persistence.xml 中的信息都可以在此以属性注入的方式提供
    • 其次,Spring实现了部分在EJB容器 下才具有的功能
    • Spring将EntityManager的创建和销毁,事务管理等代码抽取出来,并由其统一管理
  • Spring Data Jpa框架主要针对的就是Spring唯一没有简化到的业务逻辑代码,不用实现持久层的业务逻辑,唯一需要做的是声明持久层的接口,其他都交托给spring data Jpa完成。
  • 让持久层接口DAO,集成Repository接口
    • 该接口使用了泛型,需要为其提供两个类型:第一个为该接口处理的域对象类型,第二个为该域对象的主键类型。如下:
      java
      public interface UserDao extends Repository<AccountInfo, Long> {
      public AccountInfo save(AccountInfo accountInfo);
      }

      • 不需要使用UserDao的实现类,框架会为我们完成业务逻辑
      • 在spring配置文件中启用扫描并创建代理的功能
        xml
        <-- 需要在 <beans> 标签中增加对jpa命名空间的引用 -->
        <jpa:repositories base-package="footmark.springdata.jpa.dao"
        entity-manager-factory-ref="entityManagerFactory"
        transaction-manager-ref="transactionManager" />
  • spring data jpa进行持久层开发大致需要的三个步骤
    • 声明持久层的接口,该接口继承Repository,Repository是一个标记型接口,它不包含任何方法,当然如果有需要,Spring Data 也提供了若干的Repository的子接口,其中定义了一些常用的增删改查,以及分页相关的方法。
    • 在接口中声明需要的业务方法,spring data 将根据给定的策略来为其生成实现代码。
    • 在spring配置文件中增加一行声明,让spring为声明的接口创建代理对象。配置了后,spring初始化容器时会扫描base-package指定的包目录及其子目录,为继承Repository或其子接口的接口创建代理对象,并将代理对象注册为Spring bean,业务层便可以通过spring自动封装的特性来直接使用该对象。
    • 此外还提供了一些属性和子标签,便于做更细粒度的控制,可以在内部使用、来过滤掉一些不希望被扫描的接口
  • 如果持久层接口较多,且每一个接口都需要声明相似的增删改查,可以继承CrudRepository接口,会自动为域对象创建增删改查方法,供业务层使用,可能暴露了不希望暴露给业务层的方法
  • 分页查询和排序是持久层常用的功能,spring data 为此提供了PagingAndSortingRepository接口,它继承自CrudRepository接口,在CrudRepository基础上新增了两个与分页有关的方法。但是,我们很少会将自定义的持久层接口直接继承自PagingAndSortingRepository,而是在继承Repository或CrudRepository,在自己声明的方法的参数列表加一个Pageable或Sort类型的参数,用于指定分页或排序信息即可,这比直接使用PagingAndSortingRepository提供了更大的灵活性。
  • JpaRepository是继承自PagingAndSortingRepository的针对JPA提供接口,它在父及接口的基础上提供了其他一些方法,比如flush(),saveAndFlush,deleteInBatch().

  • 查询方法

    • 框架会对方法名进行解析,会把方法名多余的前辍截取掉,比如find,findBy,read,readBy,get,getBy,然后对剩下部分进行解析。并且如果方法的最后一个参数是Sort或者Pagealbe类型,也会提取相关信息,以便按规则进行排序或者分页查询。
    • 在创建查询时,我们通过在方法名中使用属性名称来表达,比如findByUserAddressZip(). 首先剔除findBy,然后对剩下属性进行解析,详细规则如下(此处假设该方法针对的域对象为AccountInfo类型)
      • 先判断userAddressZip(根据POJO规范,首字母变为小写,下同),是否为AccountInfo的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
      • 从右往左边截取第一个大写字母开头的字符串(此处是Zip),然后检查剩下的字符串是否为AccountInfo的一个属性,如果是,则表示根据该属性进行查询,如果没有该属性,则重复第二步,继续从右往左进行字符串截取,最后假设user为AccountInfo的一个属性
      • 接着处理剩下部分(AddressZip),先判断user所对于的类型是否有addressZip属性,如果有,则表示该方法最终是根据”AccountInfo.user.addressZip”的取值进行查询的,否则继续按照步骤2的规则从右往左边截取,最终表示更加”AccountInfo.user.address.zip”的值进行查询。否则继续按照步骤2的规则从右往截取;最后表示根据”AccountInfo.user.addrsss.zip”的值进行查询。
    • 在查询时通常需要同时根据多个属性进行查询,且查询的条件也各式各样(大于某个值,在某个范围等等),spring data jpa为此提供了一些表达条件查询的关键字
      • And — 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
      • Or — 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
      • Between — 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
      • LessThan — 等价于 SQL 中的 “<”,比如 findBySalaryLessThan(int max);
      • GreaterThan — 等价于 SQL 中的”>”,比如 findBySalaryGreaterThan(int min);
      • IsNull — 等价于 SQL 中的 “is null”,比如 findByUsernameIsNull();
      • IsNotNull — 等价于 SQL 中的 “is not null”,比如 findByUsernameIsNotNull();
      • NotNull — 与 IsNotNull 等价;
      • Like — 等价于 SQL 中的 “like”,比如 findByUsernameLike(String user);
      • NotLike — 等价于 SQL 中的 “not like”,比如 findByUsernameNotLike(String user);
      • OrderBy — 等价于 SQL 中的 “order by”,比如 findByUsernameOrderBySalaryAsc(String user);
      • Not — 等价于 SQL 中的 “! =”,比如 findByUsernameNot(String user);
      • In — 等价于 SQL 中的 “in”,比如 findByUsernameIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
      • NotIn — 等价于 SQL 中的 “not in”,比如 findByUsernameNotIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
    • @Query创建查询

      • @Query注解的使用非常简单,只需要在声明的方法上面标注该注解,同时提供一个JPQL查询语句即可如下所示

        public interface UserDao extends Repository<AccountInfo, Long> {
            @Query("select a from AccountInfo a where a.accountId = ?1")
            public AccountInfo findByAccountId(Long accountId);
            @Query("select a from AccountInfo a where a.balance >?1")
            public Page<AccountInfo> findByBalanceGreaterThan(Integer balance, Pageable pageable);
        }
        
      • 创建JP QL时用明明参数来代替位置编号,@Query也对此提供了支持。JP QL中通过”:变量”的格式来制定参数,同时在方法的参数前面使用@Param将方法参数与JP QL中的命名参数对应。
        java
        public interface UserDao extends Repository<AccountInfo, Long> {
        @Query("from AccountInfo a where a where a.ccountId = :id")
        public AccountInfo findByAccountId(@Param("id")Long accountId);
        @Query("from AccountInfo a where a.banlace > :balance")
        public Page<AccountInfo> findByBalanceGreaterThan(@Param("balance")Integer balance,Pageable pageable);
        }
      • 此外可以使用@Query来执行一个更新操作,为此我们需要在使用@Query的同时,用@Modifying来将该操作标识符标注就好了
        java
        @Modifying
        @Query("updata AccountInfo a set a.salary = ?1 where a.salary < ?2")
        public int increaseSalary(int after,int before);
      • 通过JPA命名查询语句创建查询
        • 命名查询是JPA提供的一种将查询语句从方法体重独立出来,以供多个方法共用的功能
      • JpaRepository 接口特色
        • 将一些查询方法返回类型由Iterable转换成了List
        • 新增了保存或更细的方法
          java
          List<T> findAll();
          List<T> findAll(Sort sort);
          List<T> findAll(Iterable<ID> ids);
          <S extends T> List<S> save(Iterable<S> entities);
          void flush();
          <S extends T> S saveAndFlush(S entity);
          void deleteInBatch(Iterable<T> entities);
          void deleteAllInBatch();
          T getOne(ID id);
      • JpaSpecificationExecutor特色:实现了带条件的查询,类似于Hiberante的cretira
        java
        T findOne(Specification<T>);
        List<T> findAll(Specification<T>);
        List<T> findAll(Specification<T>, Sort);
        List<T> findAll(Specification<T>, Pageable);
        long count(Specification<T>);
  • spring Data Jpa对实务的支持

    • 默认情况下,Spring Data JPA实现的方法都是使用事务的。针对查询类型的方法,其等价于@Transactional(readOnly=true);增删该查类型的方法,等价于@Transactional.除了查询的方法设为只读事务外,其他食物属性均采用默认值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值