Spring Data JPA教程, 第二部分: CRUD

我的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

本文版权归作者所有,未经作者同意,严禁转载及用作商业传播,否则将追究法律责任。

你可能感兴趣的:(spring)