易筋SpringBoot 2.1 | 第廿篇:SpringBoot的复杂JPA以及源码解析

写作时间:2019-09-02
Spring Boot: 2.1 ,JDK: 1.8, IDE: IntelliJ IDEA

说明

JPA 应用表的Entity继承于基类,Money处理应用第三方库。JPA重要源码说明。

工程建立

参照教程【SpringBoot 2.1 | 第一篇:构建第一个SpringBoot工程】新建一个Spring Boot项目,名字叫demodbjpastarbucks, 在目录src/main/java/resources 下找到配置文件application.properties,重命名为application.yml

在Dependency中选择
Developer Tools > Lombok
Web > Spring Web Starter
SQL > H2 DataBase / Spring Data JPA
Ops > Spring Boot Actuator。

在这里插入图片描述

设置配置

src > main > resources > application.yml

spring:
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        show_sql: true
        format_sql: true

注释: ddl-auto: create-drop 表示每次启动都都删除表以及数据(如果存在),并自动创建表以及数据。

创建表

表的基类 com.zgpeace.demodbjpastarbucks.model.BaseEntity

package com.zgpeace.demodbjpastarbucks.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.Date;

@MappedSuperclass
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BaseEntity implements Serializable {
  @Id
  @GeneratedValue
  private Long id;
  @Column(updatable = false)
  @CreationTimestamp
  private Date createTime;
  @UpdateTimestamp
  private Date updateTime;
}

com.zgpeace.demodbjpastarbucks.model.Coffee

package com.zgpeace.demodbjpastarbucks.model;

import lombok.*;
import org.hibernate.annotations.Type;
import org.joda.money.Money;

import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;

@Entity
@Table(name = "T_MENU")
@Builder
@Data
@ToString(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class Coffee extends BaseEntity implements Serializable {
  private String name;
  @Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyAmount"
      , parameters = {@org.hibernate.annotations.Parameter(name = "currencyCode", value = "CNY")})
  private Money price;
}

注释: @ToString(callSuper = true) 需要设置属性,才会把父类的属性也打印出来

枚举 com.zgpeace.demodbjpastarbucks.model.OrderState

package com.zgpeace.demodbjpastarbucks.model;

public enum OrderState {
  INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
}

com.zgpeace.demodbjpastarbucks.model.CoffeeOrder

package com.zgpeace.demodbjpastarbucks.model;

import lombok.*;

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

@Entity
@Table(name = "T_ORDER")
@Data
@ToString(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CoffeeOrder extends BaseEntity implements Serializable {
  private String customer;
  @ManyToMany
  @JoinTable(name = "T_ORDER_COFFEE")
  @OrderBy("id")
  private List<Coffee> items;
  @Enumerated
  @Column(nullable = false)
  private OrderState state;

}

数据操作Repository

基类 com.zgpeace.demodbjpastarbucks.repository.BaseRepository

package com.zgpeace.demodbjpastarbucks.repository;

import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;

import java.util.List;

@NoRepositoryBean
public interface BaseRepository<T, Long> extends PagingAndSortingRepository<T, Long> {
  List<T> findTop3ByOrderByUpdateTimeDescIdAsc();
}

com.zgpeace.demodbjpastarbucks.repository.CoffeeRepository

package com.zgpeace.demodbjpastarbucks.repository;

import com.zgpeace.demodbjpastarbucks.model.Coffee;

public interface CoffeeRepository extends BaseRepository<Coffee, Long> {
}

com.zgpeace.demodbjpastarbucks.repository.CoffeeOrderRepository

package com.zgpeace.demodbjpastarbucks.repository;

import com.zgpeace.demodbjpastarbucks.model.CoffeeOrder;

import java.util.List;

public interface CoffeeOrderRepository extends BaseRepository<CoffeeOrder, Long> {
  List<CoffeeOrder> findByCustomerOrderById(String customer);

  List<CoffeeOrder> findByItems_Name(String name);
}

Controller 插入数据,查询数据

com.zgpeace.demodbjpastarbucks.DemodbjpastarbucksApplication

package com.zgpeace.demodbjpastarbucks;

import com.zgpeace.demodbjpastarbucks.model.Coffee;
import com.zgpeace.demodbjpastarbucks.model.CoffeeOrder;
import com.zgpeace.demodbjpastarbucks.model.OrderState;
import com.zgpeace.demodbjpastarbucks.repository.CoffeeOrderRepository;
import com.zgpeace.demodbjpastarbucks.repository.CoffeeRepository;
import lombok.extern.slf4j.Slf4j;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@SpringBootApplication
@EnableJpaRepositories
@EnableTransactionManagement
@Slf4j
public class DemodbjpastarbucksApplication implements ApplicationRunner {
  @Autowired
  private CoffeeRepository coffeeRepository;
  @Autowired
  private CoffeeOrderRepository orderRepository;

  public static void main(String[] args) {
    SpringApplication.run(DemodbjpastarbucksApplication.class, args);
  }

  @Override
  @Transactional
  public void run(ApplicationArguments args) throws Exception {
    initOrder();
    findOrders();
  }

  private void initOrder() {
    Coffee latte = Coffee.builder().name("latte")
        .price(Money.of(CurrencyUnit.of("CNY"), 30.0))
        .build();
    coffeeRepository.save(latte);
    log.info("Coffee: {}", latte);

    Coffee espresso = Coffee.builder().name("espresso")
        .price(Money.of(CurrencyUnit.of("CNY"), 20.0))
        .build();
    coffeeRepository.save(espresso);
    log.info("Coffee: {}", espresso);

    CoffeeOrder order = CoffeeOrder.builder()
        .customer("Li Lei")
        .items(Collections.singletonList(espresso))
        .state(OrderState.INIT)
        .build();
    orderRepository.save(order);
    log.info("Order: {}", order);

    order = CoffeeOrder.builder()
        .customer("Li Lei")
        .items(Arrays.asList(espresso, latte))
        .state(OrderState.INIT)
        .build();
    orderRepository.save(order);
    log.info("Order: {}", order);
  }

  private void findOrders() {
    coffeeRepository.findAll(Sort.by(Sort.Direction.DESC, "id"))
        .forEach(c -> log.info("Loading {}", c));
    List<CoffeeOrder> list = orderRepository.findTop3ByOrderByUpdateTimeDescIdAsc();
    log.info("findTop3ByOrderByUpdateTimeDescIdAsc: {}", getJoinedOrderId(list));

    list = orderRepository.findByCustomerOrderById("Li Lei");
    log.info("findByCustomerOrOrderById: {}", getJoinedOrderId(list));

    // 不开启事务会因为没Session而报LazyInitializationException
    list.forEach(o -> {
      log.info("Order {}", o.getId());
      o.getItems().forEach(i -> log.info(" Item {}" , i));
    });

    list = orderRepository.findByItems_Name("latte");
    log.info("findByItems_Name: {}", getJoinedOrderId(list));
  }

  private String getJoinedOrderId(List<CoffeeOrder> list) {
    return list.stream().map(o -> o.getId().toString())
        .collect(Collectors.joining(","));
  }

}

启动成功,查看setRollbackOnly的日志

应用启动后:

  1. 删除表、递增长序列如果存在的情况
  2. 创建表主键的递增序列
  3. 创建表主键、字段。
  4. 创建外键
    drop table t_menu if exists
    drop table t_order if exists    
    drop table t_order_coffee if exists    
    drop sequence if exists hibernate_sequence
    create sequence hibernate_sequence start with 1 increment by 1
    
    create table t_menu (
       id bigint not null,
        create_time timestamp,
        update_time timestamp,
        name varchar(255),
        price decimal(19,2),
        primary key (id)
    )
    
    create table t_order (
       id bigint not null,
        create_time timestamp,
        update_time timestamp,
        customer varchar(255),
        state integer not null,
        primary key (id)
    )
    
    create table t_order_coffee (
       coffee_order_id bigint not null,
        items_id bigint not null
    )
    
    alter table t_order_coffee 
       add constraint FKj2swxd3y69u2tfvalju7sr07q 
       foreign key (items_id) 
       references t_menu
    
    alter table t_order_coffee 
       add constraint FK33ucji9dx64fyog6g17blpx9v 
       foreign key (coffee_order_id) 
       references t_order

插入表数据SQL

	insert 
    into
        t_menu
        (create_time, update_time, name, price, id) 
    values
        (?, ?, ?, ?, ?)
    
    
    insert 
    into
        t_order
        (create_time, update_time, customer, state, id) 
    values
        (?, ?, ?, ?, ?)


    insert 
    into
        t_order_coffee
        (coffee_order_id, items_id) 
    values
        (?, ?)

查询表数据

Coffee: Coffee(super=BaseEntity(id=1, createTime=null, updateTime=null), name=latte, price=CNY 30.00)
    call next value for hibernate_sequence
Coffee: Coffee(super=BaseEntity(id=2, createTime=null, updateTime=null), name=espresso, price=CNY 20.00)
    call next value for hibernate_sequence
Order: CoffeeOrder(super=BaseEntity(id=3, createTime=null, updateTime=null), customer=Li Lei, 
	items=[Coffee(super=BaseEntity(id=2, createTime=null, updateTime=null), name=espresso, price=CNY 20.00)], state=INIT)
    call next value for hibernate_sequence
Order: CoffeeOrder(super=BaseEntity(id=4, createTime=null, updateTime=null), customer=Li Lei, items=[
	Coffee(super=BaseEntity(id=2, createTime=null, updateTime=null), name=espresso, price=CNY 20.00), 
	Coffee(super=BaseEntity(id=1, createTime=null, updateTime=null), name=latte, price=CNY 30.00)], state=INIT)

关联查询以及结果数据

select
        coffeeorde0_.id as id1_1_,
        coffeeorde0_.create_time as create_t2_1_,
        coffeeorde0_.update_time as update_t3_1_,
        coffeeorde0_.customer as customer4_1_,
        coffeeorde0_.state as state5_1_ 
    from
        t_order coffeeorde0_ 
    left outer join
        t_order_coffee items1_ 
            on coffeeorde0_.id=items1_.coffee_order_id 
    left outer join
        t_menu coffee2_ 
            on items1_.items_id=coffee2_.id 
    where
        coffee2_.name=?

Item Coffee(super=BaseEntity(id=2, createTime=Sat Aug 31 08:44:24 CST 2019, updateTime=Sat Aug 31 08:44:24 CST 2019), name=espresso, price=CNY 20.00)
Item Coffee(super=BaseEntity(id=1, createTime=Sat Aug 31 08:44:24 CST 2019, updateTime=Sat Aug 31 08:44:24 CST 2019), name=latte, price=CNY 30.00)
        

-------------原码解析--------------

Repository Bean 是如何创建的

JpaRepositoriesRegistrar

  • 激活了 @EnableJpaRepositories
  • 返回了 JpaRepositoryConfigExtension
    在这里插入图片描述

RepositoryBeanDefinitionRegistrarSupport.registerBeanDefinitions

  • 注册Repository Bean (类型是 JpaRepositoryFactoryBean)

在这里插入图片描述

RepositoryConfigurationExtensionSupport.getRepositoryConfigurations

  • 取得 Repository 配置

RepositoryBeanDefinitionRestistrarSupport.registerBeanDefinitions()

在这里插入图片描述

RepositoryConfigurationDelegate.registerRepositoriesIn()

在这里插入图片描述

RepositoryBeanDefinitionBuilder.build()

在这里插入图片描述

JpaRepositoryFactory.getTargetRepository

  • 创建了 Repository

在这里插入图片描述

在这里插入图片描述

SimpleJpaRepository
JPA默认方法的实现 CRUD
在这里插入图片描述

接口中的方法是如何被解释的

RepositoryFactorySupport.getRepository 添加了Advice

  • DefaultMethodInvokingMethodInterceptor
  • QueryExecutorMethodInterceptor

在这里插入图片描述
AbstractJpaQuery.execute 执行具体的查询

在这里插入图片描述

语法解析在Part 中

PartTree
在这里插入图片描述
Part
在这里插入图片描述
OrderBySource
在这里插入图片描述

总结

恭喜你,学会了JPA 基于基类创建Repository,知道Repository Bean 是通过JpaRepositoryFactory创建的,知道接口中的方法是通过语法分析被解释的
代码下载:

https://github.com/zgpeace/Spring-Boot2.1/tree/master/db/demodbjpastarbucks

参考

https://github.com/geektime-geekbang/geektime-spring-family/tree/master/Chapter%203/jpa-complex-demo

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值