Spring Data JPA
对象与关系的范式不匹配
Hibernate
- ⼀款开源的对象关系映射(Object / Relational Mapping)框架
- 将开发者从 95% 的常 ⻅数据持久化 ⼯作中解放出来
- 屏蔽了底层数据库的各种细节
- 2006 年,Hibernate 3.2 成为 JPA 实现
Reference
Java Persistence API
JPA1.0 是作为 JSR 220 的一部分正式发布。
- 简化数据持久化代码的开发工作
- 为 Java 社区屏蔽不同持久化 API 的差异(屏蔽了 Hibernate、JDO、EJB 之间的差异)
- 相当于在 O/R Mapping 框架之前做了一层抽象。
Spring Data
在保留底层存储特性的同时,提供相对 ⼀致的、基于 Spring 的编程模型。Spring Family List :
- Spring Data Commons
- Spring Data JDBC
- Spring Data JPA
- Spring Data Redis
- ……
如何引入 Spring Data JPA
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
常用 JPA 注解
实体
- @Entity:声明此类为一个实体。
- @MappedSuperclass:在多个实体类的父类上面标注。
- @Table(name):实体与表关联。如果没有写@Table 注解,会自动将该类的名字作为表名。
主键
@Id
- @GeneratedValue(strategy, generator):用于指定主键的生成策略、和对应的生成器。
- @SequenceGenerator(name, sequenceName):指定序列。
映射
- @Column(name, nullable, length, insertable, updatable)
- @JoinTable(name):关联查询时,表与表是多对多的关系时,指定多对多关联表中间表的参数。
- @JoinColumn(name):关联查询时,表与表是一对一、一对多、多对一以及多对多的关系时,声明表关联的外键字段作为连接表的条件。必须配合关联表的注解一起使用
关系
- @OneToOne:关联表注解,表示对应的实体和本类是一对一的关系
- @OneToMany:关联表注解,表示对应的实体和本类是一对多的关系
- @ManyToOne:关联表注解,表示对应的实体和本类是多对一的关系
- @ManyToMany:关联表注解,表示对应的实体和本类是多对多的关系
- @OrderBy:
BucksProject
项目目标
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>geektime.spring.springbucks</groupId>
<artifactId>jpa-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jpa-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.jadira.usertype</groupId>
<artifactId>usertype.core</artifactId>
<version>6.0.1.GA</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
# 定义DDL 每次运行都会创建表结构、每次结束都会删除
spring.jpa.hibernate.ddl-auto=create-drop
# 打印sql
spring.jpa.properties.hibernate.show_sql=true
# 对sql进行格式化
spring.jpa.properties.hibernate.format_sql=true
实体定义
BaseEntity
package geektime.spring.springbucks.jpademo.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;
}
Coffee
package geektime.spring.springbucks.jpademo.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
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;
}
CoffeeOrder
package geektime.spring.springbucks.jpademo.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
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;
}
OrderState 枚举类(描述订单的状态)
package geektime.spring.springbucks.jpademo.model;
public enum OrderState {
INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
}
控制台输出
# 如果有,删除
Hibernate:
drop table t_menu if exists
Hibernate:
drop table t_order if exists
Hibernate:
drop table t_order_coffee if exists
Hibernate:
drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
# 创建表结构
Hibernate:
create table t_menu (
id bigint not null,
create_time timestamp,
name varchar(255),
price decimal(19,2),
update_time timestamp,
primary key (id)
)
Hibernate:
create table t_order (
id bigint not null,
create_time timestamp,
customer varchar(255),
state integer not null,
update_time timestamp,
primary key (id)
)
Hibernate:
create table t_order_coffee (
coffee_order_id bigint not null,
items_id bigint not null
)
Hibernate:
alter table t_order_coffee
add constraint FKj2swxd3y69u2tfvalju7sr07q
foreign key (items_id)
references t_menu
Hibernate:
alter table t_order_coffee
add constraint FK33ucji9dx64fyog6g17blpx9v
foreign key (coffee_order_id)
references t_order
# 删除
Hibernate:
drop table t_menu if exists
Hibernate:
drop table t_order if exists
Hibernate:
drop table t_order_coffee if exists
Hibernate:
drop sequence if exists hibernate_sequence
通过 Spring Data JPA 操作数据库
启动类添加 @EnableJpaRepositories
Repository<T, ID> 接口
- CrudRepository<T, ID>
- PagingAndSortingRepository<T, ID>
- JpaRepository<T, ID>
定义查询
根据方法名定义查询
- find…By… / read…By… / query…By… / get…By…
- count…By…
- …OrderBy…[Asc / Desc]
- And / Or / IgnoreCase
- Top / First / Distinct
分页查询
- PagingAndSortingRepository<T, ID>
- Pageable / Sort
- Slice
/ Page
定义 Repository
BaseRepository
package geektime.spring.springbucks.jpademo.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> {
/**
* 对更新时间进行升序排序,并且查询出前三名
* @return
*/
List<T> findTop3ByOrderByUpdateTimeDescIdAsc();
}
CoffeeRepository
package geektime.spring.springbucks.jpademo.repository;
import geektime.spring.springbucks.jpademo.model.Coffee;
public interface CoffeeRepository extends BaseRepository<Coffee, Long> {
}
CoffeeOrderRepository
package geektime.spring.springbucks.jpademo.repository;
import geektime.spring.springbucks.jpademo.model.CoffeeOrder;
import java.util.List;
public interface CoffeeOrderRepository extends BaseRepository<CoffeeOrder, Long> {
List<CoffeeOrder> findByCustomerOrderById(String customer);
List<CoffeeOrder> findByItems_Name(String name);
}
启动类
package geektime.spring.springbucks.jpademo;
import geektime.spring.springbucks.jpademo.model.Coffee;
import geektime.spring.springbucks.jpademo.model.CoffeeOrder;
import geektime.spring.springbucks.jpademo.model.OrderState;
import geektime.spring.springbucks.jpademo.repository.CoffeeOrderRepository;
import geektime.spring.springbucks.jpademo.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 javax.transaction.Transactional;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@SpringBootApplication
@EnableJpaRepositories
@EnableTransactionManagement
@Slf4j
public class JpaDemoApplication implements ApplicationRunner {
@Autowired
private CoffeeRepository coffeeRepository;
@Autowired
private CoffeeOrderRepository orderRepository;
public static void main(String[] args) {
SpringApplication.run(JpaDemoApplication.class, args);
}
@Override
@Transactional
public void run(ApplicationArguments args) throws Exception {
initOrders();
findOrders();
}
/**
* 增
*/
private void initOrders() {
//保存咖啡:Coffee表添加拿铁
Coffee latte = Coffee.builder().name("latte")
.price(Money.of(CurrencyUnit.of("CNY"), 30.0))
.build();
coffeeRepository.save(latte);
log.info("Coffee: {}", latte);
//保存咖啡:Coffee表添加特浓
Coffee espresso = Coffee.builder().name("espresso")
.price(Money.of(CurrencyUnit.of("CNY"), 20.0))
.build();
coffeeRepository.save(espresso);
log.info("Coffee: {}", espresso);
//Li Lei 第一个订单(特浓)
CoffeeOrder order = CoffeeOrder.builder()
.customer("Li Lei")
.items(Collections.singletonList(espresso))//不可变List
.state(OrderState.INIT) //状态为INIT
.build();
orderRepository.save(order);
log.info("Order: {}", order);
//Li Lei 第二个订单(特浓、拿铁)
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() {
//查询咖啡:根据咖啡的ID降序
coffeeRepository
.findAll(Sort.by(Sort.Direction.DESC, "id"))
.forEach(c -> log.info("Loading {}", c));
//查询订单:对订单的更新时间进行升序排序,并且查询出前三名
List<CoffeeOrder> list = orderRepository.findTop3ByOrderByUpdateTimeDescIdAsc();
log.info("findTop3ByOrderByUpdateTimeDescIdAsc: {}", getJoinedOrderId(list));
//查询订单: 查询顾客为Li Lei的所有订单:(输出其订单号每个订单的ID,及其包含的咖啡)
list = orderRepository.findByCustomerOrderById("Li Lei");
log.info("findByCustomerOrderById: {}", getJoinedOrderId(list));
// 不开启事务会因为没Session而报LazyInitializationException
list.forEach(o -> {
log.info("Order {}", o.getId());
o.getItems().forEach(i -> log.info(" Item {}", i));
});
//查询订单:查询订单中包含 拿铁的 所有订单(Items_Name 相当于查询items属性所包含集合中每个元素的name属性)
list = orderRepository.findByItems_Name("latte");
log.info("findByItems_Name: {}", getJoinedOrderId(list));
}
//过滤器list中的id,并且添加分隔符循环遍历
private String getJoinedOrderId(List<CoffeeOrder> list) {
return list.stream().map(o -> o.getId().toString())
.collect(Collectors.joining(","));
}
}
控制台输出
//第一订单:orderRepository.save(order);
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)
//第一订单:orderRepository.save(order);
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)
//查询咖啡:根据咖啡的ID降序
Loading Coffee(super=BaseEntity(id=2, createTime=Thu Jan 09 17:38:01 CST 2020, updateTime=Thu Jan 09 17:38:01 CST 2020), name=espresso, price=CNY 20.00)
Loading Coffee(super=BaseEntity(id=1, createTime=Thu Jan 09 17:38:01 CST 2020, updateTime=Thu Jan 09 17:38:01 CST 2020), name=latte, price=CNY 30.00)
//查询订单:对订单的更新时间进行升序排序,并且查询出前三名
findTop3ByOrderByUpdateTimeDescIdAsc: 3,4
//查询订单: 查询顾客为Li Lei的所有订单:(输出其订单号每个订单的ID,及其包含的咖啡)
findByCustomerOrderById: 3,4
Order 3
Item Coffee(super=BaseEntity(id=2, createTime=Thu Jan 09 17:38:01 CST 2020, updateTime=Thu Jan 09 17:38:01 CST 2020), name=espresso, price=CNY 20.00)
Order 4
Item Coffee(super=BaseEntity(id=2, createTime=Thu Jan 09 17:38:01 CST 2020, updateTime=Thu Jan 09 17:38:01 CST 2020), name=espresso, price=CNY 20.00)
Item Coffee(super=BaseEntity(id=1, createTime=Thu Jan 09 17:38:01 CST 2020, updateTime=Thu Jan 09 17:38:01 CST 2020), name=latte, price=CNY 30.00)
//查询订单:查询订单中包含 拿铁的 所有订单(Items_Name 相当于查询items属性所包含集合中每个元素的name属性)
findByItems_Name: 4
JpaRepository 接口的方法
@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);
void deleteInBatch(Iterable<T> entities);
void deleteAllInBatch();
T getOne(ID id);
@Override
<S extends T> List<S> findAll(Example<S> example);
@Override
<S extends T> List<S> findAll(Example<S> example, Sort sort);
}
QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {
<S extends T> Optional<S> findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example, Sort sort);
<S extends T> Page<S> findAll(Example<S> example, Pageable pageable);
<S extends T> long count(Example<S> example);
<S extends T> boolean exists(Example<S> example);
}
CoffeeRepository
package geektime.spring.springbucks.repository;
import geektime.spring.springbucks.model.Coffee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CoffeeRepository extends JpaRepository<Coffee, Long> {
}
CoffeeOrderRepository
package geektime.spring.springbucks.repository;
import geektime.spring.springbucks.model.CoffeeOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CoffeeOrderRepository extends JpaRepository<CoffeeOrder, Long> {
/**
* 查询指定用户名的所有订单并根据 id 排序
* @param customer
* @return
*/
List<CoffeeOrder> findByCustomerOrderById(String customer);
}
CoffeeService
package geektime.spring.springbucks.service;
import geektime.spring.springbucks.model.Coffee;
import geektime.spring.springbucks.model.CoffeeOrder;
import geektime.spring.springbucks.model.OrderState;
import geektime.spring.springbucks.repository.CoffeeOrderRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
@Slf4j
@Service
@Transactional
public class CoffeeOrderService {
@Autowired
private CoffeeOrderRepository orderRepository;
public CoffeeOrder createOrder(String customer, Coffee...coffee) {
CoffeeOrder order = CoffeeOrder.builder()
.customer(customer)
.items(new ArrayList<>(Arrays.asList(coffee)))
.state(OrderState.INIT)
.build();
CoffeeOrder saved = orderRepository.save(order);
log.info("New Order: {}", saved);
return saved;
}
public boolean updateState(CoffeeOrder order, OrderState state) {
if (state.compareTo(order.getState()) <= 0) { //检测状态,传入的状态不能比现有订单状态码小
log.warn("Wrong State order: {}, {}", state, order.getState());
return false;
}
order.setState(state);
orderRepository.save(order);
log.info("Updated Order: {}", order);
return true;
}
}
CoffeeOrderService
package geektime.spring.springbucks.service;
import geektime.spring.springbucks.model.Coffee;
import geektime.spring.springbucks.model.CoffeeOrder;
import geektime.spring.springbucks.model.OrderState;
import geektime.spring.springbucks.repository.CoffeeOrderRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Service
@Transactional
public class CoffeeOrderService {
@Autowired
private CoffeeOrderRepository orderRepository;
public CoffeeOrder createOrder(String customer, Coffee...coffee) {
CoffeeOrder order = CoffeeOrder.builder()
.customer(customer)
.items(new ArrayList<>(Arrays.asList(coffee)))
.state(OrderState.INIT)
.build();
CoffeeOrder saved = orderRepository.save(order);
log.info("New Order: {}", saved);
return saved;
}
public boolean updateState(CoffeeOrder order, OrderState state) {
if (state.compareTo(order.getState()) <= 0) { //检测状态,传入的状态不能比现有订单状态码小
log.warn("Wrong State order: {}, {}", state, order.getState());
return false;
}
order.setState(state);
orderRepository.save(order);
log.info("Updated Order: {}", order);
return true;
}
public List<CoffeeOrder> findByCustomerOrderById(String customer){
return orderRepository.findByCustomerOrderById(customer);
}
}
启动类
package geektime.spring.springbucks;
import geektime.spring.springbucks.model.Coffee;
import geektime.spring.springbucks.model.CoffeeOrder;
import geektime.spring.springbucks.model.OrderState;
import geektime.spring.springbucks.repository.CoffeeRepository;
import geektime.spring.springbucks.service.CoffeeOrderService;
import geektime.spring.springbucks.service.CoffeeService;
import lombok.extern.slf4j.Slf4j;
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.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.swing.text.html.Option;
import java.security.spec.ECParameterSpec;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@EnableTransactionManagement
@SpringBootApplication
@EnableJpaRepositories
public class SpringBucksApplication implements ApplicationRunner {
@Autowired
private CoffeeRepository coffeeRepository;
@Autowired
private CoffeeService coffeeService;
@Autowired
private CoffeeOrderService orderService;
public static void main(String[] args) {
SpringApplication.run(SpringBucksApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
//直接使用coffeeRepository
log.info("All Coffee: {}", coffeeRepository.findAll());
//All Coffee: [Coffee(super=BaseEntity(id=1, createTime=2020-01-12 16:03:36.35, updateTime=2020-01-12 16:03:36.35), name=espresso, price=CNY 20.00), Coffee(super=BaseEntity(id=2, createTime=2020-01-12 16:03:36.352, updateTime=2020-01-12 16:03:36.352), name=latte, price=CNY 25.00), Coffee(super=BaseEntity(id=3, createTime=2020-01-12 16:03:36.352, updateTime=2020-01-12 16:03:36.352), name=capuccino, price=CNY 25.00), Coffee(super=BaseEntity(id=4, createTime=2020-01-12 16:03:36.352, updateTime=2020-01-12 16:03:36.352), name=mocha, price=CNY 30.00), Coffee(super=BaseEntity(id=5, createTime=2020-01-12 16:03:36.352, updateTime=2020-01-12 16:03:36.352), name=macchiato, price=CNY 30.00)]
//findOneCoffee
Optional<Coffee> latte = coffeeService.findOneCoffee("Latte");
Optional<Coffee> espresso = coffeeService.findOneCoffee("espresso");
if (latte.isPresent() && espresso.isPresent()) {
CoffeeOrder order = orderService.createOrder("Li Lei", latte.get(), espresso.get());
log.info("Update INIT to PAID: {}", orderService.updateState(order, OrderState.PAID));
//Update INIT to PAID: true
log.info("Update PAID to INIT: {}", orderService.updateState(order, OrderState.INIT));
//Update PAID to INIT: false 无法从 PAID 转到 INIT
}
//orderService
List<CoffeeOrder> list = orderService.findByCustomerOrderById("Li Lei");
log.info("findByCustomerOrderById: {}", getJoinedOrderId(list));
//findByCustomerOrderById: 1
}
//过滤器list中的id,并且添加分隔符循环遍历
private String getJoinedOrderId(List<CoffeeOrder> list) {
return list.stream().map(o -> o.getId().toString())
.collect(Collectors.joining(","));
}
}