儲存庫模式(Repository Pattern)
此模式屬於物件關係元資料對映模式目錄,這個目錄屬於企業應用程式體系結構的模式。
目的:
在域和資料對映層之間新增倉儲層,以將域物件與資料庫訪問程式碼的細節隔離開來,並最小化查詢程式碼的分散和重複。
儲存庫模式在使用大量域類或大量查詢的系統中特別有用。
適用性:
以下情況下適合使用儲存庫模式:
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>