写作时间: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的日志
应用启动后:
- 删除表、递增长序列如果存在的情况
- 创建表主键的递增序列
- 创建表主键、字段。
- 创建外键
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