我的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
本文版权归作者所有,未经作者同意,严禁转载及用作商业传播,否则将追究法律责任。