写点什么

Spring Data —— 完全统一的 API?

2012 年 9 月 05 日

Spring Data 作为 SpringSource 的其中一个父项目, 旨在统一和简化对各类型持久化存储, 而不拘泥于是关系型数据库还是 NoSQL 数据存储。

无论是哪种持久化存储, 数据访问对象(或称作为 DAO,即 Data Access Objects)通常都会提供对单一域对象的 CRUD (创建、读取、更新、删除)操作、查询方法、排序和分页方法等。Spring Data 则提供了基于这些层面的统一接口(CrudRepository,PagingAndSortingRepository)以及对持久化存储的实现。

你可能接触过某一种 Spring 模型对象——比如 JdbcTemplate ——来编写访问对象的实现。基于 Spring Data 的数据访问对象, 我们只需定义和编写一些查询方法的接口(基于不同的持续化存储, 定义有可能稍有不同)。Spring Data 会在运行时间生成正确的实现。 请看下面的例子:

复制代码
public interface UserRepository extends MongoRepository<User, String> {
        @Query("{ fullName: ?0 }")
        List<User> findByTheUsersFullName(String fullName);
        List<User> findByFullNameLike(String fullName, Sort sort);
}
...
Autowired UserRepository repo;

本文将比较两个 JPA 的子项目, MongoDB 和 Neo4j。JPA 是 J2EE 的一部分, 它定义了一系列用于操作关系型数据库和 O/R 映射的 API。 MongoDB 是一种可扩展的、高性能的、开源的、面向文档的数据库。Neo4j 则是一种图形数据库,一种完整的用于存储图形数据的事务性数据库。

所有这些 Spring Data 的子项目都支持:

  • 模板
  • 对象、数据存储映射
  • 对数据访问对象的支持

其他一些 Spring Data 子项目,如 Spring Data Redis 和 Spring Data Riak 都只是提供模板, 这是由于其相应的数据存储都只支持非结构化的数据,而不适用于对象的映射和查询。

下面,我们来深入了解一下模板、对象数据映射和数据访问对象。

模板

Spring Data 模板的主要目的, 同时也是所有其他 Spring 模板的目的, 就是资源分配和异常处理。

这里所说的资源就是数据存储资源, 通常来说会通过远程 TCP/IP 连接访问。下面的实例展示了如果配置 MongoDB 的模板:

复制代码
<!-- Connection to MongoDB server -->
<mongo:db-factory host="localhost" port="27017" dbname="test" />
<!-- MongoDB Template -->
<bean id="mongoTemplate"
class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
</bean>

首先我们需要定义连接工厂,MongoTemplate 会引用这个连接工厂。这个例子中, Spring Data 采用了较底层的数据库驱动, MongoDB Java driver

一般来说, 这类较底层的数据库驱动会有自己的一套异常处理策略。 Spring 的异常处理采用的是未检查异常(unchecked exception),因此开发人员可以自己决定是否须要捕获异常。MongoDB 的模板的实现方式是把捕获到的底层的异常封装成未检查异常, 这些异常都是 Spring 里 DataAccessException 的子类。

模板提供了基于数据存储的操作, 诸如保存、更新、删除单一记录或执行查询的方法。但所有这些方法只能用于相应的底层数据存储。

由于 JPA 的实现本身已经是位于 JDBC API 上层的抽象层,Spring Data JPA 并没有提供模板。和模板概念相对应的是 JPA 的 EntityManager, 而异常处理则是由数据访问对象来负责的。

对象、数据映射

JPA 引入了 O/R 映射的标准(如关系型数据库中表和对象的映射)。Hibernate 很有可能是被最为广泛使用的 JPA 标准的实现。

Spring Data 采用类对象的方式将 O/R 映射的支持延伸到了 NoSQL 数据库。但在各种 NoSQL 数据库中, 数据结构差异较大, 所以很难形成一种通用的 API。 每一种数据存储都有各自一套注释用以标注映射所需要的元信息。下面我们来看一个简单的例子,如何映射一个简单领域对象:

JPA MongoDB Neo4j @Entity
@Table(name=“TUSR”)
public class User {
@Id
private String id;
@Column(name=“fn”)
private String name;
private Date lastLogin;

} @Document(collection=“usr”)
public class User {
@Id
private String id;
@Field(“fn”)
private String name;
private Date lastLogin;

} @NodeEntity
public class User {
@GraphId
Long id;
private String name;
private Date lastLogin;

}如果你已经熟悉 JPA 实体,不难看出这里用了标准的 JPA 注释。Spring Data 复用了这些标准的注释, 而且没有引入其他新的内容。对象的映射正是由这些 JPA 的实现完成的。 MangoDB 和 Neo4j 各种需要一套类似的注释。在上面的例子中, 我们使用了类级别的注释 collection 和 nodetype. MangoDB 中, collection 就相当于关系型数据库的表, 而 node 和 edge 则是图形数据库(如 Neo4j)的主要数据类型。

每个 JPA 实体都需要有唯一标识符,即便是 MongoDB 的文档和 Neo4j 的节点也是如此。

MongoDB 使用 @Id 这个注释作为唯一标识符(这 @Id 是在 org.springframework.data.annotation 包中, 和 JPA 的 @Id 并不相同)。Neo4j 则使用了 @GraphId 这个注释。这些属性的值是在域对象成功存储后被设置的。 当类属性的名称和 MongoDB 的文档中的属性名称不同时, 可以使用 @Field 注释标注。

同样这两种映射也支持对其他对象的引用,请看下面的例子:

JPA MongoDB Neo4j @OneToMany
private List roles; private List roles; @RelatedTo( type = “has”, direction = Direction.OUTGOING)
private List roles; 在 JPA 中, 我们使用 @OneToMany 来标识一对多的关系, 通常多的一端的数据存放在子表中, 通过联合查询获得。MongoDB 并不支持文档间的联合查询,默认情况下, 被引用的对象和主对象存储在同一个文档中。当然, 我们也可以通过客户端的虚拟联合查询引用其他文档的数据。在 Neo4j 中, 关系被称作 edges, 而 edge 也是一个基本的数据类型。

总结来说, MongoDB 和 Neo4j 所使用的对象映射和我们大家所熟悉的 JPA O/R 映射非常类似, 但由于不同的数据结构,两者存在着显著的区别。但不管怎么说, 基本概念都是实现对象和数据结构的映射。

数据访问对象

你一定写过这样的 DAO 对象,针对单一记录的 CRUD 操作、针对多记录的 CRUD 操作, 基于各种查询条件的查询方法。

随着 JPA 的引入, 虽然 EntityManager 接口已经包含了 CRUD 操作,但 编写查询方法仍然是一件麻烦事, 为此, 完成一次查询需要创建命名查询, 设置参数, 执行查询。请看下面的例子:

复制代码
@Entity
@NamedQuery( name="myQuery", query = "SELECT u FROM User u where u.name = :name" )
public class User {
...
}
@Repository
public class ClassicUserRepository {
@PersistenceContext EntityManager em;
public List<User> findByName(String Name) {
TypedQuery<User> q = getEntityManger().createNamedQuery("myQuery", User.class);
q.setParameter("name", fullName);
return q.getResultList();
}
...

TypedQuery 可以略微简化这个过程,如:

复制代码
@Repository
public class ClassicUserRepository {
@PersistenceContext EntityManager em;
public List<User> findByName(String name) {
return getEntityManger().createNamedQuery("myQuery", User.class)
.setParameter("name", fullName)
.getResultList();
}
...

我们仍然须要编写类似这样的查询方法,为查询赋值, 执行查询。如果可以引入 Spring Data JPA,要实现这类的查询, 编码就可以大大简化, 如下:

复制代码
package repositories;
public interface UserRepository extends JpaRepository<User, String> {
List<User> findByName(String name);
}

在 Spring Data JPA 中, 我们将不再需要在 JPA 实体类中定义 @NamedQuerys, 来实现 JPQL 的查询。 相反, 我们可以为数据访问对象的各方法加上 @Query 这样的注释。如:

复制代码
@Transactional(timeout = 2, propagation = Propagation.REQUIRED)
@Query("SELECT u FROM User u WHERE u.name = 'User 3'")
List<User> findByGivenQuery();

Spring Data MongoDB 和 Spring Data Neo4j 可以使上述方法同样适用于 MangoDB 和 Neo4j 数据库。下面的例子的是通过 Cipher 查询语言来实现对 Neo4j 数据库的查询。

复制代码
public interface UserRepository extends GraphRepository<User> {
User findByLogin(String login);
@Query("START root=node:User(login = 'root') MATCH root-[:knows]->friends RETURN friends")
List<User> findFriendsOfRoot();
}

当然,各个持久化层的方的命名规则稍有差异。比如说, MongoDB 支持一种叫 geospatial queries 的查询语言, 通常我们可以这样写查询:

复制代码
public interface LocationRepository extends MongoRepository<Location, String> {
List<Location> findByPositionWithin(Circle c);
List<Location> findByPositionWithin(Box b);
List<Location> findByPositionNear(Point p, Distance d);
}

Spring Data 也提供对分页和排序的通用方法,这需要在查询方法中加入特殊的参数。

支持数据访问对象的优势在于:

  • 减少相似代码的编写
  • 定义方法的同时, 可以定义查询语句,这也使文档变得更清晰
  • 另外一个优点是, 查询语句将和 Spring 上下文同时编译并组装,而不是在初次使用的时候编译, 这样可以大大减少代码编写中的语法错误。

总结

本文只是简单介绍了 Spring Data 的这些新内容, 意在阐明和普通 JPA 的相似点和不同之处。下面这些链接可以帮助大家对 Spring Data 的相关项目做更输入的了解。

对标题中问题的回答显然是否定的, 不可能存在支持所有持久存储的通用 API, 其原因也是不言而喻的。Spring Data 项目旨在为大家提供一种通用的编码模式。

数据访问对象实现了对物理数据层的抽象, 为编写查询方法提供了方便。通过对象映射, 实现域对象和持续化存储之间的转换, 而模板提供的是对底层存储实体的访问实现。

关于作者

Tobias Trelle 是 codecentric AG 的高级 IT 顾问,有 15 年的 IT 从业经验。致力于对软件系统架构、EAI 以及云计算的研究。作者会在博客中发布一些培训材料和公开演讲内容。

Twitter

Blog Linked In G+

原文地址: http://www.infoq.com/articles/spring-data-intro


感谢侯伯薇对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012 年 9 月 05 日 03:0030336

评论

发布
暂无评论
发现更多内容

设计模式:建造者模式

看山

设计模式 建造者模式

模拟电路设计工程师发展九段

老壳有点爽

集成电路 IC 芯片设计 模拟电路

高并发系统三大利器之缓存

java金融

Java 缓存 高并发 本地缓存 分布式缓存

Vim小技巧(2)

老壳有点爽

vim Linux 脚本语言

商业计划书制作(3):写作过程中要思考的问题

老壳有点爽

创业 财富自由 商业计划书

git回退版本,再返回最新分支git pull失败的解决经验

良知犹存

git

数字后端工程师发展六阶段

老壳有点爽

芯片 集成电路 IC 数字电路工程师

Linux指令简述&vim引入(1)

老壳有点爽

vim Linux 脚本

商业计划书制作(6):商业模式

老壳有点爽

创业 商业模式 财富自由 商业计划书

高并发系统三大利器之限流

java金融

架构 高并发 分布式限流 限流 单机限流

Verilog 的debug技巧(1)

老壳有点爽

芯片 集成电路 IC Verilog 电路

商业计划书制作(2):商业计划书的完成阶段

老壳有点爽

创业 财富自由 商业计划书

商业计划书制作(8):财务分析部分

老壳有点爽

创业 财富自由 商业计划书 财务分析

魅力非凡的半导体电路行业

老壳有点爽

芯片 集成电路 IC 芯片营销

商业计划书制作(4):自我评估&投资商关注重点

老壳有点爽

创业 财富自由 商业计划书

硬件产品管理(1):手板管理流程

老壳有点爽

创业 硬件产品 智能硬件 手板

硬件产品管理(5):硬件产品工作流程管理及案例分析

老壳有点爽

创业 硬件产品 智能硬件 产品管理

sed 语言学习技巧(2)

老壳有点爽

vim sed 脚本语言

商业计划书制作(5):业务发展的历史与未来

老壳有点爽

创业 财富自由 商业计划书 业务发展的历史与未来

商业计划书制作(7):编写规范及常见内容

老壳有点爽

创业 财富自由 商业计划书

可伸缩系统架构简介

Rayjun

分布式 可伸缩

面试的时候不能做捧哏

escray

学习 面试 面试现场

半导体行业个人理解

老壳有点爽

芯片 半导体 集成电路 IC

sed语言学习技巧(1)

老壳有点爽

vim 编程语言 sed 脚本语言

硬件产品管理(2):产品QA检测

老壳有点爽

硬件产品 智能硬件 QA 产品管理

硬件产品管理(3):产品问题整理-举例

老壳有点爽

创业 硬件产品 智能硬件

硬件产品管理(4):人体工程学验证

老壳有点爽

硬件产品 智能硬件 产品管理 人体工程学

《我在一线做用户增长》读书笔记及感想

王新涵

用户增长

Java中的单例模式(完整篇)

看山

Java 设计模式 单例模式

IC设计流程及工具

老壳有点爽

芯片 集成电路 IC IC设计流程及工具

商业计划书制作(1):商业计划书的信息需求

老壳有点爽

创业 商业计划书 信息需求

Spring Data —— 完全统一的API?-InfoQ