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先通过案例实践其怎么用吧. 用之前得明 ...
随机推荐
- Asp.Net MVC Views页面不包含“GetEnumerator”的公共定义
“/”应用程序中的服务器错误. 编译错误 说明: 在编译向该请求提供服务所需资源的过程中出现错误.请检查下列特定错误详细信息并适当地修改源代码. 编译器错误消息: CS1579: “Web.Model ...
- Relativelayout属性
// 相对于给定ID控件 android:layout_above 将该控件的底部置于给定ID的控件之上; android:layout_below 将该控件的底部置于给定ID的控件之下; andro ...
- 自学了三天的SeaJs学习,解决了前端的一些问题,与小伙伴们一起分享一下!
我为什么学习SeaJs? [第一]:为了解决项目中资源文件版本号的问题,以及打包压缩合并等问题. [第二]:好奇心和求知欲.[我发现很多知名网站也都在使用(qq空间, msn, 淘宝等等),而且 Se ...
- Sublime-text markdown with Vim mode and auto preview
说明 最近看到markdown相关的东西,被其书写方式吸引,其实以前就在找这种类似的工具,但是也没找到,由于习惯了Vim,可Vim不支持markdown预览,这点可能不是很好,于是找到Sublime- ...
- H.264中NAL、Slice与frame意思及相互关系
H.264中NAL.Slice与frame意思及相互关系 NAL nal_unit_type中的1(非IDR图像的编码条带).2(编码条带数据分割块A).3(编码条带数据分割块B).4(编码条带数据分 ...
- 系统性能监控之vmstat和iostat命令
这篇文章主要介绍一些Linux性能检测相关的命令. vmstat和iostat的两个命令可以运行在主流的Linux/Unix操作系统上. 如果vmstat和iostat命令不能再你的电脑上运行,请安装 ...
- DB2之隔离级别和锁的论述
在DB2数据库中, 是通过行级锁和表级锁协调作用来提供较好的并发性, 同时保证数据库中数据的安全. 在DB2中缺省情况下使用行级锁(当然需要IS/IX锁配合),只有当出现锁资源不足, 或者是用命令指定 ...
- 一天一个Java基础——数组
一天一个变成了几天一个,最近接受的新东西太多.太快,有好多需要blog的但没有时间,这些基础知识应该是要深挖并好好研究的,不应该每次都草草了事,只看个皮毛. 数组: JVM将数组存储在一个称为堆(he ...
- HTML5和CSS3的学习视频
用Windows8和IE10开发HTML5网页视频教程专辑(Build New World) http://dreamdesign.csrjgzs.com/Article/ShowArticle.as ...
- JS实现连接方式的菜单
<html> <head><meta http-equiv="Content-Language" content="zh-cn"& ...