SpringInAction笔记(十一)—— 使用对象-关系映射持久化数据(下)

借助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";  
	}
}

 

 

 

 

 

 





    

    

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值