借助Spring Data实现自动化的JPA Repository
删掉JpaSpitterRepository、JpaSpittleRepository,使用Spring Data编写Repository接口。
看一下SpitterRepository接口
程序清单11.4 借助Spring Data,以接口定义的方式创建 Repository
public interface SpitterRepository extends JpaRepository<Spitter, Long>
这里,SpitterRepository扩展了Spring Data JPA的JpaRepository。通过这种方式,JpaRepository进行了参数化,所以它就能知道这是一个用来持久化Spitter对象的Repository,并且Spitter的ID类型为Long。另外,它还会继承18个执行持久化操作的通用方法,如保 存Spitter、删除Spitter以及根据ID查询Spitter。
为了要求Spring Data创建SpitterRepository的实现,我们需要在 Spring配置中添加一个元素。要使用Java配置的话,要在Java配置类上添加@EnableJpaRepositories注解。
程序清单 DefaultAppConfig.java
package spittr.config;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Spring4基于注解的配置类, 用于代替原来的<b>applicationContext.xml配置文件
* @author lenovo
*
*/
@Configuration
@Import(DataSourceConfig.class) //导入spring jdbc配置
//开启事务管理,proxyTargetClass= true代表开启类的事务管理
@EnableTransactionManagement(proxyTargetClass = true)
//basePackages属性用于配置扫描Repositories所在的package及子package,transactionManagerRef属性是事务管理工厂引用名称,对应到@Bean注解对应的方法
@EnableJpaRepositories(basePackages="spittr.data", entityManagerFactoryRef="entityManagerFactory")
public class DefaultAppConfig {
@Autowired
private DataSource dataSource;
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(dataSource);
emfb.setJpaVendorAdapter(jpaVendorAdapter);
emfb.setJpaProperties(hibernateProperties());
emfb.setPackagesToScan("spittr");
return emfb;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
//设置所使用的数据库为MySQL
adapter.setDatabase(Database.MYSQL);
//是否在控制台打印生成的SQL语句
adapter.setShowSql(true);
//是否在控制台打印生成的SQL语句
adapter.setGenerateDdl(false);
adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
return adapter;
}
private Properties hibernateProperties() {
Properties props = new Properties();
//SQL dialect 指定使用MySQL数据库格式的SQL语句
//props.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
//是否在控制台打印生成的SQL语句
//props.setProperty("hibernate.show_sql", "true");
//输出格式化后的sql,更方便查看
props.setProperty("hibernate.format_sql", "false");
//指定Hibernate启动的时候自动创建或更新表结构
props.setProperty("hibernate.hbm2ddl.auto", "update");
//关闭beanvalitionFactory自动验证
props.setProperty("javax.persistence.validation.mode", "none");
return props;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager manager = new JpaTransactionManager();
manager.setEntityManagerFactory(emf);
return manager;
}
@Bean
public BeanPostProcessor persistenceTranceslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
让我们回到SpitterRepository接口,它扩展自JpaRepository,而JpaRepository又扩展自Repository标记接口 (虽然是间接的)。因此,SpitterRepository就传递性地扩展了Repository接口,也就是Repository扫描时所要查找的接口。当 Spring Data找到它后,就会创建SpitterRepository的实现类,其 中包含了继承自 JpaRepository、PagingAndSortingRepository和 CrudRepository的18个方法。 很重要的一点在于Repository的实现类是在应用启动的时候生成的, 也就是Spring的应用上下文创建的时候。它并不是在构建时通过代码 生成技术产生的,也不是接口方法调用时才创建的。
11.3.1 定义查询方法
现在,SpitterRepository需要完成的一项功能是根据给定的 username查找Spitter对象。比如,我们将SpitterRepository接 口修改为如下所示的样子:
public interface SpitterRepository extends JpaRepository<Spitter, Long> {
Spitter findByUsername(String username);
}
当创建Repository实现的时候,Spring Data会检查Repository接口的所 有方法,解析方法的名称,并基于被持久化的对象来试图推测方法的 目的。本质上,Spring Data定义了一组小型的领域特定语言(domainspecific language ,DSL),在这里,持久化的细节都是通过 Repository方法的签名来描述的。
Spring Data能够知道这个方法是要查找Spitter的,因为我们使 用Spitter对JpaRepository进行了参数化。方法 名findByUsername确定该方法需要根据username属性相匹配来 查找Spitter,而username是作为参数传递到方法中来的。另外, 因为在方法签名中定义了该方法要返回一个Spitter对象,而不是 一个集合,因此它只会查找一个username属性匹配的Spitter。
findByUsername()方法非常简单,但是Spring Data也能处理更加 有意思的方法名称。Repository方法是由一个动词、一个可选的主题 (Subject)、关键词By以及一个断言所组成。 在findByUsername()这个样例中,动词是find,断言 是Username,主题并没有指定,暗含的主题是Spitter。
作为编写Repository方法名称的样例,我们参照名为readSpitterByFirstname-OrLastname()的方法,看一下方法中的各个部分是如何映射的。图11.1展现了这个方法是如何拆分的。
我们可以看到,这里的动词是read,与之前样例中的find有所差别。 Spring Data允许在方法名中使用四种动词:get、read、find和count。 其中,动词get、read和find是同义的,这三个动词对应的Repository方法都会查询数据并返回对象。而动词count则会返回匹配对象的数量,而不是对象本身。
图11.1 Repository方法的命名遵循一种模式,有助于Spring Data 生成针对数据库的查询
Repository方法的主题是可选的。它的主要目的是让你在命名方法的时候,有更多的灵活性。对于大部分场景来说,主题会被省略掉。readSpittersByFirstnameOrLastname() 与readPuppiesByFirstnameOrLastname()并没有什么差别, 它们与readThose ThingsWeWantByFirstnameOrLastname() 同样没有什么区别。要查询的对象类型是通过如何参数化 JpaRepository接口来确定的,而不是方法名称中的主题。
在省略主题的时候,有一种例外情况。如果主题的名称以Distinct开 头的话,那么在生成查询的时候会确保所返回结果集中不包含重复记录。
断言是方法名称中最为有意思的部分,它指定了限制结果集的属性。 在readByFirstnameOrLastname()这个样例中,会通过 firstname属性或lastname属性的值来限制结果。
在断言中,会有一个或多个限制结果的条件。每个条件必须引用一个属性,并且还可以指定一种比较操作。如果省略比较操作符的话,那么这暗指是一种相等比较操作。
JPA在创建查询时,我们通过在方法名中使用属性名称来表达,比如 findByUserAddressZip ()。框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,详细规则如下(此处假设该方法针对的域对象为 AccountInfo 类型):
- 先判断 userAddressZip (根据 POJO 规范,首字母变为小写,下同)是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
- 从右往左截取第一个大写字母开头的字符串(此处为 Zip),然后检查剩下的字符串是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为 AccountInfo 的一个属性;
- 接着处理剩下部分( AddressZip ),先判断 user 所对应的类型是否有 addressZip 属性,如果有,则表示该方法最终是根据 "AccountInfo.user.addressZip" 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 "AccountInfo.user.address.zip" 的值进行查询。
可能会存在一种特殊情况,比如 AccountInfo 包含一个 user 的属性,也有一个 userAddress 属性,此时会存在混淆。读者可以明确在属性之间加上 "_" 以显式表达意图,比如 "findByUser_AddressZip()" 或者 "findByUserAddress_Zip()"。
在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:
- And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
- Or --- 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
- Between --- 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
- LessThan --- 等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max);
- GreaterThan --- 等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min);
- IsNull --- 等价于 SQL 中的 "is null",比如 findByUsernameIsNull();
- IsNotNull --- 等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
- NotNull --- 与 IsNotNull 等价;
- Like --- 等价于 SQL 中的 "like",比如 findByUsernameLike(String user);
- NotLike --- 等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
- OrderBy --- 等价于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);
- Not --- 等价于 SQL 中的 "! =",比如 findByUsernameNot(String user);
- In --- 等价于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
- NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
11.3.2 声明自定义查询
如果所需的数据无法通过方法名称进行恰当地描述,那么我们可以使用@Query注解,为Spring Data提供要执行的查询。
例如:查询最新的指定数量的Spittle列表,如果用MySQL语言进行查询,sql语句如下:
select s from Spittle s order by s.postedTime desc limit count
但limit是特定于某些数据库(例如:mysql),但HQL是针对所有hibernate支持的数据库,也就是说,Mysql的限制返回记录数是limit,也许其他的数据库则不是limit(例如:Oracle)。
所以在Repository中是这样查询的,使用@Query注解及Pageable:
@Query("select s from Spittle s order by s.postedTime desc")
List<Spittle> findSpittles(Pageable pageable);
在Controller层传入PageRequest:
@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
//@RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max,
@RequestParam(value="count", defaultValue="20") int count) {
//return spittleRepository.findSpittles(max, count);
return spittleRepository.findSpittles(new PageRequest(0, count));
}
清单1 SpitterRepository.java:
package spittr.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import spittr.Spitter;
public interface SpitterRepository extends JpaRepository<Spitter, Long> {
@SuppressWarnings("unchecked")
Spitter save(Spitter spitter);
Spitter findByUsername(String username);
Spitter findById(long id);
@Query("select s from Spitter s")
List<Spitter> findAll();
long count();
}
清单2 SpittleRepository.java:
package spittr.data;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import spittr.Spittle;
public interface SpittleRepository extends JpaRepository<Spittle, Long> {
/**
*
* @param count 返回多少个Spittle对象
* @return 获取最新的Spittle列表
*/
@Query("select s from Spittle s order by s.postedTime desc")
List<Spittle> findSpittles(Pageable pageable);
Spittle findById(long id);
Spittle save(Spittle spittle);
List<Spittle> findBySpitterId(long spitterId);
@Modifying
@Transactional
@Query("delete from Spittle s where s.id = ?1")
void delete(long id);
long count();
}
清单3 SpittleController.java
package spittr.web;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import spittr.Spitter;
import spittr.Spittle;
import spittr.data.SpitterRepository;
import spittr.data.SpittleRepository;
import spittr.form.SpittleForm;
@Controller
@RequestMapping("/spittles")
public class SpittleController {
private static final String MAX_LONG_AS_STRING = "9223372036854775807";
private SpittleRepository spittleRepository;
private SpitterRepository spitterRepository;
@Autowired
public SpittleController(SpittleRepository spittleRepository, SpitterRepository spitterRepository) {
this.spittleRepository = spittleRepository;
this.spitterRepository = spitterRepository;
}
/*
@RequestMapping(method=RequestMethod.GET)
public String spittles(Model model){
//将spittle添加到模型中
model.addAttribute("spittleList", spittleRepository.findSpittles(Long.MAX_VALUE, 20));
return "spittles";
}
*/
//通过 @RequestParam 注解指定所绑定的 URL 参数
@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
//@RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max,
@RequestParam(value="count", defaultValue="20") int count) {
//return spittleRepository.findSpittles(max, count);
return spittleRepository.findSpittles(new PageRequest(0, count));
}
@RequestMapping(value="/show", method=RequestMethod.GET)
public String showSpittle(
@RequestParam("spittle_id") long spittleId,
Model model) {
System.out.println("处理show请求");
model.addAttribute(spittleRepository.findOne(spittleId));
return "spittle";
}
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(
@PathVariable long spittleId,
Model model) {
Spittle spittle = spittleRepository.findOne(spittleId);
if (spittle == null) {
System.out.println("抛出异常");
throw new SpittleNotFoundException();
}
model.addAttribute(spittle);
return "spittle";
}
@RequestMapping(method=RequestMethod.POST)
public String saveSpittle(SpittleForm form, Model model, HttpSession httpSession) {
//spittleRepository.save(new Spittle(null, form.getSpitter(), form.getMessage(), new Date()));
Spitter spitter = null;
if(httpSession.getAttribute("spitter") != null) {
System.out.println("在Spittle页面模型中包含spitter属性");
spitter = (Spitter) httpSession.getAttribute("spitter");
} else {
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
String username = userDetails.getUsername();
System.out.println("页面获取Spring Security登录用户: " + username);
spitter = spitterRepository.findByUsername(username);
}
spittleRepository.save(new Spittle(spitter, form.getMessage(), new Date()));
return "redirect:/spittles";
}
@ExceptionHandler(DuplicateSpittleException.class)
public String handleNotFound() {
return "error/duplicate";
}
}
清单4 SpittlerController.java
package spittr.web;
import java.io.File;
import java.io.IOException;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
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.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import spittr.Spitter;
import spittr.data.SpitterRepository;
import spittr.form.SpitterForm;
@Controller
@RequestMapping("/spitter")
public class SpitterController {
private SpitterRepository spitterRepository;
@Autowired
public SpitterController(SpitterRepository spitterRepository) {
this.spitterRepository = spitterRepository;
}
//处理"/spitter/register"的GET请求
@RequestMapping(value="/register", method=RequestMethod.GET)
public String showRegistrationForm(Model model) {
System.out.println("处理注册get请求");
model.addAttribute(new SpitterForm());
return "registerForm";
}
// @RequestMapping(value="/register", method=RequestMethod.POST)
// public String processRegistration(
// @RequestPart("profilePicture") byte[] profilePicture,
// @Valid Spitter spitter,
// Errors errors) {
// //如果校验出现错误,则重新返回表单
// if (errors.hasErrors()) {
// System.out.println("注册有误,返回到注册页面");
// return "registerForm";
// }
// spitterRepository.save(spitter);
// return "redirect:/spitter/" + spitter.getUsername();
// }
@RequestMapping(value="/register", method=RequestMethod.POST)
public String processRegistration(
RedirectAttributes redirectAttributes,
@Valid SpitterForm spitterForm,
HttpSession httpSession,
Errors errors) throws IllegalStateException, IOException {
System.out.println("处理注册post请求");
if (errors.hasErrors()) {
System.out.println("校验错误");
return "registerForm";
}
Spitter spitter = spitterForm.toSpitter();
System.out.println("保存用户信息");
spitterRepository.save(spitter);
MultipartFile profilePicture = spitterForm.getProfilePicture();
System.out.println("保存图片");
System.out.println("上传图片名字:" + profilePicture.getOriginalFilename());
profilePicture.transferTo(new File(spitter.getUsername() + ".jpg"));
redirectAttributes.addAttribute("username", spitter.getUsername());
redirectAttributes.addFlashAttribute("spitter", spitter);
httpSession.setAttribute("spitter", spitter);
return "redirect:/spitter/{username}";
}
@RequestMapping(value="/{username}", method=RequestMethod.GET)
public String showSpitterProfile(@PathVariable String username, Model model) {
// Spitter spitter = spitterRepository.findByUsername(username);
// model.addAttribute(spitter);
// return "profile";
//如果模型中不包含spitter属性的话,那 么showSpitterProfile()将会从Repository中查找Spitter,并 将其存放到模型中。
if(!model.containsAttribute("spitter")) {
System.out.println("模型中不包含spitter属性");
model.addAttribute(spitterRepository.findByUsername(username));
}
return "profile";
}
@RequestMapping(value="/me", method=RequestMethod.GET)
public String me() {
System.out.println("ME ME ME ME ME ME ME ME ME ME ME");
return "home";
}
}