我的Spring Data Jpa教程的第一部分描述了,如何配置Spring Data JPA,本博文進一步描述怎樣使用Spring Data JPA創建一個簡單的CRUD應用。該應用要求如下:
- person 必須有 first name 和 last name. 這兩者是強制的.
- 能夠列出所有persons.
- 能夠添加新的persons.
- 能夠編輯已存在的persons的信息.
- 能夠刪除persons.
現在我已經描述了創建的應用的要求,現在開始工作並實現它。
所需步驟
CRUD應用的實現可以分割成如下步驟:
- 實現Person 模型對象
- 為Person 對象創建repository
- 使用創建的repository
下面詳細解釋每一步驟.
實現模型對象
Person 類的實現是相當簡單的,不過有幾個問題我需要指出:
- builder用於創建Person類的新實例. 這似乎是國度設計,不過本人喜歡這種方式,其原因有二:首先,它比telescopic constructor pattern代碼更易於閱讀. 其次,它確保你不能在它們的構造期間創建一個不一致狀態的對象(這是通常的JavaBeans 模式 不能保證的).
- 改變存儲在Person對象里面的信息的唯一方式是調用 update()方法,我傾向盡可能的向model對象放入很多邏輯,這種方式使服務層不至於充斥領域邏輯,並且你不會以 anemic domain model結束(譯者注:請參考貧血型與富血型模型).
我的 Person 類的源碼如下:
import org.apache.commons.lang.builder.ToStringBuilder; import javax.persistence.*; /** * An entity class which contains the information of a single person. * @author Petri Kainulainen */ @Entity @Table(name = "persons") public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name = "creation_time", nullable = false) private Date creationTime; @Column(name = "first_name", nullable = false) private String firstName; @Column(name = "last_name", nullable = false) private String lastName; @Column(name = "modification_time", nullable = false) private Date modificationTime; @Version private long version = 0; public Long getId() { return id; } /** * Gets a builder which is used to create Person objects. * @param firstName The first name of the created user. * @param lastName The last name of the created user. * @return A new Builder instance. */ public static Builder getBuilder(String firstName, String lastName) { return new Builder(firstName, lastName); } public Date getCreationTime() { return creationTime; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } /** * Gets the full name of the person. * @return The full name of the person. */ @Transient public String getName() { StringBuilder name = new StringBuilder(); name.append(firstName); name.append(" "); name.append(lastName); return name.toString(); } public Date getModificationTime() { return modificationTime; } public long getVersion() { return version; } public void update(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @PreUpdate public void preUpdate() { modificationTime = new Date(); } @PrePersist public void prePersist() { Date now = new Date(); creationTime = now; modificationTime = now; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } /** * A Builder class used to create new Person objects. */ public static class Builder { Person built; /** * Creates a new Builder instance. * @param firstName The first name of the created Person object. * @param lastName The last name of the created Person object. */ Builder(String firstName, String lastName) { built = new Person(); built.firstName = firstName; built.lastName = lastName; } /** * Builds the new Person object. * @return The created Person object. */ public Person build() { return built; } } /** * This setter method should only be used by unit tests. * @param id */ protected void setId(Long id) { this.id = id; } }
創建Repository
實現一個為Person模型對象提供CRUD操作的repository是相當簡略的,你所要做的就是常見一個繼承自JpaRepository接口的接口。 JpaRepository接口是向Repository接口的JPA規范擴展,給你訪問如下方法,它們用於實現CRUD應用.
- delete(T entity) which deletes the entity given as a parameter.
- findAll() which returns a list of entities.
- findOne(ID id) which returns the entity using the id given a parameter as a search criteria.
- save(T entity) which saves the entity given as a parameter.
我的PersonRepository 接口源碼如下:
import org.springframework.data.jpa.repository.JpaRepository; /** * Specifies methods used to obtain and modify person related information * which is stored in the database. * @author Petri Kainulainen */ public interface PersonRepository extends JpaRepository<Person, Long> { }
使用創建的Repository
你現在已經創建model對象和與數據庫交互需要的repository,下一步是實現服務類,它是控制器和實現repository之間的中介,服務層的結構下一步描述
PersonDTO是一個簡單的DTO對象,在我的示例應用中用於form對象,它的源碼如下
import org.apache.commons.lang.builder.ToStringBuilder; import org.hibernate.validator.constraints.NotEmpty; /** * A DTO object which is used as a form object * in create person and edit person forms. * @author Petri Kainulainen */ public class PersonDTO { private Long id; @NotEmpty private String firstName; @NotEmpty private String lastName; public PersonDTO() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } }
PersonService接口聲明實際實現提供的方法,它的源碼如下:
/** * Declares methods used to obtain and modify person information. * @author Petri Kainulainen */ public interface PersonService { /** * Creates a new person. * @param created The information of the created person. * @return The created person. */ public Person create(PersonDTO created); /** * Deletes a person. * @param personId The id of the deleted person. * @return The deleted person. * @throws PersonNotFoundException if no person is found with the given id. */ public Person delete(Long personId) throws PersonNotFoundException; /** * Finds all persons. * @return A list of persons. */ public List<Person> findAll(); /** * Finds person by id. * @param id The id of the wanted person. * @return The found person. If no person is found, this method returns null. */ public Person findById(Long id); /** * Updates the information of a person. * @param updated The information of the updated person. * @return The updated person. * @throws PersonNotFoundException if no person is found with given id. */ public Person update(PersonDTO updated) throws PersonNotFoundException; }
RepositoryPersonService類實現PersonService接口,其源碼如下:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; /** * This implementation of the PersonService interface communicates with * the database by using a Spring Data JPA repository. * @author Petri Kainulainen */ @Service public class RepositoryPersonService implements PersonService { private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryPersonService.class); @Resource private PersonRepository personRepository; @Transactional @Override public Person create(PersonDTO created) { LOGGER.debug("Creating a new person with information: " + created); Person person = Person.getBuilder(created.getFirstName(), created.getLastName()).build(); return personRepository.save(person); } @Transactional(rollbackFor = PersonNotFoundException.class) @Override public Person delete(Long personId) throws PersonNotFoundException { LOGGER.debug("Deleting person with id: " + personId); Person deleted = personRepository.findOne(personId); if (deleted == null) { LOGGER.debug("No person found with id: " + personId); throw new PersonNotFoundException(); } personRepository.delete(deleted); return deleted; } @Transactional(readOnly = true) @Override public List<Person> findAll() { LOGGER.debug("Finding all persons"); return personRepository.findAll(); } @Transactional(readOnly = true) @Override public Person findById(Long id) { LOGGER.debug("Finding person by id: " + id); return personRepository.findOne(id); } @Transactional(rollbackFor = PersonNotFoundException.class) @Override public Person update(PersonDTO updated) throws PersonNotFoundException { LOGGER.debug("Updating person with information: " + updated); Person person = personRepository.findOne(updated.getId()); if (person == null) { LOGGER.debug("No person found with id: " + updated.getId()); throw new PersonNotFoundException(); } person.update(updated.getFirstName(), updated.getLastName()); return person; } /** * This setter method should be used only by unit tests. * @param personRepository */ protected void setPersonRepository(PersonRepository personRepository) { this.personRepository = personRepository; } }
本步驟的最后部分是為RepositoryPersonService類編寫單元測試,這些單元測試的源碼如下:
import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import static junit.framework.Assert.assertEquals; import static org.mockito.Mockito.*; public class RepositoryPersonServiceTest { private static final Long PERSON_ID = Long.valueOf(5); private static final String FIRST_NAME = "Foo"; private static final String FIRST_NAME_UPDATED = "FooUpdated"; private static final String LAST_NAME = "Bar"; private static final String LAST_NAME_UPDATED = "BarUpdated"; private RepositoryPersonService personService; private PersonRepository personRepositoryMock; @Before public void setUp() { personService = new RepositoryPersonService(); personRepositoryMock = mock(PersonRepository.class); personService.setPersonRepository(personRepositoryMock); } @Test public void create() { PersonDTO created = PersonTestUtil.createDTO(null, FIRST_NAME, LAST_NAME); Person persisted = PersonTestUtil.createModelObject(PERSON_ID, FIRST_NAME, LAST_NAME); when(personRepositoryMock.save(any(Person.class))).thenReturn(persisted); Person returned = personService.create(created); ArgumentCaptor<Person> personArgument = ArgumentCaptor.forClass(Person.class); verify(personRepositoryMock, times(1)).save(personArgument.capture()); verifyNoMoreInteractions(personRepositoryMock); assertPerson(created, personArgument.getValue()); assertEquals(persisted, returned); } @Test public void delete() throws PersonNotFoundException { Person deleted = PersonTestUtil.createModelObject(PERSON_ID, FIRST_NAME, LAST_NAME); when(personRepositoryMock.findOne(PERSON_ID)).thenReturn(deleted); Person returned = personService.delete(PERSON_ID); verify(personRepositoryMock, times(1)).findOne(PERSON_ID); verify(personRepositoryMock, times(1)).delete(deleted); verifyNoMoreInteractions(personRepositoryMock); assertEquals(deleted, returned); } @Test(expected = PersonNotFoundException.class) public void deleteWhenPersonIsNotFound() throws PersonNotFoundException { when(personRepositoryMock.findOne(PERSON_ID)).thenReturn(null); personService.delete(PERSON_ID); verify(personRepositoryMock, times(1)).findOne(PERSON_ID); verifyNoMoreInteractions(personRepositoryMock); } @Test public void findAll() { List<Person> persons = new ArrayList<Person>(); when(personRepositoryMock.findAll()).thenReturn(persons); List<Person> returned = personService.findAll(); verify(personRepositoryMock, times(1)).findAll(); verifyNoMoreInteractions(personRepositoryMock); assertEquals(persons, returned); } @Test public void findById() { Person person = PersonTestUtil.createModelObject(PERSON_ID, FIRST_NAME, LAST_NAME); when(personRepositoryMock.findOne(PERSON_ID)).thenReturn(person); Person returned = personService.findById(PERSON_ID); verify(personRepositoryMock, times(1)).findOne(PERSON_ID); verifyNoMoreInteractions(personRepositoryMock); assertEquals(person, returned); } @Test public void update() throws PersonNotFoundException { PersonDTO updated = PersonTestUtil.createDTO(PERSON_ID, FIRST_NAME_UPDATED, LAST_NAME_UPDATED); Person person = PersonTestUtil.createModelObject(PERSON_ID, FIRST_NAME, LAST_NAME); when(personRepositoryMock.findOne(updated.getId())).thenReturn(person); Person returned = personService.update(updated); verify(personRepositoryMock, times(1)).findOne(updated.getId()); verifyNoMoreInteractions(personRepositoryMock); assertPerson(updated, returned); } @Test(expected = PersonNotFoundException.class) public void updateWhenPersonIsNotFound() throws PersonNotFoundException { PersonDTO updated = PersonTestUtil.createDTO(PERSON_ID, FIRST_NAME_UPDATED, LAST_NAME_UPDATED); when(personRepositoryMock.findOne(updated.getId())).thenReturn(null); personService.update(updated); verify(personRepositoryMock, times(1)).findOne(updated.getId()); verifyNoMoreInteractions(personRepositoryMock); } private void assertPerson(PersonDTO expected, Person actual) { assertEquals(expected.getId(), actual.getId()); assertEquals(expected.getFirstName(), actual.getFirstName()); assertEquals(expected.getLastName(), expected.getLastName()); } }
下一步?
本人已經向你演示了如何用Spring Data JPA實現一個簡單的CRUD應用,如果你對查看我的全部實踐的功能示例感興趣,你可以從Github獲取,我的Spring Data JPA教程的第三部分描述如何用query方法創建自定義查詢
---------------------------------------------------------------------------
本系列Spring Data JPA 教程翻譯系本人原創
作者 博客園 刺蝟的溫馴
本文鏈接http://www.cnblogs.com/chenying99/archive/2013/06/19/3143527.html
本文版權歸作者所有,未經作者同意,嚴禁轉載及用作商業傳播,否則將追究法律責任。