《Spring实战》(第6版) 第3章 使用数据
第3章 使用数据
- 使用Spring的JdbcTemplate;
- 创建Spring Data JDBC存储库;
- 使用Spring Data声明JPA存储库;
本章对Taco Cloud应用添加对数据库持久化的支持,关注JDBC和JPA。
3.1 使用JDBC读取和写入数据
Spring对JDBC的支持要归功于JdbcTemplate类。JdbcTemplate提供特殊方式使得开发人员对关系型数据执行SQL操作的时候能够避免使用JDBC常见的繁文缛节和样板式代码。
先
不适用JdbcTemplate查询数据库,案例是原生的,包含连接,准备状态,结果集以及遍历结果集,关闭连接和异常处理,不看也罢,略。
3.1.1 调整领域对象以适应持久化
数据库中的表都有一个ID,Ingredient类已有id字段,同时Taco和TacoOrder都需要增加id,创建日期和时间createdAt字段。
- Taco增加Long id,Date createdAt = new Date();
- TacoOrder增加Long id,Date placedAt;
构造器,getter和setter等方法也对应修改。
3.1.2 使用JdbcTemplate
自动(前面讲过)或手动将Spring Boot的JDBC starter和H2嵌入式数据库依赖添加到构建文件中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
默认情况下,H2数据库名称是随机的,我们要想使用H2控制台连接数据库(Spring Boot DevTools会在http://localhost:8080/h2-console启用该功能),我们要设置一个确定的数据库名称,在配置文件application.properties中通过设置下面属性确定数据库名称:
#false表示不要生成随机数据库名
spring.datasource.generate-unique-name=false
spring.datasource.name=tacocloud
可将配置文件替换成application.yml,了解即可,更有层次性,后续想改自己改。
数据库URL会是:“jdbc:h2:men:tacocloud”,可将其设置到H2控制台连接的JDBC URL中。
定义JDBC存储库
对Ingredient对象进行增删改查操作。
IngredientRepository接口定义三个方法
package tacos.data;
import java.util.Optional;
import tacos.Ingredient;
public interface IngredientRepository {
Iterable<Ingredient> findAll();
Optional<Ingredient> findById(String id);
Ingredient save(Ingredient ingredient);
}
编写IngredientRepository实现类,使用JdbcTemplate来查询数据库。
Spring定义的**构造型(stereotype)**注解:@Repository,@Controller,@Component,组件能扫描到它,将其初始化为上下文的Bean。
@Autowired会隐式通过该构造器的参数应用依赖的自动装配(只有一个构造器时)。
JdbcTemplate的query方法接受SQL语句以及Spring RowMapper的实现(用来将结果集每行数据映射为一个对象)。
用JdbcTemplate的时候,Java方法引用和lambda表达式很便利,能够显式替换RowMapper实现。可以自己使用显式RowMapper。
插入数据,用update方法,提供3个参数。
开始使用JdbcTemplate编写配料存储库
package tacos.data;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import tacos.Ingredient;
@Repository
public class JdbcIngredientRepository implements IngredientRepository {
private JdbcTemplate jdbcTemplate;
@Autowired //这里是自动注入,属于构造器注入
public JdbcIngredientRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Iterable<Ingredient> findAll() {
return jdbcTemplate.query("select id,name,type from Ingredient",
this::mapRowToIngredient);//参考Java核心I中lamdba表达式简写
}
@Override
public Optional<Ingredient> findById(String id) {
List<Ingredient> results = jdbcTemplate.query(
"select id,name,type from Ingredient where id = ?",
this::mapRowToIngredient,
id);
return results.size() == 0 ? Optional.empty() :
Optional.of(results.get(0));
}
@Override
public Ingredient save(Ingredient ingredient) {
jdbcTemplate.update(
"insert into Ingredient (id,name,type) values (?,?,?)",
ingredient.getId(),
ingredient.getName(),
ingredient.getType().toString());
return ingredient;
}
//将结果集转为对象
private Ingredient mapRowToIngredient(ResultSet row,int rowNum) throws SQLException {
return new Ingredient(
row.getString("id"),
row.getString("name"),
Ingredient.Type.valueOf(row.getString("type")));
}
}
编写完毕后,将其注入DesignTacoController,然后使用它来提供配料列表,不用硬编码了。
在控制器中注入和使用存储库
package tacos.web;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.Taco;
import tacos.TacoOrder;
import tacos.data.IngredientRepository;
@Controller
@RequestMapping("/design")
@SessionAttributes("tacoOrder")
public class DesignTacoController {
private static final Logger log = LoggerFactory.getLogger(DesignTacoController.class);
private final IngredientRepository ingredientRepository;
@Autowired
public DesignTacoController(IngredientRepository ingredientRepository) {
this.ingredientRepository = ingredientRepository;
}
@ModelAttribute
public void addIngredientsToModel(Model model) {
Iterable<Ingredient> ingredients = ingredientRepository.findAll();
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
}
@ModelAttribute(name = "tacoOrder")
public TacoOrder order() {
return new TacoOrder();
}
@ModelAttribute(name = "taco")
public Taco taco() {
return new Taco();
}
@GetMapping
public String showDesignForm() {
return "design";
}
@PostMapping
public String processTaco(@Validated Taco taco,Errors errors,
@ModelAttribute TacoOrder tacoOrder) {
if(errors.hasErrors()) {
return "design";
}
tacoOrder.addTaco(taco);
log.info("处理 taco:{}",taco);
return "redirect:/orders/current";
}
private Iterable<Ingredient> filterByType(Iterable<Ingredient> ingredients,Type type){
return StreamSupport.stream(ingredients.spliterator(), true)
.filter(x -> x.getType().equals(type))
.collect(Collectors.toList());
}
}
转换器也可以改变了,通过findById进行简化。
package tacos;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import tacos.data.IngredientRepository;
@Component
public class IngredientByIdConverter implements Converter<String, Ingredient> {
private IngredientRepository ingredientRepository;
@Autowired
public IngredientByIdConverter(IngredientRepository ingredientRepository) {
this.ingredientRepository = ingredientRepository;
}
@Override
public Ingredient convert(String id) {
return ingredientRepository.findById(id).orElse(null);//参考Java核心技术II中optional内容
}
}
改变这些并启动应用之前,先要创建表并填充一些配料数据。
3.1.3 定义模式和预加载数据
- Taco_Order:保存订单的细节信息。
- Taco:保存taco设计相关的必要信息。
- Ingredient_Ref:将taco和与之相关的配料映射在一起,Taco中的每行数据都对应该表中的一行或多行数据。
- Ingredient:保存配料信息。
Taco无法再Taco_Order环境之外存在。
他两被视为同一 聚合(aggregate) 的成员,Taco_Order是聚合根(aggregate root)。
而Ingredient对象则是其聚合的唯一成员,会通过Ingredient_Ref建立与Taco的引用关系。
注意:看看《领域驱动设计》
创建表的SQL语句,schema.sql文件
create table if not exists Taco_Order(
id identity,
delivery_Name varchar(50) not null,
delivery_Street varchar(50) not null,
delivery_City varchar(50) not null,
delivery_State varchar(2) not null,
delivery_Zip varchar(10) not null,
cc_number varchar(16) not null,
cc_expiration varchar(5) not null,
cc_cvv varchar(3) not null,
placed_at timestamp not null
);
create table if not exists Taco(
id identity,
name varchar(50) not null,
taco_order bigint not null,
taco_order_key bigint not null,
created_at timestamp not null
);
create table if not exists Ingredient_Ref(
ingredient varchar(4) not null,
taco bigint not null,
taco_key bigint not null
);
create table if not exists Ingredient(
id varchar(4) not null ,
name varchar(25) not null,
type varchar(10) not null
);
alter table Ingredient add constraint unique_ingredient_id UNIQUE (id);
alter table Taco add foreign key(taco_order) references Taco_Order(id);
alter table Ingredient_Ref add foreign key(ingredient) references Ingredient(id);
这些模式定义放在什么地方,SpringBoot回答了这个问题,如果应用的根路径下存在名为schema.sql的文件,应用启动时会基于数据库执行这个文件中的SQL。
我们将文件保存为名为schema.sql的文件并放到"src/main/resources"文件夹下。
我们还希望在数据库中预加载一些配料数据,SpringBoot还会再应用启动的时候执行根类路径下名为data.sql的文件,我们可将下面的插入语句作为数据库加载配料数据并将其保存在"src/main/resources/data.sql"文件中。
使用data.sql预加载数据库
delete from Ingredient_Ref;
delete from Taco;
delete from Taco_Order;
delete from Ingredient;
insert into Ingredient(id,name,type) values ('FLTO','Flour Tortilla','WRAP');
insert into Ingredient(id,name,type) values ('COTO','Corn Tortilla','WRAP');
insert into Ingredient(id,name,type) values ('GRBF','Ground Beef','PROTEIN');
insert into Ingredient(id,name,type) values ('CARN','Carnitas','PROTEIN');
insert into Ingredient(id,name,type) values ('TMTO','Diced Tomatoes','VEGGIES');
insert into Ingredient(id,name,type) values ('LETC','Lettuce','VEGGIES');
insert into Ingredient(id,name,type) values ('CHED','Cheddar','CHEESE');
insert into Ingredient(id,name,type) values ('JACK','Monterrey Jack','CHEESE');
insert into Ingredient(id,name,type) values ('SLSA','Salsa','SAUCE');
insert into Ingredient(id,name,type) values ('SRCR','Sour Cream','SAUCE');
启动项目,查看功能,按原书来可能出错,自己需要微调!
3.1.4 插入数据
我们已经走马观花的了解了如何使用JdbcTemplate将数据写入数据库。
定义OrderRepository接口
package tacos.data;
import tacos.TacoOrder;
public interface OrderRepository {
TacoOrder save(TacoOrder order);
}
保存TacoOrder的时候必须保存与之关联的Taco对象,同时也要保存特殊的对象即taco和ingredient之间的关系IngredientRef。
package tacos;
public class IngredientRef {
private final String ingredient;
public IngredientRef(String ingredient) {
this.ingredient = ingredient;
}
public String getIngredient() {
return ingredient;
}
@Override
public String toString() {
return "IngredientRef [ingredient=" + ingredient + "]";
}
}
Taco_Order表的id属性是一个identity字段,数据库会自动生成这个值。新增保存后要返回的值中要包含ID,Spring中辅助类GeneratedKeyHolder帮助实现。
订单实现类
package tacos.data;
import java.sql.Types;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import tacos.Ingredient;
import tacos.IngredientRef;
import tacos.Taco;
import tacos.TacoOrder;
@Repository
public class JdbcOrderRepository implements OrderRepository {
private JdbcOperations jdbcOperations;
public JdbcOrderRepository(JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
}
private String pscfSQL = "insert into Taco_Order"
+ "(delivery_name,delivery_street,delivery_city,"
+ "delivery_state,delivery_zip,cc_number,"
+ "cc_expiration,cc_cvv,placed_at)"
+ "values (?,?,?,?,?,?,?,?,?)";
@Override
@Transactional
public TacoOrder save(TacoOrder order) {
PreparedStatementCreatorFactory pscf =
new PreparedStatementCreatorFactory(
pscfSQL,
Types.VARCHAR,Types.VARCHAR,Types.VARCHAR,
Types.VARCHAR,Types.VARCHAR,Types.VARCHAR,
Types.VARCHAR,Types.VARCHAR,Types.TIMESTAMP
);
//生成id
pscf.setReturnGeneratedKeys(true);
order.setPlacedAt(new Date());
PreparedStatementCreator psc = pscf.newPreparedStatementCreator(
Arrays.asList(
order.getDeliveryName(),
order.getDeliveryStreet(),
order.getDeliveryCity(),
order.getDeliveryState(),
order.getDeliveryZip(),
order.getCcNumber(),
order.getCcExpiration(),
order.getCcCVV(),
order.getPlacedAt()));
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcOperations.update(psc,keyHolder);
long orderId = keyHolder.getKey().longValue();
order.setId(orderId);
List<Taco> tacos = order.getTacos();
int i = 0;
for (Taco taco : tacos) {
//保存taco
saveTaco(orderId,i++,taco);
}
return order;
}
private long saveTaco(Long orderId,int orderKey,Taco taco) {
taco.setCreatedAt(new Date());
PreparedStatementCreatorFactory pscf =
new PreparedStatementCreatorFactory(
"insert into Taco"
+"(name,created_at,taco_order,taco_order_key)"
+"values (?,?,?,?)",
Types.VARCHAR,Types.TIMESTAMP,Types.LONGVARCHAR,Types.LONGVARCHAR
);
pscf.setReturnGeneratedKeys(true);
PreparedStatementCreator psc = pscf.newPreparedStatementCreator(
Arrays.asList(
taco.getName(),
taco.getCreatedAt(),
orderId,
orderKey));
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcOperations.update(psc,keyHolder);
long tacoId = keyHolder.getKey().longValue();
taco.setId(tacoId);
saveIngredientRefs(tacoId, taco.getIngredients());
return tacoId;
}
private void saveIngredientRefs(long tacoId,List<Ingredient> ingredients) {
int key = 0;
for (Ingredient ingredient : ingredients) {
jdbcOperations.update(
"insert into Ingredient_Ref(ingredient,taco,taco_key)"
+"values (?,?,?)",
ingredient.getId(),tacoId,key++);
}
}
}
save方法中:
- 创建PreparedStatementCreatorFactory,描述出入以及插入字段的类型,设置稍后GeneratedKeys(true),生成ID。
- 通过上面一条创建PreparedStatementCreator,调用update来真正保存订单数据,API要求传GeneratedKeyHolder用来获取生成后的id,将这个值复制到TacoOrder的id属性上。
- 最后进行循环保存,关注一下@Transactional事务,订单中的所有taco要不全部成功,要不全部失败回滚,保持事务的一致性。
在订单控制器中注入订单仓库实现
package tacos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import tacos.data.OrderRepository;
@Controller
@RequestMapping("/orders")
@SessionAttributes("tacoOrder")
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
private OrderRepository orderRepository;
public OrderController(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@GetMapping("/current")
public String orderForm() {
return "orderForm";
}
@PostMapping
public String processOrder(@Validated TacoOrder order, Errors errors,
SessionStatus sessionStatus) {
if(errors.hasErrors()) {
return "orderForm";
}
TacoOrder returnOrder = orderRepository.save(order);
log.info("Order submitted:{}",returnOrder);
sessionStatus.setComplete();
return "redirect:/";
}
}
3.2 使用Spring Data JDBC
Spring Data是一个非常大的伞形项目,多个子项目对不同数据库类型进行持久化。
- Spring Data JDBC:对关系型数据库进行JDBC持久化。
- Spring Data JPA:对关系型数据库JPA持久化。
- Spring Data MongoDB:持久化Mongo文档数据库。
- Spring Data Neo4j:持久化到Neo4j图数据库
- Spring Data Redis:持久化到Redis键值存储。
- Spring Data Cassandra:持久化到Cassandra列存储数据库。
有趣的特性:基于存储库规范接口自动创建存储库。
3.2.1 添加Spring Data JDBC到构建文件中
添加Spring Data JDBC依赖,手动或自动。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
可以移除starter-jdbc了。
3.2.2 定义存储库接口
我们需要细微改变两个仓库,以Spring Data JDBC方式使用它们。
Spring Data会在运行时自动生成存储库接口的实现,我们只需要拓展Repository接口就行。
修改IngredientRepository
package tacos.data;
import java.util.Optional;
import org.springframework.data.repository.Repository;
import tacos.Ingredient;
public interface IngredientRepository extends Repository<Ingredient, String> {
Iterable<Ingredient> findAll();
Optional<Ingredient> findById(String id);
Ingredient save(Ingredient ingredient);
}
可以看到Repository接口是参数化的,第1个参数是需要持久化的对象类型,第2个参数是持久化对象的ID字段,这里是String。
SpringData也为一些常见操作提供了CrudRepository基础接口,其中包含我们定义的三个方法,与其拓展Repository还不如拓展CrudRepository。
package tacos.data;
import org.springframework.data.repository.CrudRepository;
import tacos.Ingredient;
public interface IngredientRepository extends CrudRepository<Ingredient, String> {
}
方法可以都删除了。
类似的,将OrderRepository也拓展CrudRepository。
package tacos.data;
import org.springframework.data.repository.CrudRepository;
import tacos.TacoOrder;
public interface OrderRepository extends CrudRepository<TacoOrder, Long> {
}
关于SpringData,根本不需要编写任何实现,启动时会自动生成实现,意味着存储库已经就绪,直接注入控制器就可以了。两个实现也可以删了。
3.2.3 为实体类添加持久化注解
在TacoOrder类上添加@Table(可选,映射到Taco_Order表,也可以指定其他表名@Table(“taco_cloud_order”)),ID上添加@Id。
@Column声明deliveryName映射为指定名称。
其他类也类似。
3.2.4 使用CommandLineRunner预加载数据
SpringBoot提供两个非常有用的接口,用于应用启动时执行一定的逻辑,即CommandLineRunner和ApplicationRunner。
都是函数式接口,需要实现一个run方法,启动应用时,上下文所有实现了CommandLineRunner和ApplicationRunner的Bean都会执行其run()方法,执行时机是应用上下文和所有bean装配完毕之后,其他所有功能执行之前,这为将数据加载到数据库中提供了便利。
在配置类中很容易声明为bean,只需要在一个返回lambda表达式的方法上使用@Bean注解,请看如下案例:
package tacos.web;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.data.IngredientRepository;
@Configuration
public class RunnerConfig {
@Bean
public CommandLineRunner dataLoader(IngredientRepository repo) {
return args -> {
repo.save(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
repo.save(new Ingredient("COTO", "Corn Tortilla", Type.WRAP));
repo.save(new Ingredient("GRBF", "Ground Beef", Type.PROTEIN));
repo.save(new Ingredient("CARN", "Carnitas", Type.PROTEIN));
repo.save(new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES));
repo.save(new Ingredient("LETC", "Lettuce", Type.VEGGIES));
repo.save(new Ingredient("CHED", "Cheddar", Type.CHEESE));
repo.save(new Ingredient("JACK", "Monterrey Jack", Type.CHEESE));
repo.save(new Ingredient("SLSA", "Salsa", Type.SAUCE));
repo.save(new Ingredient("SRCR", "Sour Cream", Type.SAUCE));
};
}
/*
* @Bean public ApplicationRunner dataLoader(IngredientRepository repo) { return
* args -> { repo.save(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
* repo.save(new Ingredient("COTO", "Corn Tortilla", Type.WRAP)); repo.save(new
* Ingredient("GRBF", "Ground Beef", Type.PROTEIN)); repo.save(new
* Ingredient("CARN", "Carnitas", Type.PROTEIN)); repo.save(new
* Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES)); repo.save(new
* Ingredient("LETC", "Lettuce", Type.VEGGIES)); repo.save(new
* Ingredient("CHED", "Cheddar", Type.CHEESE)); repo.save(new Ingredient("JACK",
* "Monterrey Jack", Type.CHEESE)); repo.save(new Ingredient("SLSA", "Salsa",
* Type.SAUCE)); repo.save(new Ingredient("SRCR", "Sour Cream", Type.SAUCE)); };
* }
*/
}
两个方法差不多,也就是arg参数不同,具体用到自己查吧。
这样的好处是可以不用SQL文件,而用存储库来创建持久化对象。
3.3 使用Spring Data JPA持久化数据
Java Persistence API(JPA)是另一个处理关系型数据库中数据的流行方案。
3.3.1 添加Spring Data JPA到项目中
手动或自动
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
JPA默认引用Hibernate依赖,想使用其他的需要替代它,用exclusion标签,自己查吧。
3.3.2 将领域对象标注为实体
解释一下注解意思:
- @Entity,实体类上必须加,识别为一个持久化对象。
- @Id,当前字段为id。
- @GeneratedValue(strategy = GenerationType.AUTO),id是自动生成的。
- @ManyToMany,多对多,表示每个Taco可以有多个Ingredient,每个Ingredient可以是多个Taco的组成部分。
- @OneToMany(cascade = CascadeType.ALL),所有taco都属于这个订单,删除此订单时,所有的关联taco也都会删除。
3.3.3 声明JPA存储库
CrudRepository同样也适用于JPA,与Spring Data JDBC定义的接口完全一样,此接口在众多Spring Data项目中广泛使用,无须关心底层的持久化机制是什么。
存储库不用改。
3.3.4 自定义JPA存储库
除了CrudRepository提供的基本CRUD操作之外,还需要获取投递到指定邮编(ZIP code)的订单,实际上,只需要添加如下的方法声明到OrderRepository中。
package tacos.data;
import java.util.Date;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import tacos.TacoOrder;
public interface OrderRepository extends CrudRepository<TacoOrder, Long> {
List<TacoOrder> findByDeliveryZip(String deliveryZip);
List<TacoOrder> readOrdersByDeliveryZipAndPlacedAtBetween(
String deliveryZip, Date startDate, Date endDate);
List<TacoOrder> findByDeliveryToAndDeliveryCityAllIgnoresCase(
String deliveryTo, String deliveryCity);
List<TacoOrder> findByDeliveryCityOrderByDeliveryTo(String city);
@Query("select from Order o where o.deliveryCity = 'Seattle'")
List<TacoOrder> readOrdersDeliveredInSeattle();
}
- Spring Data定义了一组小型领域特定语言(Domain-Specific Language,DSL),持久化的细节都是通过存储库方法的签名描述的。
- 存储库的方法由一个动词、一个可选的主题(subject)、关键词By,以及一个断言组成,findByDeliveryZip中,动词是find,断言是DeliveryZip,主题没有指定,暗含主题是TacoOrder。
- 更复杂的方法,readOrdersByDeliveryZipAndPlacedAtBetween。
通过@Query可以执行任何想要的查询。
登录H2看一下数据