Spring Data —— 完全统一的API?
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。 每一种数据存储都有各自一套注释用以标注映射所需要的元信息。下面我们来看一个简单的例子,如何映射一个简单领域对象:
@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注释标注。
同样这两种映射也支持对其他对象的引用,请看下面的例子:
@OneToMany private List<Role> roles; |
private List<Role> roles; | @RelatedTo( type = "has", direction = Direction.OUTGOING) private List<Role> 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 项目旨在为大家提供一种通用的编码模式。
数据访问对象实现了对物理数据层的抽象, 为编写查询方法提供了方便。通过对象映射, 实现域对象和持续化存储之间的转换, 而模板提供的是对底层存储实体的访问实现。
关于作者
原文地址:http://www.infoq.com/articles/spring-data-intro
感谢侯伯薇对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。
相关推荐
<artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</...
<artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> ``` 2....
<artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 配置 在application.yml配置文件添加如下配置 spring.cache.type: REDIS # REDIS (RedisProperties) spring.redis.database: 0 spring....
RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 可以根据需求配置序列化方式,例如使用JdkSerializationRedisSerializer // template....
<artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> ``` 然后,...
<artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </...
Spring Data Redis提供了丰富的API,使得在Spring应用程序中操作Redis变得非常便捷。 首先,让我们了解Spring Data Redis的核心特性: 1. **自动配置**:Spring Boot通过自动配置类`RedisAutoConfiguration`和`...
<artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> ``` 对于Gradle,这将在`build.gradle`中: ```groovy implementation 'com.jolbox:bonecp:0.8.0.RELEASE' implementation 'org....
本知识点主要聚焦于Spring框架的一个重要组件——Spring JDBC,以及如何将它与MySQL8数据库进行整合,同时考虑了Java 8的新特性。下面我们将深入探讨这个主题。 Spring JDBC模块是Spring框架对Java数据库连接(JDBC...
<artifactId>springdoc-openapi-data-rest</artifactId> <version>1.6.9</version> </dependency> ``` 接下来,我们需要在SpringBoot的主配置类上添加`@EnableOpenApi`注解,启动Swagger3的功能: ```java ...
<groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.x.y</version> </dependency> ``` 然后,创建一个XML配置文件,如`beans.xml`,在这里声明你的bean定义: ```xml...
SpringData JPA会自动实现这些接口,提供基本的CRUD操作。例如: ```java import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, Long> { }...
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="17367648787"/> <property name="defaultEncoding" value=...
在本篇文章中,我们将聚焦于Spring Data的一个重要模块——Spring Data Elasticsearch,这是针对流行搜索引擎Elasticsearch的Java开发插件。通过对Spring Data Elasticsearch 2.1.10.RELEASE的源码分析,我们可以...
【标题】"商城项目——goods-api"所涉及的是一个电商系统的商品管理API接口部分,它主要负责处理与商品相关的各种操作,包括但不限于商品的创建、删除、更新以及查询等核心功能。在电商平台上,商品是交易的核心元素...
这就引出了我们今天要讨论的主题——Spring Data JPA 中的动态查询。 在传统的 Hibernate 开发模式中,我们通常会根据前端传递的参数动态拼接 SQL(HQL)语句。Spring Data JPA 为了支持这种动态查询,引入了 ...
Spring Data是一个模块化的项目,它的主要目标是通过提供统一的API来减少数据访问层的开发工作。这个框架支持多种数据存储,包括MongoDB、Hadoop、Cassandra等,以及我们的关注点——Redis。Spring Data Redis模块...
【Java配置详解——Spring与Ajax整合实践】 在Java开发领域,Spring框架以其强大的依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)能力,成为了企业级应用开发的首选。...
在Spring Boot项目中,我们通常会使用`Springfox`库来整合Swagger,因为Springfox提供了对Spring MVC和Spring Data REST的全面支持。 要开始使用Swagger,我们需要在项目中添加Springfox的依赖。在`pom.xml`文件中...
Spring框架本身并不提供具体的缓存实现,但它提供了一套统一的缓存管理API,能够轻松地与各种第三方缓存系统集成。这一设计思路使得开发者能够根据实际需求灵活地选择不同的缓存策略。 **核心思想**:Spring Cache...