此模式属于对象关系元数据映射模式目录,这个目录属于企业应用程序体系结构的模式。
目的:
在域和数据映射层之间添加仓储层,以将域对象与数据库访问代码的细节隔离开来,并最小化查询代码的分散和重复。
存储库模式在使用大量域类或大量查询的系统中特别有用。
适用性:
以下情况下适合使用存储库模式:
1. 域对象的数目很大
2. 希望避免重复查询代码
3. 希望将数据库查询代码保留在一个位置
4. 有多个数据源
说明:
具有复杂域模型的系统通常受益于一个层,例如由Data Mapper 提供的层,它将域对象与数据库访问代码的细节隔离开来。在这样的系统中,在映射层上构建另一个抽象层是值得的,查询构造代码被集中在一起。当存在大量域类或大量查询时,这变得更加重要,添加此层有助于使重复的查找逻辑最小化。
存储库在域和数据映射层之间进行调解,其作用类似于内存中的域对象集合客户端对象以声明方式构造查询规范,并将它们提交到存储库以满足需求。
示例代码:
在此示例中,使用Spring Data从Person域对象自动生成存储库。
使用PersonRepository,我们对实体执行CRUD操作,此外,还执行 org.springframework.data.jpa.domain.Specification 的查询。
为了测试这种模式,我在这个例子中使用了H2内存数据库。
第1步:创建Person实体类
@Entity <b>public</b> <b>class</b> Person { @Id @GeneratedValue <b>private</b> Long id; <b>private</b> String name; <b>private</b> String surname; <b>private</b> <b>int</b> age; <b>public</b> Person() { } <font><i>/** * Constructor */</i></font><font> <b>public</b> Person(String name, String surname, <b>int</b> age) { <b>this</b>.name = name; <b>this</b>.surname = surname; <b>this</b>.age = age; } <b>public</b> Long getId() { <b>return</b> id; } <b>public</b> <b>void</b> setId(Long id) { <b>this</b>.id = id; } <b>public</b> String getName() { <b>return</b> name; } <b>public</b> <b>void</b> setName(String name) { <b>this</b>.name = name; } <b>public</b> String getSurname() { <b>return</b> surname; } <b>public</b> <b>void</b> setSurname(String surname) { <b>this</b>.surname = surname; } <b>public</b> <b>int</b> getAge() { <b>return</b> age; } <b>public</b> <b>void</b> setAge(<b>int</b> age) { <b>this</b>.age = age; } @Override <b>public</b> String toString() { <b>return</b> </font><font>"Person [id="</font><font> + id + </font><font>", name="</font><font> + name + </font><font>", surname="</font><font> + surname + </font><font>", age="</font><font> + age + </font><font>"]"</font><font>; } @Override <b>public</b> <b>int</b> hashCode() { <b>final</b> <b>int</b> prime = 31; <b>int</b> result = 1; result = prime * result + age; result = prime * result + (id == <b>null</b> ? 0 : id.hashCode()); result = prime * result + (name == <b>null</b> ? 0 : name.hashCode()); result = prime * result + (surname == <b>null</b> ? 0 : surname.hashCode()); <b>return</b> result; } @Override <b>public</b> <b>boolean</b> equals(Object obj) { <b>if</b> (<b>this</b> == obj) { <b>return</b> <b>true</b>; } <b>if</b> (obj == <b>null</b>) { <b>return</b> false; } <b>if</b> (getClass() != obj.getClass()) { <b>return</b> false; } Person other = (Person) obj; <b>if</b> (age != other.age) { <b>return</b> false; } <b>if</b> (id == <b>null</b>) { <b>if</b> (other.id != <b>null</b>) { <b>return</b> false; } } <b>else</b> <b>if</b> (!id.equals(other.id)) { <b>return</b> false; } <b>if</b> (name == <b>null</b>) { <b>if</b> (other.name != <b>null</b>) { <b>return</b> false; } } <b>else</b> <b>if</b> (!name.equals(other.name)) { <b>return</b> false; } <b>if</b> (surname == <b>null</b>) { <b>if</b> (other.surname != <b>null</b>) { <b>return</b> false; } } <b>else</b> <b>if</b> (!surname.equals(other.surname)) { <b>return</b> false; } <b>return</b> <b>true</b>; } } </font>
第2步:这是Spring的基于注释的配置示例
@EnableJpaRepositories <b>public</b> <b>class</b> AppConfig { <b>private</b> <b>static</b> <b>final</b> Logger LOGGER = LoggerFactory.getLogger(AppConfig.<b>class</b>); <font><i>/** * Creation of H2 db * * @return A new Instance of DataSource */</i></font><font> @Bean(destroyMethod = </font><font>"close"</font><font>) <b>public</b> DataSource dataSource() { BasicDataSource basicDataSource = <b>new</b> BasicDataSource(); basicDataSource.setDriverClassName(</font><font>"org.h2.Driver"</font><font>); basicDataSource.setUrl(</font><font>"jdbc:h2:~/databases/person"</font><font>); basicDataSource.setUsername(</font><font>"sa"</font><font>); basicDataSource.setPassword(</font><font>"sa"</font><font>); <b>return</b> (DataSource) basicDataSource; } </font><font><i>/** * Factory to create a especific instance of Entity Manager */</i></font><font> @Bean <b>public</b> LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean entityManager = <b>new</b> LocalContainerEntityManagerFactoryBean(); entityManager.setDataSource(dataSource()); entityManager.setPackagesToScan(</font><font>"com.iluwatar"</font><font>); entityManager.setPersistenceProvider(<b>new</b> HibernatePersistenceProvider()); entityManager.setJpaProperties(jpaProperties()); <b>return</b> entityManager; } </font><font><i>/** * Properties for Jpa */</i></font><font> <b>private</b> <b>static</b> Properties jpaProperties() { Properties properties = <b>new</b> Properties(); properties.setProperty(</font><font>"hibernate.dialect"</font><font>, </font><font>"org.hibernate.dialect.H2Dialect"</font><font>); properties.setProperty(</font><font>"hibernate.hbm2ddl.auto"</font><font>, </font><font>"create-drop"</font><font>); <b>return</b> properties; } </font><font><i>/** * Get transaction manager */</i></font><font> @Bean <b>public</b> JpaTransactionManager transactionManager() throws SQLException { JpaTransactionManager transactionManager = <b>new</b> JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); <b>return</b> transactionManager; } </font><font><i>/** * Program entry point * * @param args * command line args */</i></font><font> <b>public</b> <b>static</b> <b>void</b> main(String[] args) { AnnotationConfigApplicationContext context = <b>new</b> AnnotationConfigApplicationContext( AppConfig.<b>class</b>); PersonRepository repository = context.getBean(PersonRepository.<b>class</b>); Person peter = <b>new</b> Person(</font><font>"Peter"</font><font>, </font><font>"Sagan"</font><font>, 17); Person nasta = <b>new</b> Person(</font><font>"Nasta"</font><font>, </font><font>"Kuzminova"</font><font>, 25); Person john = <b>new</b> Person(</font><font>"John"</font><font>, </font><font>"lawrence"</font><font>, 35); Person terry = <b>new</b> Person(</font><font>"Terry"</font><font>, </font><font>"Law"</font><font>, 36); </font><font><i>// Add new Person records</i></font><font> repository.save(peter); repository.save(nasta); repository.save(john); repository.save(terry); </font><font><i>// Count Person records</i></font><font> LOGGER.info(</font><font>"Count Person records: {}"</font><font>, repository.count()); </font><font><i>// Print all records</i></font><font> List<Person> persons = (List<Person>) repository.findAll(); <b>for</b> (Person person : persons) { LOGGER.info(person.toString()); } </font><font><i>// Update Person</i></font><font> nasta.setName(</font><font>"Barbora"</font><font>); nasta.setSurname(</font><font>"Spotakova"</font><font>); repository.save(nasta); LOGGER.info(</font><font>"Find by id 2: {}"</font><font>, repository.findOne(2L)); </font><font><i>// Remove record from Person</i></font><font> repository.delete(2L); </font><font><i>// count records</i></font><font> LOGGER.info(</font><font>"Count Person records: {}"</font><font>, repository.count()); </font><font><i>// find by name</i></font><font> Person p = repository.findOne(<b>new</b> PersonSpecifications.NameEqualSpec(</font><font>"John"</font><font>)); LOGGER.info(</font><font>"Find by John is {}"</font><font>, p); </font><font><i>// find by age</i></font><font> persons = repository.findAll(<b>new</b> PersonSpecifications.AgeBetweenSpec(20, 40)); LOGGER.info(</font><font>"Find Person with age between 20,40: "</font><font>); <b>for</b> (Person person : persons) { LOGGER.info(person.toString()); } context.close(); } } </font>
第3步:Helper类,包括各种规范作为SQL查询条件的抽象
<b>public</b> <b>class</b> PersonSpecifications { <font><i>/** * Specifications stating the Between (From - To) Age Specification */</i></font><font> <b>public</b> <b>static</b> <b>class</b> AgeBetweenSpec implements Specification<Person> { <b>private</b> <b>int</b> from; <b>private</b> <b>int</b> to; <b>public</b> AgeBetweenSpec(<b>int</b> from, <b>int</b> to) { <b>this</b>.from = from; <b>this</b>.to = to; } @Override <b>public</b> Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) { <b>return</b> cb.between(root.get(</font><font>"age"</font><font>), from, to); } } </font><font><i>/** * Name specification * */</i></font><font> <b>public</b> <b>static</b> <b>class</b> NameEqualSpec implements Specification<Person> { <b>public</b> String name; <b>public</b> NameEqualSpec(String name) { <b>this</b>.name = name; } </font><font><i>/** * Get predicate */</i></font><font> <b>public</b> Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) { <b>return</b> cb.equal(root.get(</font><font>"name"</font><font>), <b>this</b>.name); } } } </font>
第4步:使用Spring Data JPA定义Person存储库
@Repository <b>public</b> <b>interface</b> PersonRepository <b>extends</b> CrudRepository<Person, Long>, JpaSpecificationExecutor<Person> { Person findByName(String name); }
第5步:使用Client(main方法)测试这种模式
<b>public</b> <b>class</b> Client { <b>private</b> <b>static</b> <b>final</b> Logger LOGGER = LoggerFactory.getLogger(App.<b>class</b>); <font><i>/** * Program entry point * * @param args * command line args */</i></font><font> <b>public</b> <b>static</b> <b>void</b> main(String[] args) { ClassPathXmlApplicationContext context = <b>new</b> ClassPathXmlApplicationContext( </font><font>"applicationContext.xml"</font><font>); PersonRepository repository = context.getBean(PersonRepository.<b>class</b>); Person peter = <b>new</b> Person(</font><font>"Peter"</font><font>, </font><font>"Sagan"</font><font>, 17); Person nasta = <b>new</b> Person(</font><font>"Nasta"</font><font>, </font><font>"Kuzminova"</font><font>, 25); Person john = <b>new</b> Person(</font><font>"John"</font><font>, </font><font>"lawrence"</font><font>, 35); Person terry = <b>new</b> Person(</font><font>"Terry"</font><font>, </font><font>"Law"</font><font>, 36); </font><font><i>// Add new Person records</i></font><font> repository.save(peter); repository.save(nasta); repository.save(john); repository.save(terry); </font><font><i>// Count Person records</i></font><font> LOGGER.info(</font><font>"Count Person records: {}"</font><font>, repository.count()); </font><font><i>// Print all records</i></font><font> List<Person> persons = (List<Person>) repository.findAll(); <b>for</b> (Person person : persons) { LOGGER.info(person.toString()); } </font><font><i>// Update Person</i></font><font> nasta.setName(</font><font>"Barbora"</font><font>); nasta.setSurname(</font><font>"Spotakova"</font><font>); repository.save(nasta); LOGGER.info(</font><font>"Find by id 2: {}"</font><font>, repository.findOne(2L)); </font><font><i>// Remove record from Person</i></font><font> repository.delete(2L); </font><font><i>// count records</i></font><font> LOGGER.info(</font><font>"Count Person records: {}"</font><font>, repository.count()); </font><font><i>// find by name</i></font><font> Person p = repository.findOne(<b>new</b> PersonSpecifications.NameEqualSpec(</font><font>"John"</font><font>)); LOGGER.info(</font><font>"Find by John is {}"</font><font>, p); </font><font><i>// find by age</i></font><font> persons = repository.findAll(<b>new</b> PersonSpecifications.AgeBetweenSpec(20, 40)); LOGGER.info(</font><font>"Find Person with age between 20,40: "</font><font>); <b>for</b> (Person person : persons) { LOGGER.info(person.toString()); } repository.deleteAll(); context.close(); } } </font>