Spring Data for Mongo 介绍

本文将快速介绍Spring Data for Mongo的使用。
Spring Data for MongoDB是Spring Data的一个子模块。 目标是为MongoDB提供一个相近的一致的基于Spring的编程模型。
Spring Data for MongoDB核心功能是映射POJO到Mongo的DBCollection中的文档,并且提供Repository 风格数据访问层。相似的ORM/持久化框架还有morphia: MongoDB官方支持的ORM框架,可以很好的和Spring, Guice等DI框架集成,使用起来很方便。
Hibernate OGM: Hibernate提供了Hibernate风格的NoSql ORM框架。
jongo: 提供Mongo shell一样灵活的查询,并且提供ORM by Jackson,和Mongo java driver一样快。

特性:

可以通过@Configuration注解或者XML风格配置
MongoTemplate 辅助类 (类似JdbcTemplate),方便常用的CRUD操作
异常转换
丰富的对象映射
通过注解指定对象映射
持久化和映射声明周期事件
通过MongoReader/MongoWriter 定义底层的映射
基于Java的Query, Criteria, Update DSL
自动实现Repository,可以提供定制的查找
QueryDSL 支持类型安全的查询
跨数据库平台的持久化 - 支持JPA with Mongo
GeoSpatial 支持
Map-Reduce 支持
JMX管理和监控
CDI 支持
GridFS 支持

本文介绍的Spring Data for MongoDB版本为1.7.0.M1。

Spring Data for MongoDB提供了两种编程风格来应用MongoDB,下面逐一介绍这两种方式。
Spring Data Repository 风格

Spring Data提供了repository 抽象方式,可以极大的减少数据访问层千篇一律的类似的重复的代码。 基本DAO都会实现,find,findAll, findById, save, delete,update等方法,而且代码逻辑基本一致。Spring Data提供了简化方法,通过接口定义 Spring Data通过Proxy自动提供具体的实现。
这里有一篇介绍文章。
核心概念

Spring Data最重要的接口是Repository。它使用域类型和它的ID类型作为类型参数。

public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {

<S extends T> S save(S entity);

T findOne(ID primaryKey);

Iterable<T> findAll();

Long count();

void delete(T entity);

boolean exists(ID primaryKey);

// … more functionality omitted.
}

同时也提供特定数据库的接口: JpaRepository和 MongoRepository。 这些接口继承CrudRepository。 CrudRepository之上还有一个接口PagingAndSortingRepository提供分页的功能。

public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {

Iterable<T> findAll(Sort sort);

Page<T> findAll(Pageable pageable);
}

可以增加一些特定的统计和删除方法。

public interface UserRepository extends CrudRepository<User, Long> {
Long countByLastname(String lastname);
}

public interface UserRepository extends CrudRepository<User, Long> {
Long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
查询方法

标准的CRUD功能的repositories一般会提供一些查询方法。在Spring Data中只需四步。
1) 声明一个子接口: interface PersonRepository extends Repository<User, Long> { … }
2) 声明查询方法

interface PersonRepository extends Repository<User, Long> {
List<Person> findByLastname(String lastname);
}

3) 设置Spring为这些接口产生Proxy实例:两种方式。

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableJpaRepositories
class Config {}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/data/jpa

http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

<jpa:repositories base-package="com.acme.repositories"/>

</beans>

4) 注入此repository实例并使用它

public class SomeClient {

@Autowired
private PersonRepository repository;

public void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}
定义repository 接口

第一步就是为特定的domain类定义相应的定义repository。
调整repository 定义

典型的,repository 接口应该继承Repository,CrudRepository,PagingAndSortingRepository。 如果你不想继承这些接口,使用@RepositoryDefinition注解标记此接口为repository。
@NoRepositoryBean增加一些非典型的方法,然后在domain class Repository继承它。

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

T findOne(ID id);

T save(T entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
定义查询方法

repository proxy有两种方式根据方法导出数据库特定的语句。 一种是根据方法名直接导出。 另一种是是手工定义。
Query lookup策略

通过XML方式的query-lookup-strategy 属性或者${store}注解的queryLookupStrategy 指定。

CREATE: 根据方法名
USE_DECLARED_QUERY: 通过注解或者其它方式得到
CREATE_IF_NOT_FOUND: 复合前面两种

Query creation

方法名应该是find…By, read…By, query…By, count…By, and get…By这样的格式。可以设置Distinct 和And, Or:

public interface PersonRepository extends Repository<User, Long> {

List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
Property expressions

假定Person有Address, Address有ZipCode 属性。
List<Person> findByAddressZipCode(ZipCode zipCode); 会使用x.address.zipCode遍历。
更明确的用:

List<Person> findByAddress_ZipCode(ZipCode zipCode);
特殊参数

Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
限制查询结果

User findFirstByOrderByLastname();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
产生repository实例
XML配置方式

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/data/jpa

http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

<repositories base-package="com.acme.repositories" />
</beans:beans>

使用筛选器:

<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
JavaConfig方式

使用@Enable${store}Repositories注解。如EnableMongoRepositories, EnableJpaRepositories

@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

@Bean
public EntityManagerFactory entityManagerFactory() {
// …
}
}
编程方式

RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
定制Repository实现

如果觉得默认的约定不够,可以定制实现。

interface UserRepositoryCustom {
public void someCustomMethod(User user);
}

class UserRepositoryImpl implements UserRepositoryCustom {

public void someCustomMethod(User user) {
// Your custom implementation
}
}

注意实现名是接口名加Impl。 不过可以定制后缀。

<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="FooBar" />

你可以到这里下载例子。
相关文件:
文件    描述
config/MongoConfig    Spring配置文件,替代Spring XML配置文件
entity/Customer    domain object
entity/Address    domain object
repository/CustomerRepository    DAO层接口
APP    测试类
MongoTemplate方式

以上啰嗦了很多,感觉和Mongo关系不大。 这是Spring Data为各种数据库如JPA,Mongo提供的一种统一的方式。 理论上来说,无论你使用Mysql, Oracle,Mongo,都可以采用这种方式组织你的代码。

但是, Spring Data for MongoDB还提供了另外一种方式, 类似JdbcTemplate的方式。 这种方式你可以自己定义你的repository的编程方式。 这种方式让你感觉更灵活, 不被上面的各种约定束缚住。

你可以通过XML或者JavaConfig方式配置MongoTemplate.

1) JavaConfig方式

/*
* Use the standard Mongo driver API to create a com.mongodb.Mongo instance.
*/
public @Bean Mongo mongo() throws UnknownHostException {
return new Mongo("localhost");
}

或者

/*
* Factory bean that creates the com.mongodb.Mongo instance
*/
public @Bean MongoFactoryBean mongo() {
MongoFactoryBean mongo = new MongoFactoryBean();
mongo.setHost("localhost");
return mongo;
}

2) XML方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation=
"http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd

*http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd*

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Default bean name is 'mongo' -->
*<mongo:mongo host="localhost" port="27017"/>*

</beans>

可以设置更多的参数。

<beans>

<mongo:mongo host="localhost" port="27017">
<mongo:options connections-per-host="8"
threads-allowed-to-block-for-connection-multiplier="4"
connect-timeout="1000"
max-wait-time="1500}"
auto-connect-retry="true"
socket-keep-alive="true"
socket-timeout="1500"
slave-ok="true"
write-number="1"
write-timeout="0"
write-fsync="true"/>
</mongo:mongo/>

</beans>

如果你想将MongoTemplate配置成Spring Bean:

@Configuration
public class MongoConfiguration {

public @Bean MongoDbFactory mongoDbFactory() throws Exception {
UserCredentials userCredentials = new UserCredentials("joe", "secret");
return new SimpleMongoDbFactory(new Mongo(), "database", userCredentials);
}

public @Bean MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongoDbFactory());
}
}

或者XML:

<mongo:db-factory id="anotherMongoDbFactory"
host="localhost"
port="27017"
dbname="database"
username="joe"
password="secret"/>
MongoTemplate

MongoTemplate提供了非常多的操作MongoDB的方法。 它是线程安全的,可以在多线程的情况下使用。
MongoTemplate实现了MongoOperations接口, 此接口定义了众多的操作方法如"find", "findAndModify", "findOne", "insert", "remove", "save", "update" and "updateMulti"等。
它转换domain object为DBObject,并提供了Query, Criteria, and Update等流式API。
缺省转换类为MongoMappingConverter。

它有几个构造函数:

MongoTemplate(Mongo mongo, String databaseName)
MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials)
MongoTemplate(MongoDbFactory mongoDbFactory)
MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter)

Repository

在这种方式下,你需要自己实现Repository的具体类。
一般情况下你需要为所有的Repository实现一个抽象的父类,在父类中实现大部分CRUD操作。 在子类中实现特定的操作方法。
你的Repository可以实现MongoRepository或者CrudRepository接口,但是不是必须的。
本例子中简单实现了一个简单的Repository,纯演示使用。

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Repository;

import com.colobu.springmongo.entity.Customer;

@Repository
public class AnotherCustomerRepository {
@Autowired
private MongoTemplate mongoTemplate;

public Customer save(Customer c) {
mongoTemplate.save(c);
return c;
}

public void deleteAll() {
mongoTemplate.dropCollection(Customer.class);
}

public List<Customer> findAll() {
return mongoTemplate.findAll(Customer.class);
}

public List<Customer> findByLastname(String lastname, Sort sort){
Criteria criteria = new Criteria("lastname").is(lastname);
return mongoTemplate.find(new Query(criteria), Customer.class);
}

public GeoResults<Customer> findByAddressLocationNear(Point point, Distance distance){
return mongoTemplate.geoNear(NearQuery.near(point).maxDistance(distance), Customer.class);
}
}

在这种情况下,方法名比较自由,你毋须遵守某种约定。
参考文档

http://docs.spring.io/spring-data/data-mongo/docs/1.7.0.M1/reference/html/

发表评论