Spring实战(使用数据)
使用数据
- 使用JDBC读取和写入数据
- 调整领域对象以适应持久化
- 使用JdbcTemplate
- 定义JDBC repository
- 定义模式和预加载数据
- 插入数据
- 使用Spring Data JPA持久化数据
- 将领域对象标注为实体
- 声明JPA repository
- 自定义JPA repository
- 小结
在本章会为Taco Cloud应用添加对数据持久化的支持。会使用Spring对JDBC(Java Database Connectivity) 的支持来消除样板式代码。 随后,会使用JPA(Java Persistence API) 重写
数据repository, 进一步消除更多的代码。
使用JDBC读取和写入数据
在处理关系型数据的时候, Java开发人员有多种可选方案, 其中最常见的是JDBC和JPA。 Spring同时支持这两种抽象形式, 能够让JDBC或JPA的使用更加容易。
Spring对JDBC的支持要归功于JdbcTemplate类。 JdbcTemplate提供了一种特殊的方式, 通过这种方式, 开发人员在对关系型数据库执行SQL操作的时候能够避免使用JDBC时常见的繁文缛节和样板式代码。
Spring对JDBC的支持要归功于JdbcTemplate类。 JdbcTemplate提供了一种特殊的方式, 通过这种方式, 开发人员在对关系型数据库执行SQL操作的时候能够避免使用JDBC时常见的繁文缛节和样板式代码。
不使用JdbcTemplate类查询
@Override
public Ingredient findOne(String id) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = dataSource.getConnection();statement = connection.prepareStatement("select id, name, type from Ingredient where id=?");statement.setString(1, id);resultSet = statement.executeQuery();Ingredient ingredient = null;if(resultSet.next()) {ingredient = new Ingredient(resultSet.getString("id"),resultSet.getString("name"),Ingredient.Type.valueOf(resultSet.getString("type")));}return ingredient;} catch (SQLException e) {// ??? What should be done here ???} finally {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {}}if (statement != null) {try {statement.close();} catch (SQLException e) {}}if (connection != null) {try {connection.close();} catch (SQLException e) {}}}return null;
}
使用JdbcTemplate类
private JdbcTemplate jdbc;
@Override
public Ingredient findOne(String id) {return jdbc.queryForObject("select id, name, type from Ingredient where id=?",this::mapRowToIngredient, id);}
private Ingredient mapRowToIngredient(ResultSet rs, int rowNum)throws SQLException {return new Ingredient(rs.getString("id"),rs.getString("name"),Ingredient.Type.valueOf(rs.getString("type")));
}
不使用JdbcTemplate类,需要自己创建连接、 创建语句以及关闭连接、 语句和结果集的清理等。
而使用JdbcTemplate类,仅仅关注执行查询(调用JdbcTemplate的queryForObject()) 和将结果映射到Ingredient对象(在mapRowToIngredient()方法中)上。
调整领域对象以适应持久化
在将对象持久化到数据库的时候, 通常最好有一个字段作为对象的唯一标识。 Ingredient类现在已经有了一个id字段, 但是我们还需要将id字段添加到Taco和Order类中。
除此之外, 记录Taco和Order是何时创建的可能会非常有用。 所以, 还要为每个对象添加一个字段来捕获它所创建的日期和时间。
@Data
public class Taco {private Long id;private Date createdAt;...
}
@Data
public class Order {private Long id;private Date placedAt;...
}
使用JdbcTemplate
在开始使用JdbcTemplate之前,需要添加JDBC的依赖,只需要在Spring Boot的JDBC starter依赖添加到构建文件中就可以了:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
还需要一个存储数据的数据库。 这里使用H2嵌入式数据库, 将如下的依赖添加到构建文件中:
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope>
</dependency>
定义JDBC repository
Ingredient repository需要完成以下操作。
- 查询所有的配料信息, 将它们放到一个Ingredient对象的集合中;
- 根据id, 查询单个Ingredient;
- 保存Ingredient对象。
IngredientRepository接口以方法声明的方式定义了3个操作:
public interface IngredientRepository {Iterable<Ingredient> findAll();Ingredient findOne(String id);Ingredient save(Ingredient ingredient);
}
实现IngredientRepository接口, 使用JdbcTemplate来查询数据库。
@Repository
public class IngredientRepositoryImpl implements IngredientRepository {@Autowiredprivate JdbcTemplate jdbc;@Overridepublic Iterable<Ingredient> findAll() {return jdbc.query("select id, name, type from Ingredient",this::mapRowToIngredient);}@Overridepublic Ingredient findOne(String id) {return jdbc.queryForObject("select id, name, type from Ingredient where id=?",this::mapRowToIngredient, id);}private Ingredient mapRowToIngredient(ResultSet rs, int rowNum)throws SQLException {return new Ingredient(rs.getString("id"),rs.getString("name"),Ingredient.Type.valueOf(rs.getString("type")));}@Overridepublic Ingredient save(Ingredient ingredient) {jdbc.update("insert into Ingredient (id, name, type) values (?, ?, ?)",ingredient.getId(),ingredient.getName(),ingredient.getType().toString());return ingredient;}
}
IngredientRepositoryImpl 添加了@Repository注解。IngredientRepositoryImpl 添加@Repository注解之后, Spring的组件扫描就会自动发现它, 并且会将其初始化为Spring应用上下文中的bean。
@Autowired注解将JdbcTemplate注入进来。 给JdbcTemplate赋值一个实例变量, 这个变量会被其他方法用来执行数据库查询和插入操作。
findAll()和findOne()以相同的方式使用了JdbcTemplate。
findAll()方法预期返回一个对象的集合, 它使用了JdbcTemplate的query()方法。
query()会接受要执行的SQL以及Spring RowMapper的一个实现(用来将结果集中的每行数据映射为一个对象)。
findOne()方法预期只会返回一个Ingredient对象, 所以它使用了JdbcTemplate的queryForObject()方法。queryForObject()方法的运行方式和query()非常类似, 只不过它只返回一个对象, 而不是对象的List。
save()方法向数据库中写入数据,JdbcTemplate的update()方法可以用来执行向数据库中写入或更新数据的查询语句,在save方法中使用jdbc中的update()方法。
update()方法只需要一个包含待执行SQL的String以及每个查询参数对应的值即可。
IngredientRepository编写完成之后, 我们就可以将其注入到DesignTacoController中了, 然后使用它来提供Ingredient对象的列表。
修改后的DesignTacoController
@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {@Autowiredprivate IngredientRepository ingredientRepository;@GetMappingpublic String showDesignForm(Model model) {List<Ingredient> ingredients = new ArrayList<>();ingredientRepo.findAll().forEach(i -> ingredients.add(i));Type[] types = Ingredient.Type.values();for (Type type : types) {model.addAttribute(type.toString().toLowerCase(),filterByType(ingredients, type));} return "design";} ...
}
定义模式和预加载数据
在数据库中定义出表结构和向表中写入数据。
在数据库中将定义如下的表:
- Ingredient: 保存配料信息。
- Taco: 保存taco设计相关的信息。
- Taco_Ingredients: Taco中的每行数据都对应一行或多行, 将taco和与之相关的配料映射在一起。
- Taco_Order: 保存必要的订单细节。
- Taco_Order_Tacos: Taco_Order中的每行数据都对应一行或多行,将订单和与之相关的taco映射在一起。
create table if not exists Ingredient (id varchar(4) not null primary key,name varchar(25) not null,type varchar(10) not null
);
create table if not exists Taco (id identity,name varchar(50) not null,createdAt timestamp not null
);
create table if not exists Taco_Ingredients (taco bigint not null,ingredient varchar(4) not null
);
alter table Taco_Ingredients
add foreign key (taco) references Taco(id);
alter table Taco_Ingredients
add foreign key (ingredient) references Ingredient(id);
create table if not exists Taco_Order (id identity,deliveryName varchar(50) not null,deliveryStreet varchar(50) not null,deliveryCity varchar(50) not null,deliveryState varchar(2) not null,deliveryZip varchar(10) not null,ccNumber varchar(16) not null,ccExpiration varchar(5) not null,ccCVV varchar(3) not null,placedAt timestamp not null
);
create table if not exists Taco_Order_Tacos (tacoOrder bigint not null,taco bigint not null
);
alter table Taco_Order_Tacos
add foreign key (tacoOrder) references Taco_Order(id);
alter table Taco_Order_Tacos
add foreign key (taco) references Taco(id);
将上面的文件命名为schema.sql,并放到“src/main/resources”文件夹下。应用启动的时候将会基于数据库执行这个文件中的SQL。
delete from Taco_Order_Tacos;
delete from Taco_Ingredients;
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');
在数据库中预加载一些配料数据。Spring Boot还会在应用启动的时候执行根类路径下名为data.sql的文件。可以将上面的sql文件命名为data.sql,将文件放在src/main/resources/路径下。
插入数据
有以下两种保存数据的方法。
直接使用update()方法。
使用SimpleJdbcInsert包装器类。
IngredientRepositoryImpl 的save()方法就是使用JdbcTemplate的update()方法将Ingredient对象保存到了数据库中。
使用JdbcTemplate保存数据
在程序中创建taco和order的repository类,类中唯一需要做的事情就是保存对应的对象。
TacoRepository接口
public interface TacoRepository {Taco save(Taco design);
}
OrderRepository接口
public interface OrderRepository {Order save(Order order);
}
保存taco的时候需要同时将与该taco关联的配料保存到Taco_Ingredients表中。 与之类似, 保存订单的时候, 需要同时将与该订单关联的taco保存到Taco_Order_Tacos表中。 这样看来, 保存taco和订单就会比保存配料更困难一些。
save()方法首先保存必要的taco设计细节(比如, 名称和创建时间) , 然后对Taco对象中的每种配
料都插入一行数据到Taco_Ingredients中。
@Repository
public class TacoRepositoryImpl implements TacoRepository {@Autowiredprivate JdbcTemplate jdbc;@Overridepublic Taco save(Taco taco) {long tacoId = saveTacoInfo(taco);taco.setId(tacoId);for (Ingredient ingredient : taco.getIngredients()) {saveIngredientToTaco(ingredient, tacoId);}return taco;}private long saveTacoInfo(Taco taco) {taco.setCreatedAt(new Date());PreparedStatementCreatorFactory preparedStatementCreatorFactory =new PreparedStatementCreatorFactory("INSERT into Taco (name,createdAt) values (?,?)", Types.VARCHAR,Types.TIMESTAMP);preparedStatementCreatorFactory.setReturnGeneratedKeys(true); //不加这句话会导致keyHolder.getKey()获取不到idPreparedStatementCreator psc = preparedStatementCreatorFactory.newPreparedStatementCreator(Arrays.asList(taco.getName(), new Timestamp(taco.getCreatedAt().getTime())));KeyHolder keyHolder = new GeneratedKeyHolder();jdbc.update(psc, keyHolder);return keyHolder.getKey().longValue();}private void saveIngredientToTaco(Ingredient ingredient, long tacoId) {jdbc.update("insert into Taco_Ingredients (taco, ingredient) " + "values (?, ?)",tacoId, ingredient.getId());}
}
save()方法首先调用了私有的saveTacoInfo()方法,然后使用该方法所返回的taco ID来调用saveIngredientToTaco(), 最后的这个方法会保存每种配料。
当向Taco中插入一行数据的时候, 我们需要知道数据库生成的ID,这样我们才可以在每个配料信息中引用它。 这里的update()方法需要接受一个PreparedStatementCreator和一个KeyHolder。 KeyHolder将会为我们提供生成的taco ID。
创建PreparedStatementCreator,需要创建PreparedStatementCreatorFactory, 并将我们要执行的
SQL传递给它, 同时还要包含每个查询参数的类型。 调用工厂类的newPreparedStatementCreator()方法, 并将查询参数所需的值传递进来, 这样才能生成一个PreparedStatementCreator。
调用update()将PreparedStatementCreator和KeyHolder传递进来,keyHolder.getKey().longValue()返回taco的ID。
接下来轮询Taco中的每个Ingredient,并调用saveIngredientToTaco()。 saveIngredientToTaco()使用更简单的update()形式来将对配料的引用保存到Taco_Ingredients表中。
对于TacoRepository来说, 剩下的事情就是将它注入到DesignTacoController中, 并在保存taco的时候调用它。
@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {@ModelAttribute(name = "order")public Order order() {return new Order();} @ModelAttribute(name = "taco")public Taco taco() {return new Taco();} @PostMappingpublic String processDesign(@Valid Taco design, Errors errors,@ModelAttribute Order order) {if (errors.hasErrors()) {return "design";} Taco saved = designRepo.save(design);order.addDesign(saved);return "redirect:/orders/current";} ...
}
@SessionAttributes(“order”),类级别的@SessionAttributes能够指定模型对象要保存在session中, 这样才能跨请求使用。
@SessionAttributes的使用:
如果在类上面使用了@SessionAttributes(“attributeName”)注解,而本类中恰巧存在attributeName,则会将attributeName放入到session作用域。
如果在类上面使用了@SessionAttributes(“attributeName”)注解,SpringMVC会在执行方法之前,自动从session中读取key为attributeName的值,并注入到Model中。
@ModelAttribute运用详解
- 应用在方法上
- 应用在方法的参数上
被@ModelAttribute注解的方法会在Controller每个方法执行之前都执行,会将方法中返回的对象放在Model中,会将对象带入对应的Controller方法中。
使用@ModelAttribute注解的参数,意思是从前面的Model中提取对应名称的属性。
在检查完校验错误之后, processDesign()使用注入的TacoRepository来保存taco。 然后, 它将Taco对象保存到session里面的Order中
使用SimpleJdbcInsert插入数据
在保存订单的时候,不仅要将订单数据保存到Taco_Order表中, 还要将订单对每个taco的引用保存到
Taco_Order_Tacos表中。 引入SimpleJdbcInsert, 这个对象对JdbcTemplate进行了包装, 能够更容易地将数据插入到表中。
@Repository
public class OrderRepositoryImpl implements OrderRepository {private JdbcTemplate jdbc;private SimpleJdbcInsert orderInserter;private SimpleJdbcInsert orderTacoInserter;private ObjectMapper objectMapper;@Autowiredpublic OrderRepositoryImpl(JdbcTemplate jdbc) {this.orderInserter = new SimpleJdbcInsert(jdbc).withTableName("Taco_Order").usingGeneratedKeyColumns("id");this.orderTacoInserter = new SimpleJdbcInsert(jdbc).withTableName("Taco_Order_Tacos");this.objectMapper = new ObjectMapper();}@Overridepublic Order save(Order order) {order.setCreatedAt(new Date());long orderId = saveOrderDetails(order);order.setId(orderId);List<Taco> tacos = order.getTacos();for (Taco taco : tacos) {saveTacoToOrder(taco, orderId);}return order;}private long saveOrderDetails(Order order) {@SuppressWarnings("unchecked")Map<String, Object> values = objectMapper.convertValue(order, Map.class);values.put("placedAt", order.getCreatedAt());long orderId = orderInserter.executeAndReturnKey(values).longValue();return orderId;}private void saveTacoToOrder(Taco taco, long orderId) {Map<String, Object> values = new HashMap<>();values.put("tacoOrder", orderId);values.put("taco", taco.getId());orderTacoInserter.execute(values);}
}
在构造器中会创建SimpleJdbcInsert的两个实例,用来把值插入到Taco_Order和Taco_Order_Tacos表中。
第一个实例赋值给了orderInserter实例变量, 配置为与Taco_Order表协作, 并且假定id属性将会由数据库提供或生成。
第二个实例赋值给了orderTacoInserter实例变量, 配置为与Taco_Order_Tacos表协作, 但是没有声明该表中ID是如何生成的。
该构造器还创建了Jackson中ObjectMapper类的一个实例, 并将其赋值给一个实例变量。
save()方法没有保存任何内容, 只是定义了保存Order及其关联的Taco对象的流程,并将实际的持久化任务委托给了saveOrderDetails()和saveTacoToOrder()。
SimpleJdbcInsert有两个非常有用的方法来执行数据插入操作:execute()和execute AndReturnKey()。
它们都接受Map<String, Object>作为参数, 其中Map的key对应表中要插入数据的列名, 而Map中的value对应要插入到列中的实际值。
在saveOrderDetails()中, 我决定使用Jackson的ObjectMapper及其convertValue()方法, 以便于将Order转换为Map。Map创建完成之后,将Map中placedAt条目的值设置为Order对象placedAt属性的值。 调用orderInserter的executeAnd ReturnKey()方法了。 该方法会将订单信息保存到Taco_Order
表中, 并以Number对象的形式返回数据库生成的ID, 继而调用longValue()方法将返回值转换为long类型。
saveTacoToOrder()方法要简单得多。创建了一个Map并设置对应的值。调用orderTacoInserter的execute()方法就能执行插入操作了。
将OrderRepository注入到OrderController中并开始使用它了。
@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
public class OrderController {@Autowiredprivate OrderRepository orderRepository;@GetMapping("/current")public String orderForm() {return "orderForm";} @PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus) {if (errors.hasErrors()) {return "orderForm";} orderRepository.save(order);sessionStatus.setComplete();return "redirect:/";}
}
将OrderRepository注入到控制器中,OrderRepository的save()方法进行保存提交的Order对象。
订单保存完成之后, 我们就不需要在session中持有它了。所以,processOrder()方法请求了一个SessionStatus参数, 并调用了它的setComplete()方法来重置session。
相对于普通的JDBC,Spring的JdbcTemplate和SimpleJdbcInsert能够极大地简化关系型数据库的使用。 但是, 使用JPA会更加简单。
尝试运行,发现很多问题。Taco类getIngredients()返回值问题,表单中的Ingredient都是String类型,而此时没有办法将String类型转化为Ingredient,需要配置Converter类将Sring转化为Ingredient类。
添加如下类,完成String到Ingredient的转换。
@Component
public class IngredientByIdConverter implements Converter<String,Ingredient> {@Autowiredprivate IngredientRepository ingredientRepository;@Overridepublic Ingredient convert(String id) {return ingredientRepository.findOne(id);}
}
问题:JdbcTacoRepository的keyHolder.getKey().longValue()空指针问题,上面代码中已经解决。
问题:H2控制台访问问题,在properties文件中,设置url,用户和密码。输入设置的url,用户,密码,进行访问。
spring.thymeleaf.cache=false
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.sql.init.schema-locations=classpath:db/schema.sql
spring.sql.init.data-locations=classpath:db/data.sql
spring.h2.console.enabled=true
使用Spring Data JPA持久化数据
Spring Boot应用可以通过JPA starter来添加Spring Data JPA。 这个starter依赖不仅会引入Spring Data JPA, 还会传递性地将Hibernate作为JPA实现引入进来:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
将领域对象标注为实体
打开Ingredient、 Taco和Order类, 并为其添加一些注解。
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@Entity
public class Ingredient {@Idprivate final String id;private final String name;private final Type type;public static enum Type {WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE}
}
将Ingredient声明为JPA实体, 它必须添加@Entity注解。 它的id属性需要使用@Id注解, 以便于将其指定为数据库中唯一标识该实体的属性。
@NoArgsConstructor注解。JPA需要实体有一个无参的构造器, Lombok的@NoArgsConstructor注解能够实现这一点。 但是, 我们不想直接使用它, 因此通过将access属性设置AccessLevel.PRIVATE使其变成私有的。 因为这里有必须要设置的final属性, 所以我们将force设置为true, 这样Lombok生成的构造器就会将它们设置为null。
@Data注解会为我们添加一个有参构造器,但使用@NoArgsConstructor注解后, 这个构造器就会被移除掉。 显式添加@RequiredArgsConstructor注解, 以确保除了private的无参构造器之外,还会有一个有参构造器。
@Data
@Entity
public class Taco {@Id@GeneratedValue(strategy=GenerationType.AUTO)private Long id;@NotNull@Size(min=5, message="Name must be at least 5 characters long")private String name;private Date createdAt;@ManyToMany(targetEntity=Ingredient.class)@Size(min=1, message="You must choose at least 1 ingredient")private List<Ingredient> ingredients;@PrePersistvoid createdAt() {this.createdAt = new Date();}
}
Taco类现在添加了@Entity注解, 并为其id属性添加了@Id注解。 要依赖数据库自动生成ID值, 所以在这里还为id属性设置了@GeneratedValue, 将它的strategy设置为AUTO。
为了声明Taco与其关联的Ingredient列表之间的关系, 为ingredients添加了@ManyToMany注解。 每个Taco可以有多个Ingredient, 而每个Ingredient可以是多个Taco的组成部分。
@PrePersist注解。 在Taco持久化之前, 我们会使用这个方法将createdAt设置为当前的日期和时间。
@Data
@Entity
@Table(name="Taco_Order")
public class Order implements Serializable {private static final long serialVersionUID = 1L;@Id@GeneratedValue(strategy=GenerationType.AUTO)private Long id;private Date placedAt;...@ManyToMany(targetEntity=Taco.class)private List<Taco> tacos = new ArrayList<>();public void addDesign(Taco design) {this.tacos.add(design);} @PrePersistvoid placedAt() {this.placedAt = new Date();}
}
@Table,它表明Order实体应该持久化到数据库中名为Taco_Order的表中。如果没有它, JPA默认会将实体持久化到名为Order的表中。
声明JPA repository
在JDBC版本的repository中, 我们显式声明想要repository提供的方法。 但是,借助 Spring Data,我们可以扩展CrudRepository接口。
CrudRepository定义了很多用于CRUD(创建、 读取、 更新、 删除)操作的方法。 注意, 它是参数化的, 第一个参数是repository要持久化的实体类型, 第二个参数是实体ID属性的类型。
import org.springframework.data.repository.CrudRepository;
import tacos.Ingredient;
public interface IngredientRepository extends CrudRepository<Ingredient, String> {}
import org.springframework.data.repository.CrudRepository;
import tacos.Taco;
public interface TacoRepository extends CrudRepository<Taco, Long> {}
import org.springframework.data.repository.CrudRepository;
import tacos.Order;
public interface OrderRepository extends CrudRepository<Order, Long> {}
现在,有了3个repository。 你可能会想, 我们应该需要编写它们的实现类, 包括每个实现类所需的十多个方法。
Spring Data JPA带来的好消息是, 我们根本就不用编写实现类! 当应用启动的时候, Spring Data JPA会在运行期自动生成实现类。 可以直接使用这些repository了。 我们只需要像使用基于JDBC的实现那样将它们注入控制器中就可以了。
自定义JPA repository
除了CrudRepository提供的基本CRUD操作之外,
Spring Data定义了一组小型的领域特定语言(DomainSpecific Language, DSL) , 在这里持久化的细节都是通过repository方法的签名来描述的,
repository方法是由一个动词、 一个可选的主题(Subject) 、 关键词By以及一个断言所组成的。
例子:
List<Order> readOrdersByDeliveryZipAndPlacedAtBetween(String deliveryZip, Date startDate, Date endDate);
动词是read。Spring Data会将get、 read和find视为同义词, 它们都是用来获取一个或多个实体的。 count作为动词, 它会返回一个int值, 代表匹配实体的数量。
方法的主题是可选的, 但是这里要查找的就是Order。 SpringData会忽略主题中大部分的单词。
deliveryZip属性的值必须要等于方法第一个参数传入的值。 关键字Between表明placedAt属性的值必须要位于方法最后两个参数的值之间。
除了Equals和Between操作之外, Spring Data方法签名还能包括如下的操作符:
- IsAfter、 After、 IsGreaterThan、 GreaterThan
- IsGreaterThanEqual、 GreaterThanEqual
- IsBefore、 Before、 IsLessThan、 LessThan
- IsLessThanEqual、 LessThanEqual
- IsBetween、 Between
- IsNull、 Null
- IsNotNull、 NotNull
- IsIn、 In
- IsNotIn、 NotIn
- IsStartingWith、 StartingWith、 StartsWith
- IsEndingWith、 EndingWith、 EndsWith
- IsContaining、 Containing、 Contains
- IsLike、 Like
- IsNotLike、 NotLike
- IsTrue、 True
- IsFalse、 False
- Is、 EqualsIsNot、 Not
- IgnoringCase、 IgnoresCase
还可以在方法名称的结尾处添加OrderBy, 实现结果集根据某个列排序。
尽管方法名称约定对于相对简单的查询非常有用,但是,对于更为复杂的查询, 方法名可能会面临失控的风险。 在这种情况下, 可以将方法定义为任何你想要的名称, 并为其添加@Query注解,从而明确指明方法调用时要执行的查询。
例子:
@Query("Order o where o.deliveryCity='Seattle'")
List<Order> readOrdersDeliveredInSeattle();
小结
- Spring的JdbcTemplate能够极大地简化JDBC的使用。
- 在我们需要知道数据库所生成的ID值时, 可以组合使用PreparedStatementCreator和KeyHolder。为了简化数据的插入, 可以使用SimpleJdbcInsert。
- Spring Data JPA能够极大地简化JPA持久化, 我们只需编写repository接口即可。
Spring实战(使用数据)相关推荐
- 【Spring实战】—— 14 传统的JDBC实现的DAO插入和读取
从这篇开始学习Spring的JDBC,为了了解Spring对于JDBC的作用,先通过JDBC传统的流程,实现一个数据库的插入和读取. 从这篇你可以了解到: 1 传统的JDBC插入和读取的过程. 2 如 ...
- Spring实战(第3版)
<Spring实战(第3版) > 基本信息 原书名:Spring in Actiong(third edition) 作者: (美)Craig Walls 译者: 耿渊 张卫滨 出版社:人 ...
- 《spring实战第四版》的读书笔记
<spring实战第四版>的读书笔记 1 概述 <Spring实战第四版>描述了Spring4架构的设计,看完了以后,最大感觉是Spring的IOC与aop理念实在是太强大了, ...
- 《Spring实战》读书笔记--SpringMVC之forward与redirect
<Spring实战>读书笔记--SpringMVC之forward与redirect 1.forward与redirect介绍 1.1 redirect 重定向,服务器收到请求后发送一个状 ...
- Spring实战6-利用Spring和JDBC访问数据库
主要内容 定义Spring的数据访问支持 配置数据库资源 使用Spring提供的JDBC模板 写在前面:经过上一篇文章的学习,我们掌握了如何写web应用的控制器层,不过由于只定义了SpitterRep ...
- Spring实战——缓存
缓存 提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis-- 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓存技术的优势就是快,无需反复查询等. 当然 ...
- Spring实战-雇员薪资管理系统
Spring实战-雇员薪资管理系统 基本概述 这里使用的是Spring2.5+Hibernate3.3+Struts1.3(SSH)框架,进行整合开发,通过该案例可以了解使用SHH框架进行开发的基本操 ...
- 【转】Nutz | Nutz项目整合Spring实战
http://blog.csdn.net/evan_leung/article/details/54767143 Nutz项目整合Spring实战 前言 Github地址 背景 实现步骤 加入spri ...
- (转)Nutz | Nutz项目整合Spring实战
http://blog.csdn.net/evan_leung/article/details/54767143 Nutz项目整合Spring实战 前言 Github地址 背景 实现步骤 加入spri ...
- Spring实战——无需一行xml配置实现自动化注入
已经想不起来上一次买技术相关的书是什么时候了,一直以来都习惯性的下载一份电子档看看.显然,如果不是基于强烈的需求或强大的动力鞭策下,大部分的书籍也都只是蜻蜓点水,浮光掠影. 就像有位同事说的一样,有些 ...
最新文章
- 软件测试培训完就业方向怎么选择
- 简简单单用OpenCV让一只小猫咪变成奶凶奶凶的科技猫
- android从放弃到精通 第九天 勿忘初心
- 个性化邮件系统用例设计和实现
- JS 移动端触屏滑动
- Linxu用户登录监测
- 8步教你打开Android之门 NDK入门教程
- 优秀的 jQuery 文本输入框自动完成 自动提示插件
- 九阴真经 服务器 显示维护,《九阴真经》1月29日服务器互通升级维护公告
- windows 服务实现定时任务调度(Quartz.Net)
- 【Java】 Java反射机制总结
- Apache Flink,流计算?不仅仅是流计算! 1
- WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None))
- paip.应用程序远程WEB 接口的设计
- 同志社交Blued赴美上市背后的三个问号
- 实时渲染——三种渲染方法介绍
- Git Gui 中文乱码
- 小象学院python数据分析课程怎么样_小象学院大数据分析集训营试听知识点整理-正则表达式...
- 几款软件界面模型设计工具
- 计算机安装系统作用是什么,告诉你重装系统对电脑有什么影响
热门文章
- 基于SPSS的聚类分析原理概述
- 冯 诺依曼计算机体系结构要点,冯诺依曼体系结构计算机的要点和工作过程
- 医院远程会诊管理系统方案/案列/软件/APP/小程序/网站
- 未来IT技术展现——虚拟化技术大盘点
- .NET软件工程师网络在线培训就业课程
- 网络电视机顶盒测试软件,网络机顶盒最值得安装的软件,可免费收看全网vip独播剧!...
- 新型安卓木马SpyNote生成器遭泄露
- Discuz论坛程序用户如何删除自己的帖子?
- Advanced IP Scanner - 网络扫描器
- CTF web题常用解题工具