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 Data JPA教程, 第二部分: CRUD(翻译)的更多相关文章
- Spring Data JPA 教程(翻译)
写那些数据挖掘之类的博文 写的比较累了,现在翻译一下关于spring data jpa的文章,觉得轻松多了. 翻译正文: 你有木有注意到,使用Java持久化的API的数据访问代码包含了很多不必要的模式 ...
- Spring Data JPA教程, 第三部分: Custom Queries with Query Methods(翻译)
在本人的Spring Data JPA教程的第二部分描述了如何用Spring Data JPA创建一个简单的CRUD应用,本博文将描述如何在Spring Data JPA中使用query方法创建自定义 ...
- Spring Data JPA教程,第一部分: Configuration(翻译)
Spring Data JPA项目旨在简化基于仓库的JPA的创建并减少与数据库交互的所需的代码量.本人在自己的工作和个人爱好项目中已经使用一段时间,它却是是事情如此简单和清洗,现在是时候与你分享我的知 ...
- Spring Data JPA教程, 第八部分:Adding Functionality to a Repository (未翻译)
The previous part of my tutorial described how you can paginate query results with Spring Data JPA. ...
- Spring Data JPA教程, 第七部分: Pagination(未翻译)
The previous part of my Spring Data JPA tutorialdescribed how you can sort query results with Spring ...
- Spring Data JPA教程, 第六部分: Sorting(未翻译)
The fifth part of my Spring Data JPA tutorialdescribed how you can create advanced queries with Spri ...
- Spring Data JPA教程, 第五部分: Querydsl(未翻译)
The fourth part of my Spring Data JPA tutorialdescribed how you can implement more advanced queries ...
- Spring Data JPA教程, 第四部分: JPA Criteria Queries(未翻译)
The third part of my Spring Data JPA tutorialdescribed how you can create custom queries by using qu ...
- Spring Data JPA应用之常规CRUD操作初体验
基于对于一个陌生的技术框架,先使用后研究其实现的原则(大部分本人如此,就如小朋友学习骑自行车不会先研究自行车是怎么动起来的而是先骑会了),对于Spring JPA先通过案例实践其怎么用吧. 用之前得明 ...
随机推荐
- UVa 253 Cube paiting
题意:输入两个骰子,判断是否等价 因为每一个面可以作顶面,共6*4种情况,枚举就可以了 #include<iostream> #include<cstdio> #include ...
- Ajax、Comet与Websocket
从 http 协议说起 1996年IETF HTTP工作组发布了HTTP协议的1.0版本 ,到现在普遍使用的版本1.1,HTTP协议经历了17 年的发展.这种分布式.无状态.基于TCP的请求/响应式 ...
- hdu 4690 EBCDIC
还有什么好说的呢?打表题= = #include<cstdio> #include<cstring> #include<algorithm> #include< ...
- UVA 11090 Going in Cycle!!(二分答案+判负环)
在加权有向图中求平均权值最小的回路. 一上手没有思路,看到“回路”,第一想法就是找连通分量,可又是加权图,没什么好思路,那就转换题意:由求回路权值->判负环,求最小值->常用二分答案. 二 ...
- android中ViewHolder通用简洁写法
public class ViewHolder { // I added a generic return type to reduce the casting noise in client ...
- SPFile的使用
转:http://blog.csdn.net/pclzr/article/details/7591741 SPFile对应于SharePoint对象模型中的文件,它的使用方法与SPFolder类大致相 ...
- CMake实践(1)
简介: 目录结构t1/main.cpp; t1/CMakeLists.txt 说明: main.cpp: #include <stdio.h> int main(){ printf( ...
- Java 小片段
public static String listToString(List<String> stringList){ if (stringList==null) { return nul ...
- JS:实用功能
ylbtech-jQuery:函数-导航 添加样式(addClass).移除样式(removeClass) 轮替函数(toggle()) 选项拼加 全选 网页刷点器 jQuery:3.1,添加样式(a ...
- MyBatis批量删除 多态sql,构建in语句
<!--==========================删除==================================== --> <delete id=&quo ...