您的位置:首页 > 编程语言 > Java开发

Spring Data JPA教程, 第二部分: CRUD(翻译)

2013-06-19 07:53 537 查看
我的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

本文版权归作者所有,未经作者同意,严禁转载及用作商业传播,否则将追究法律责任。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: