存储库模式(Repository Pattern)

19-03-18 jdon


此模式属于对象关系元数据映射模式目录,这个目录属于企业应用程序体系结构的模式。
目的:
在域和数据映射层之间添加仓储层,以将域对象与数据库访问代码的细节隔离开来,并最小化查询代码的分散和重复。
存储库模式在使用大量域类或大量查询的系统中特别有用。
适用性:
以下情况下适合使用存储库模式:
1. 域对象的数目很大
2. 希望避免重复查询代码
3. 希望将数据库查询代码保留在一个位置
4. 有多个数据源
说明:
具有复杂域模型的系统通常受益于一个层,例如由Data Mapper 提供的层,它将域对象与数据库访问代码的细节隔离开来。在这样的系统中,在映射层上构建另一个抽象层是值得的,查询构造代码被集中在一起。当存在大量域类或大量查询时,这变得更加重要,添加此层有助于使重复的查找逻辑最小化。
存储库在域和数据映射层之间进行调解,其作用类似于内存中的域对象集合客户端对象以声明方式构造查询规范,并将它们提交到存储库以满足需求。



示例代码:
在此示例中,使用Spring Data从Person域对象自动生成存储库。
使用PersonRepository,我们对实体执行CRUD操作,此外,还执行org.springframework.data.jpa.domain.Specification的查询。
为了测试这种模式,我在这个例子中使用了H2内存数据库。
第1步:创建Person实体类

@Entity
public class Person {

  @Id
  @GeneratedValue
  private Long id;
  private String name;
  private String surname;

  private int age;

  public Person() {
  }

  /**
   * Constructor
   */
  public Person(String name, String surname, int age) {
    this.name = name;
    this.surname = surname;
    this.age = age;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getSurname() {
    return surname;
  }

  public void setSurname(String surname) {
    this.surname = surname;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", name=" + name + ", surname=" + surname + ", age=" + age + "]";
  }

  @Override
  public int hashCode() {

    final int prime = 31;
    int result = 1;
    result = prime * result + age;
    result = prime * result + (id == null ? 0 : id.hashCode());
    result = prime * result + (name == null ? 0 : name.hashCode());
    result = prime * result + (surname == null ? 0 : surname.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    Person other = (Person) obj;
    if (age != other.age) {
      return false;
    }
    if (id == null) {
      if (other.id != null) {
        return false;
      }
    } else if (!id.equals(other.id)) {
      return false;
    }
    if (name == null) {
      if (other.name != null) {
        return false;
      }
    } else if (!name.equals(other.name)) {
      return false;
    }
    if (surname == null) {
      if (other.surname != null) {
        return false;
      }
    } else if (!surname.equals(other.surname)) {
      return false;
    }
    return true;
  }

}


第2步:这是Spring的基于注释的配置示例

@EnableJpaRepositories
public class AppConfig {

  private static final Logger LOGGER = LoggerFactory.getLogger(AppConfig.class);

  /**
   * Creation of H2 db
   * 
   * @return A new Instance of DataSource
   */
  @Bean(destroyMethod = "close")
  public DataSource dataSource() {
    BasicDataSource basicDataSource = new BasicDataSource();
    basicDataSource.setDriverClassName("org.h2.Driver");
    basicDataSource.setUrl("jdbc:h2:~/databases/person");
    basicDataSource.setUsername("sa");
    basicDataSource.setPassword("sa");
    return (DataSource) basicDataSource;
  }

  /**
   * Factory to create a especific instance of Entity Manager
   */
  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
    entityManager.setDataSource(dataSource());
    entityManager.setPackagesToScan("com.iluwatar");
    entityManager.setPersistenceProvider(new HibernatePersistenceProvider());
    entityManager.setJpaProperties(jpaProperties());

    return entityManager;
  }

  /**
   * Properties for Jpa
   */
  private static Properties jpaProperties() {
    Properties properties = new Properties();
    properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
    properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
    return properties;
  }

  /**
   * Get transaction manager
   */
  @Bean
  public JpaTransactionManager transactionManager() throws SQLException {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
    return transactionManager;
  }

  /**
   * Program entry point
   * 
   * @param args
   *          command line args
   */
  public static void main(String[] args) {

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
        AppConfig.class);
    PersonRepository repository = context.getBean(PersonRepository.class);

    Person peter = new Person("Peter", "Sagan", 17);
    Person nasta = new Person("Nasta", "Kuzminova", 25);
    Person john = new Person("John", "lawrence", 35);
    Person terry = new Person("Terry", "Law", 36);

    // Add new Person records
    repository.save(peter);
    repository.save(nasta);
    repository.save(john);
    repository.save(terry);

    // Count Person records
    LOGGER.info("Count Person records: {}", repository.count());

    // Print all records
    List<Person> persons = (List<Person>) repository.findAll();
    for (Person person : persons) {
      LOGGER.info(person.toString());
    }

    // Update Person
    nasta.setName("Barbora");
    nasta.setSurname("Spotakova");
    repository.save(nasta);

    LOGGER.info("Find by id 2: {}", repository.findOne(2L));

    // Remove record from Person
    repository.delete(2L);

    // count records
    LOGGER.info("Count Person records: {}", repository.count());

    // find by name
    Person p = repository.findOne(new PersonSpecifications.NameEqualSpec("John"));
    LOGGER.info("Find by John is {}", p);

    // find by age
    persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40));

    LOGGER.info("Find Person with age between 20,40: ");
    for (Person person : persons) {
      LOGGER.info(person.toString());
    }

    context.close();

  }

}


第3步:Helper类,包括各种规范作为SQL查询条件的抽象

public class PersonSpecifications {

  /**
   * Specifications stating the Between (From - To) Age Specification
   */
  public static class AgeBetweenSpec implements Specification<Person> {

    private int from;

    private int to;

    public AgeBetweenSpec(int from, int to) {
      this.from = from;
      this.to = to;
    }

    @Override
    public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

      return cb.between(root.get("age"), from, to);

    }

  }

  /**
   * Name specification
   *
   */
  public static class NameEqualSpec implements Specification<Person> {

    public String name;

    public NameEqualSpec(String name) {
      this.name = name;
    }

    /**
     * Get predicate
     */
    public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

      return cb.equal(root.get("name"), this.name);

    }
  }

}


第4步:使用Spring Data JPA定义Person存储库

@Repository
public interface PersonRepository
    extends CrudRepository<Person, Long>, JpaSpecificationExecutor<Person> {

  Person findByName(String name);
}


第5步:使用Client(main方法)测试这种模式

public class Client {

  private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

  /**
   * Program entry point
   * 
   * @param args
   *          command line args
   */
  public static void main(String[] args) {

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");
    PersonRepository repository = context.getBean(PersonRepository.class);

    Person peter = new Person("Peter", "Sagan", 17);
    Person nasta = new Person("Nasta", "Kuzminova", 25);
    Person john = new Person("John", "lawrence", 35);
    Person terry = new Person("Terry", "Law", 36);

    // Add new Person records
    repository.save(peter);
    repository.save(nasta);
    repository.save(john);
    repository.save(terry);

    // Count Person records
    LOGGER.info("Count Person records: {}", repository.count());

    // Print all records
    List<Person> persons = (List<Person>) repository.findAll();
    for (Person person : persons) {
      LOGGER.info(person.toString());
    }

    // Update Person
    nasta.setName("Barbora");
    nasta.setSurname("Spotakova");
    repository.save(nasta);

    LOGGER.info("Find by id 2: {}", repository.findOne(2L));

    // Remove record from Person
    repository.delete(2L);

    // count records
    LOGGER.info("Count Person records: {}", repository.count());

    // find by name
    Person p = repository.findOne(new PersonSpecifications.NameEqualSpec("John"));
    LOGGER.info("Find by John is {}", p);

    // find by age
    persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40));

    LOGGER.info("Find Person with age between 20,40: ");
    for (Person person : persons) {
      LOGGER.info(person.toString());
    }

    repository.deleteAll();
    
    context.close();

  }
}