开始使用 Spring Data JPA
来源:互联网 发布:重庆大学网络教育专科 编辑:程序博客网 时间:2024/05/29 18:07
域(The domain)
为了保持简单,我从最简单常用的域开始:客户(Customer)和帐号(Account)
01
@Entity
02
public
class
Customer {
03
04
@Id
05
@GeneratedValue
(strategy = GenerationType.AUTO)
06
private
Long id;
07
08
private
String firstname;
09
private
String lastname;
10
11
// … methods omitted
12
}
01
@Entity
02
public
class
Account {
03
04
@Id
05
@GeneratedValue
(strategy = GenerationType.AUTO)
06
private
Long id;
07
08
@ManyToOne
09
private
Customer customer;
10
11
@Temporal
(TemporalType.DATE)
12
private
Date expiryDate;
13
14
// … methods omitted
15
}
帐户只有一个到期日(expriyDate).再无其他. - 它使用JPA注解.现在我们来看看管理帐号的组件.
01
@Repository
02
@Transactional
(readOnly =
true
)
03
class
AccountServiceImpl
implements
AccountService {
04
05
@PersistenceContext
06
private
EntityManager em;
07
08
@Override
09
@Transactional
10
public
Account save(Account account) {
11
12
if
(account.getId() ==
null
) {
13
em.persist(account);
14
return
account;
15
}
else
{
16
return
em.merge(account);
17
}
18
}
19
20
@Override
21
public
List<Account> findByCustomer(Customer customer) {
22
23
TypedQuery query = em.createQuery(
"select a from Account a where a.customer = ?1"
, Account.
class
);
24
query.setParameter(
1
, customer);
25
26
return
query.getResultList();
27
}
28
}
为了在后面重构引入存储层(repository layer)时不引起名称冲突,我特意命名为 class*Service.但是在概念上,这个类是一个存储对象,而不是服务.事实上我们有吗?
这个被@Repository 注释的类抛出的异常可以被Spring的DataAccessException捕获。另外我们还用到了@Transactional 来保证 save(...) 操作的事务性和该类其他方法(这里是findByCustomer(...))的事务只读标识。这样会对数据库性能和我们持久化提供器的性能有一定的优化。
由于我们不想让程序员去决定什么时候调用EntityManager的merge(...)或者persist(...)方法。我们用了实体类主键字段内容来判断到底调用哪一个。这是判断逻辑是全局通用的所以不用对每一个域对象都去声明实现。查询方法也是很直观的,我们写一个查询,绑定一个参数然后执行这条查询并取得结果。这个方法名其实已经很直观了,其实我们可以根据方法名就推出查询语句,所以这里是可以提升一下的。(在后面可以直接按照这样的模式写方法名和参数而不需要去写实现,SPRING DATA JPA会根据你方法的命名自动转成SQL。(如果配合良好的数据库设计和视图设计,省了不少事情))
Spring Data 存储库支持
在我们开始重构该实现之前,看一下示例项目中包含的那些在重构课程中能够运行的测试用例,确保那些代码现在依旧能用。下面我们就来看如何改善我们的实现类。
Spring Data JPA 提供了一个存储库编程模型,首先,每一个受管理的领域对象都要有一个接口:
1
public
interface
AccountRepository
extends
JpaRepository<Account, Long> { … }
定义这个接口是出于两个目的:首先,通过继承JpaRepository我们获得了一组泛型的CRUD方法,使我们能保存Accounts,删除它们等等。其次,这使得Spring Data JPA存储库框架在classpath中扫描该接口,并为它创建一个Spring bean。
为了让Spring创建一个实现该接口的bean,你仅需要使用Sping JPA"命名空间"并用适当元素激活其对repository 的支持。
1
<
jpa:repositories
base-package
=
"com.acme.repositories"
/>
这将扫描包含在包(package)com.acme.repositories下所有的继承于JpaRepository的接口,并为该接口创建实现了SimpleJpaRepository的Sping bean。下面让我们迈出第一步,稍微重构我们的AccountService实现以便使用我们新介绍的repository接口。
01
@Repository
02
@Transactional
(readOnly =
true
)
03
class
AccountServiceImpl
implements
AccountService {
04
05
@PersistenceContext
06
private
EntityManager em;
07
08
@Autowired
09
private
AccountRepository repository;
10
11
@Override
12
@Transactional
13
public
Account save(Account account) {
14
return
repository.save(account);
15
}
16
17
@Override
18
public
List<Account> findByCustomer(Customer customer) {
19
20
TypedQuery query = em.createQuery(
"select a from Account a where a.customer = ?1"
, Account.
class
);
21
query.setParameter(
1
, customer);
22
23
return
query.getResultList();
24
}
25
}
下一步,我们将重构查询方法。并且使查询方法与保存方法遵循相同的委派策略。我们在存储库接口里面引入查询方法并且将我们原有方法委托给新引进的方法:
1
@Transactional
(readOnly =
true
)
2
public
interface
AccountRepository
extends
JpaRepository<Account, Long> {
3
4
List<Account> findByCustomer(Customer customer);
5
}
01
@Repository
02
@Transactional
(readOnly =
true
)
03
class
AccountServiceImpl
implements
AccountService {
04
05
@Autowired
06
private
AccountRepository repository;
07
08
@Override
09
@Transactional
10
public
Account save(Account account) {
11
return
repository.save(account);
12
}
13
14
@Override
15
public
List<Account> findByCustomer(Customer customer) {
16
return
repository.findByCustomer(Customer customer);
17
}
18
}
我们快速补充点事务处理的知识。在这种非常简单的情况下我们可以从AccountServiceImpl实现类中移除@Transaction注解,因为在存储库的CRUD方法已经进行了事务管理,查询方法在存储库接口中也已经用@Transactional(readOnly = true)进行了标注。当前设置——在service层次标记为事务性的方法是一个最佳实践(尽管在这里并不需要),因为当你在service层次查看方法时它可以显式清楚的表明操作是在同一个事务中处理的。除此之外,如果一个service层次的方法修改了,需要进行多次的存储库方法调用,这种情况下所有的代码任然将会在同一个事务中执行,因为在存储库内部的事务将会简单的加入到外部service层次的事务中。存储库事务行为和调整方式在参考文档中有详细说明。
尝试再次运行测试案例,看看测试是否正常运行。打住,我们还没有实现forfindByCustomer()方法,是不?那它是如何工作的呢?
查询方法
当Spring Data JPA为创建AccountRepository接口创建Spring实例的时候,它会检查接口里面定义的所有查询方法并且 为它们每个都派生一个查询。默认情况下,Spring Data JPA 将自动解析方法名并以此创建一个查询,查询用标准JPA的API实现。在本例中findByCustomer(...)方法在逻辑上等同于JPQL 查询“select a from Account a where a.customer = ?1”。解析方法名称的解析器支持大量的关键字比如And,Or,GreaterThan,LessThan,Like,IsNull,Notand等等,如果您喜欢,您还可以添加ORDER BY子句。有关详情请参阅文档。这种机制给我们提供了一个查询方法编程模型就像你在Grails 或 Spring Roo中用到的一样。
现在,假设你想要显式的使用指定的查询。要做到这点你可以按照如下命名规约(本例中为Account.findByCustomer)在实体上通过注解或在orm.xml中声明一个JPA命名的查询来实现,另外一个选择就是你可以在存储库方法中使用@Query注解:
1
@Transactional
(readOnly =
true
)
2
public
interface
AccountRepository
extends
JpaRepository<Account, Long> {
3
4
@Query
(
"<JPQ statement here>"
)
5
List<Account> findByCustomer(Customer customer);
6
}
01
@Repository
02
@Transactional
(readOnly =
true
)
03
public
class
CustomerServiceImpl
implements
CustomerService {
04
05
@PersistenceContext
06
private
EntityManager em;
07
08
@Override
09
public
Customer findById(Long id) {
10
return
em.find(Customer.
class
, id);
11
}
12
13
@Override
14
public
List<Customer> findAll() {
15
return
em.createQuery(
"select c from Customer c"
, Customer.
class
).getResultList();
16
}
17
18
@Override
19
public
List<Customer> findAll(
int
page,
int
pageSize) {
20
21
TypedQuery query = em.createQuery(
"select c from Customer c"
, Customer.
class
);
22
23
query.setFirstResult(page * pageSize);
24
query.setMaxResults(pageSize);
25
26
return
query.getResultList();
27
}
28
29
@Override
30
@Transactional
31
public
Customer save(Customer customer) {
32
33
// Is new?
34
if
(customer.getId() ==
null
) {
35
em.persist(customer);
36
return
customer;
37
}
else
{
38
return
em.merge(customer);
39
}
40
}
41
42
@Override
43
public
List<Customer> findByLastname(String lastname,
int
page,
int
pageSize) {
44
45
TypedQuery query = em.createQuery(
"select c from Customer c where c.lastname = ?1"
, Customer.
class
);
46
47
query.setParameter(
1
, lastname);
48
query.setFirstResult(page * pageSize);
49
query.setMaxResults(pageSize);
50
51
return
query.getResultList();
52
}
53
}
1
@Transactional
(readOnly =
true
)
2
public
interface
CustomerRepository
extends
JpaRepository<Customer, Long> { … }
01
@Repository
02
@Transactional
(readOnly =
true
)
03
public
class
CustomerServiceImpl
implements
CustomerService {
04
05
@PersistenceContext
06
private
EntityManager em;
07
08
@Autowired
09
private
CustomerRepository repository;
10
11
@Override
12
public
Customer findById(Long id) {
13
return
repository.findById(id);
14
}
15
16
@Override
17
public
List<Customer> findAll() {
18
return
repository.findAll();
19
}
20
21
@Override
22
public
List<Customer> findAll(
int
page,
int
pageSize) {
23
24
TypedQuery query = em.createQuery(
"select c from Customer c"
, Customer.
class
);
25
26
query.setFirstResult(page * pageSize);
27
query.setMaxResults(pageSize);
28
29
return
query.getResultList();
30
}
31
32
@Override
33
@Transactional
34
public
Customer save(Customer customer) {
35
return
repository.save(customer);
36
}
37
38
@Override
39
public
List<Customer> findByLastname(String lastname,
int
page,
int
pageSize) {
40
41
TypedQuery query = em.createQuery(
"select c from Customer c where c.lastname = ?1"
, Customer.
class
);
42
43
query.setParameter(
1
, lastname);
44
query.setFirstResult(page * pageSize);
45
query.setMaxResults(pageSize);
46
47
return
query.getResultList();
48
}
49
}
到目前为止,一切都很好。以下两种方法都可以处理常见的场景:你希望给定查询能够分页而不是返回全部实体(比如一页10条记录)。眼下我们通过两个整数适当限制查询来实现,但这样做有两个问题,两个整数实际上只代表了一个方面,这里并没有明确这一点。除此之外,结果我们只返回了一个简单的list,所以我们丢掉了实际页面的元数据信息:这是第一页吗?这是最后一页吗?总共有多少页?Spring Data提供了一个抽象包括两个接口:Pageable(捕捉分页请求)和Page(捕获结果以及元信息)。让我们按照如下方式尝试添加findByLastname(…)到存储库接口中并重写findAll(…)和findByLastname(…)方法:
1
@Transactional
(readOnly =
true
)
2
public
interface
CustomerRepository
extends
JpaRepository<Customer, Long> {
3
4
Page<Customer> findByLastname(String lastname, Pageable pageable);
5
}
1
@Override
2
public
Page<Customer> findAll(Pageable pageable) {
3
return
repository.findAll(pageable);
4
}
5
6
@Override
7
public
Page<Customer> findByLastname(String lastname, Pageable pageable) {
8
return
repository.findByLastname(lastname, pageable);
9
}
总结
在本文的课程中,我们把为存储库编写的代码减少到2个接口,包含3个方法一行XML:
1
@Transactional
(readOnly =
true
)
2
public
interface
CustomerRepository
extends
JpaRepository<Customer, Long> {
3
4
Page<Customer> findByLastname(String lastname, Pageable pageable);
5
}
1
@Transactional
(readOnly =
true
)
2
public
interface
AccountRepository
extends
JpaRepository<Account, Long> {
3
4
List<Account> findByCustomer(Customer customer);
5
}
1
<
jpa:repositories
base-package
=
"com.acme.repositories"
/>
- 开始使用 Spring Data JPA
- 开始使用 Spring Data JPA(1)
- 开始使用 Spring Data JPA(2)
- Spring Data JPA 使用
- spring-data-jpa 使用
- spring data jpa使用
- spring-data-jpa 使用
- Spring Data JPA使用
- spring-data-jpa 使用
- spring-data-jpa初步开始的helloworld
- spring-data-jpa初步开始的helloworld
- 使用 Spring Data JPA 简化 JPA 开发
- 使用 Spring Data JPA 简化 JPA 开发
- 使用 Spring Data JPA 简化 JPA 开发
- 使用 Spring Data JPA 简化 JPA 开发
- 使用 Spring Data JPA 简化 JPA 开发
- 使用 Spring Data JPA 简化 JPA 开发
- 使用 Spring Data JPA 简化 JPA 开发
- tableView的编辑
- 【HDU5371】Hotaru's problem(Manacher + set)
- Singapore National Day (SG50 Golden Jubilee)
- 如何使用1M的内存排序100万个8位数
- ORM进阶:Hibernate的优劣对比
- 开始使用 Spring Data JPA
- 数据结构实验之链表六:有序链表的建立
- 2.3 寻找"水贴王"
- 新手学习C++笔记7:结构体、联合体的区别与联系
- lftp复制文件及镜像
- 8.11学习总结
- 定义AlertDialog子类
- 高并发系统设计
- iOS项目开发实战——获取网页源代码的二进制数据