(转)JPA & Restful

参考博客:

JPA:

https://www.jianshu.com/p/b6932740f3c0

https://shimo.im/docs/zOer2qMVEggbF33d/

Restful:

https://blog.csdn.net/chenxiaochan/article/details/73716617

https://www.cnblogs.com/wang-yaz/p/9237981.html

(二期)10、spring data jpa综合运用

【课程十预习】spri...使用.xmind0.3MB

【课程十】基本...接口.xmind0.1MB

【课程十】jpa_hibe..._jpa.xmind0.2MB

【课程十】jpa实体...注解.xmind85.8KB

【课程十】spring_d...介绍.xmind0.1MB

什么是jpa?

全称Java Persistence API,通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

JPA的出现有两个原因:

 其一,简化现有Java EE和Java SE应用的对象持久化的开发工作;

 其二,Sun希望整合对ORM技术,实现持久化领域的统一。

JPA提供的技术:

 1)ORM映射元数据:JPA支持XML和JDK 5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中;

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

 3)查询语言:通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。

JPA的规范提现在哪?

元数据映射、CRUD操作、JPQL查询语言

javax.persistence.Entity
javax.persistence.EntityManager
javax.transaction.Transactional
javax.persistence.criteria.CriteriaQuery
#JPQL
select o from Order o 或  select o from Order as o

JPQL查询能力

JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是HibernateHQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。

高级特性

JPA中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。

什么是spring data jpa?

Spring提供的一个用于简化JPA开发的框架。可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。

  • SpringData JPA只是SpringData中的一个子模块
  • JPA是一套标准接口,而Hibernate是JPA的实现
  • SpringData JPA 底层默认实现是使用Hibernate
  • SpringDataJPA 的首个接口就是Repository,它是一个标记接口。只要我们的接口实现这个接口,那么我们就相当于在使用SpringDataJPA了。
  • 就是说,只要我们实现了这个接口,我们就可以使用"按照方法命名规则"来进行查询。
SpringDataJPA的核心接口
  • Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别。
  • CrudRepository :是Repository的子接口,提供CRUD的功能
  • PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能
  • JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,比如:批量操作等。
  • JpaSpecificationExecutor:用来做负责查询的接口,类似条件(QBC)查询
  • Specification:是Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可
CrudRepository 查询方法与命名规则

SpringDataJPA默认情况下, 提供了查询的相关的方法, 基本上能满足我们80%左右的需要. 但是还有一些是没有满足的,我们可以遵循它的命名规范来定义方法名. 如果没有满足命名规范的, 可以在方法上加@Query注解来写语句。

 

 JPA命令规范

 

 

jpa、hibernate与spring data jpa三者关系

JPA作为一种规范——也就说JPA规范中提供的只是一些接口,显然接口不能直接拿来使用。虽然应用程序可以面向接口编程,但JPA底层一定需要某种JPA实现,否则JPA依然无法使用。

Sun之所以提出JPA规范,其目的是以官方的身份来统一各种ORM框架的规范,包括著名的Hibernate、TopLink等。不过JPA规范给开发者带来了福音:开发者面向JPA规范的接口,但底层的JPA实现可以任意切换:觉得Hibernate好的,可以选择Hibernate JPA实现;觉得TopLink好的,可以选择TopLink JPA实现……这样开发者可以避免为使用Hibernate学习一套ORM框架,为使用TopLink又要再学习一套ORM框架。

 

JPA是一套ORM规范,hibernate实现了JPA规范
hibernate中有自己的独立ORM操作数据库方式,也有JPA规范实现的操作数据库方式。
在数据库增删改查操作中,我们hibernate和JPA的操作都要会。

 

虽然ORM框架都实现了JPA规范,但是在不同ORM框架之间切换是需要编写的代码有一些差异,而通过使用Spring Data Jpa能够方便大家在不同的ORM框架中间进行切换而不要更改代码。并且Spring Data Jpa对Repository层封装的很好,可以省去不少的麻烦。

 

Spring Data JPA 可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现。

 

 

jpa实体相关注解
 @Entity

标注位置:实体类声明语句之前; 

主要作用:标注该Java类为实体类,且将其映射到指定的数据库表。

@Table

标注位置:实体类声明语句之前,与@Entity注释并列使用; 

主要作用:标注当前实体类映射到数据库中的数据表名,当实体类与数据表名不同时使用。

@Id

标注位置:实体类的属性声明语句之前,或属性的getter()方法之前; 

主要作用:指定该实体类的当前属性映射到数据表的主键列。

 @GeneratedValue

标注位置:与@Id注释配合使用; 

主要作用:通过其strategy属性指定数据表主键的生成策略。默认情况下,JPA自动选择最适合底层数据库的主键生成策略,即SqlServer对应identity,而MySQL对应auto increment。

@Column

标注位置:实体类的属性声明语句之前,或属性的getter()方法之前; 

主要作用:标注实体类的当前属性映射到数据库表的字段名,当属性名与数据表字段名不一致时使用。

@Transient

标注位置:实体类的属性声明语句之前,或属性的getter()方法之前; 

主要作用:标注实体类的当前属性不进行数据表字段的映射,ORM框架将忽略此映射,如实体类的getInfo()方法通常不需要映射到数据表的字段上。

@Temporal

标注位置:实体类的属性声明语句之前,或属性的getter()方法之前; 

主要作用:标注实体类中Date类型(Java核心API中未定义Date类型的精度)的属性映射到数据表字段的具体精度(数据库中Date类型的数据有DATE、TIME和TIMESTAMP三种精度)。

基本CRUD操作
Repository接口

仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别。

CRUDRespository接口

继承Repository,实现了一组CRUD相关的方法。

 

 

命名参数,索引参数
  • 索引参数如下所示,索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致 
@Modifying 
@Query("update User u set u.firstname = ?1 where u.lastname = ?2") 
int setFixedFirstnameFor(String firstname, String lastname); 
  • 命名参数(推荐使用这种方式) 
    可以定义好参数名,赋值时采用@Param("参数名"),而不用管顺序。如下所示:
public interface UserRepository extends JpaRepository<User, Long> { 

  @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname") 
  User findByLastnameOrFirstname(@Param("lastname") String lastname, 
                                 @Param("firstname") String firstname); 
} 
PagingAndSortRespository接口查询

该接口继承了CrudRepository接口,提供了两个方法,实现了分页和排序的功能了

 

JpaRepository接口

该接口继承了PagingAndSortingRepository接口。同时也继承QueryByExampleExecutor接口,这是个用“实例”进行查询的接口

 

事务在spring data jpa中的使用,@Modifying @Transactional的综合使用

@Modifying表示是更新执行

@Transactional带事务

JpaSpecificationExecutor接口

该接口提供了对JPA Criteria查询(动态查询)的支持。

 

spring data jpa 通过创建方法名来做查询,只能做简单的查询,那如果我们要做复杂一些的查询呢,多条件分页怎么办,这里,spring data jpa为我们提供了JpaSpecificationExecutor接口,只要简单实现toPredicate方法就可以实现复杂的查询

 

1.首先让我们的接口继承于JpaSpecificationExecutor

2.JpaSpecificationExecutor提供了以下接口

其中Specification就是需要我们传进去的参数,它是一个接口

 

spring集成spring data jpa

第一步:pom导入坐标

<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>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

 

第二步:配置文件中配置spring jpa和数据库相关信息

spring:
    http:
        encoding:
          charset: UTF-8
          force: true
          enabled: true
    datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost/jpademo?useSSL=false&characterEncoding=utf8
        username: root
        password: admin

    jpa:
        database: mysql
        show-sql: true
        hibernate:
            ddl-auto: update

第三步:根据需求,dao层文件继承Repository接口或者子集

 

JPA原理
aop - 动态代理 - jpa

以下观点来自文章:https://www.jb51.net/article/104608.htm

Java动态代理

要想了解Java动态代理,首先要了解什么叫做代理,熟悉设计模式的朋友一定知道在Gof总结的23种设计模式中,有一种叫做代理(Proxy)的对象结构型模式,动态代理中的代理,指的就是这种设计模式。

 

在我看来所谓的代理模式,和23种设计模式中的“装饰模式”是一个东西。23种设计模式中将它们作为两种模式,网上也有些文章讲这两种模式的异同,从细节来看,确实可以人为地区分这两种模式,但是抽象到一定高度后,我认为这两种模式是完全一样的。因此学会了代理模式,也就同时掌握了装饰模式。

代理模式

代理模式简单来说,就是对一个对象进行包装,包装后生成的对象具有和原对象一样的方法列表,但是每个方法都可以是被包装过的。

静态代理

让我们先来看一段代码:

package common;

public class Test {
  static interface Subject{
    void sayHi();
    void sayHello();
  }
  
  static class SubjectImpl implements Subject{

    @Override
    public void sayHi() {
      System.out.println("hi");
    }

    @Override
    public void sayHello() {
      System.out.println("hello");
    }
  }
  
  static class SubjectImplProxy implements Subject{
    private Subject target;
    
    public SubjectImplProxy(Subject target) {
      this.target=target;
    }
    
    @Override
    public void sayHi() {
      System.out.print("say:");
      target.sayHi();
    }

    @Override
    public void sayHello() {
      System.out.print("say:");
      target.sayHello();
    }
  }
  
  public static void main(String[] args) {
    Subject subject=new SubjectImpl();
    Subject subjectProxy=new SubjectImplProxy(subject);
    subjectProxy.sayHi();
    subjectProxy.sayHello();
  }
}

这段代码中首先定义了一个Subject接口,接口中有两个方法。

然后定义了SubjectImpl类实现Subject接口并实现其中的两个方法,到这里肯定是没问题的。

现在再定义一个SubjuectImplProxy类,也实现Subject接口。这个SubjectImplProxy类的作用是包装SubjectImpl类的实例,它的内部定义一个变量target来保存一个SubjectImpl的实例。SubjectImplProxy也实现了接口规定的两个方法,并且在它的实现版本中,都调用了SubjectImpl的实现,但是又添加了自己的处理逻辑。

相信这段代码不难理解,它通过对SubjectImpl进行包装,达到了给输出内容添加前缀的功能。这种代理方式叫做静态代理。

动态代理

从上面的演示中我们不难看出静态代理的缺点:我们对SubjectImpl的两个方法,是进行的相同的包装,但是却要在SubjectImplProxy里把相同的包装逻辑写两次,而且以后如果Subject接口再添加新的方法,SubjectImplProxy也必须要添加新的实现,尽管SubjectImplProxy对所有方法的包装可能都是一样的。

 

下面我把上面例子的静态代理改成动态代理,我们来看一下区别:

package common;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
  static interface Subject{
    void sayHi();
    void sayHello();
  }
  
  static class SubjectImpl implements Subject{

    @Override
    public void sayHi() {
      System.out.println("hi");
    }

    @Override
    public void sayHello() {
      System.out.println("hello");
    }
  }
  
  static class ProxyInvocationHandler implements InvocationHandler{
    private Subject target;
    public ProxyInvocationHandler(Subject target) {
      this.target=target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.print("say:");
      return method.invoke(target, args);
    }
    
  }
  
  public static void main(String[] args) {
    Subject subject=new SubjectImpl();
    Subject subjectProxy=(Subject)             Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject));
    subjectProxy.sayHi();
    subjectProxy.sayHello();
    
  }
}

只看main方法的话,只有第二行和之前的静态代理不同,同样是生成一个subjectProxy代理对象,只是生成的代码不同了。静态代理是直接new 一个SubjectImplProxy的实例,而动态代理则调用了java.lang.reflect.Proxy.newProxyInstance()方法。

 

以上就是动态代理!

 


JPA原理解析

第一步、获取所有的Repository持久类。

  1. JpaRepositoriesAutoConfiguration
  1. JpaRepositoriesAutoConfigureRegistrar--》ImportBeanDefinitionRegistrar --》registerBeanDefinitions()方法是是Spring提供的策略,spring会在加载BeanDefinition的时候调用它的方法
  1. RepositoryConfigurationDelegate --》registerRepositoriesIn()方法注册Repositories持久类--》getRepositoryConfigurations()获取Repository的配置信息,(包含扫描包获取类和区分Repository类)--》getCandidates()方法扫描所有的class

到此,第一步完成,我们知道了Spring Data JPA如何通过包扫描获得哪些类需要被代理.

 

第二步:注册FactoryBean的过程,注册到spring容器中。就是JpaRepositoryFactoryBean。

 

第三步:Spring会根据我们注册的BeanDefinition实例化JpaRepositoryFactoryBean,并调用它的getObject方法获得实际的Repository代理。

JpaRepositoryFactoryBean --》afterPropertiesSet()--》getRepository()设置代理对象(打断点调试)--》QueryExecutorMethodInterceptor()方法用于根据规则自动生成代码

 

 

第四步,生成Repository的重任交到了JpaRepositoryFactoryBean手中.JpaRepositoryFactoryBean 调用下面这个方法生成Repository的代理

 

 

---------------------华丽丽的分割线-------------------

 

 

Repository是个标记接口。用于快速定位Repository接口。

 

 

ImportBeanDefinitionRegistrar

原来JpaRepository接口只是它的外壳,其真正实现类是simplejpaReository

 

JdkDynamicAopProxy

 

首先、扫描所有class文件。关键类:

AnnotationRepositoryConfigurationSource

 

 

然后再所有类中筛选出实现类Repository的类。关键类:

RepositoryConfigurationSourceSupport

 

这个刷选过程也设计到bean的初始化成BeanDefinition,所以关键类:

 

我们从@EnableJpaRepositories开始,可以看到在它上边有@Import(JpaRepositoriesRegistrar.class)注解,其中JpaRepositoriesRegistrar是ImportBeanDefinitionRegistrar的子类.

 

其中ImportBeanDefintionRegistrar是Spring提供的策略,spring会在加载BeanDefinition的时候调用它的方法:

 

public void registerBeanDefinitions(
      AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

 

public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
    //获得基于注解的配置信息,如注解信息,要扫描的包,要扫描的注解等
    AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(annotationMetadata, getAnnotation(), resourceLoader, environment);
    
    //这个类用来做一些准备工作,不是本文的重点
    RepositoryConfigurationExtension extension = getExtension();
    
    //实例化一个委托对象,用来完成整体的注册逻辑.
    RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, resourceLoader,environment);
    
    //重点,调用委托对象,完成实际的注册逻辑,也就是上边的第一步和第二步
    delegate.registerRepositoriesIn(registry, extension); 
}

 

接下来看RepositoryConfigurationDelegate的关键方法,delegate.registerRepositoriesIn(registry, extension)

 

//省略了一些代码
public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry,
            RepositoryConfigurationExtension extension) {

        //调用extension准备JPA的上下文
        extension.registerBeansForRoot(registry, configurationSource);

       //生成Repository的BeanDefinitionBuilder的工具类
        RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(registry, extension, resourceLoader,
                environment);

      //extension.getRepositoryConfigurations方法完成刚刚说的第一步,包扫描,读取配置.找出有哪些类需要Spring Data JPA生成代理
        for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
                .getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {

//接下来开始第二步,一个一个地生成RepositoryFactoryBean的Definition

            //由配置信息,主要是接口名称,生成基础的DefinitionBuilder
            BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

            //配置EntityManager
            extension.postProcess(definitionBuilder, configurationSource);

            //获得FactoryBean的Definition
            AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
            String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

            //注册FactoryBean的Definition
            registry.registerBeanDefinition(beanName, beanDefinition);

        }
            
    }
posted @ 2018-10-18 17:11  free_wings  阅读(981)  评论(0编辑  收藏  举报