本文将快速介绍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类型作为类型参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
同时也提供特定数据库的接口: JpaRepository
和 MongoRepository
。 这些接口继承CrudRepository
。 CrudRepository
之上还有一个接口PagingAndSortingRepository
提供分页的功能。
1
2
3
4
5
6
7
public interface PagingAndSortingRepository <T , ID extends Serializable >
extends CrudRepository <T , ID > {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
可以增加一些特定的统计和删除方法。
1
2
3
public interface UserRepository extends CrudRepository <User , Long > {
Long countByLastname(String lastname);
}
1
2
3
4
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) 声明查询方法
1
2
3
interface PersonRepository extends Repository<User, Long> {
List<Person> findByLastname(String lastname);
}
3) 设置Spring为这些接口产生Proxy实例:两种方式。
1
2
3
4
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableJpaRepositories
class Config {}
或
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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实例并使用它
1
2
3
4
5
6
7
8
9
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继承它。
1
2
3
4
5
6
7
8
9
10
11
@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
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface PersonRepository extends Repository <User , Long > {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
List<Person> findByLastnameIgnoreCase(String lastname);
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
Property expressions 假定Person
有Address
, Address
有ZipCode
属性。List<Person> findByAddressZipCode(ZipCode zipCode);
会使用x.address.zipCode遍历。 更明确的用:
1
List<Person> findByAddress_ZipCode(ZipCode zipCode);
特殊参数 1
2
3
4
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);
限制查询结果 1
2
3
4
5
6
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配置方式 1
2
3
4
5
6
7
8
9
10
11
<?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 >
使用筛选器:
1
2
3
<repositories base-package ="com.acme.repositories" >
<context:exclude-filter type ="regex" expression =".*SomeRepository" />
</repositories >
JavaConfig方式 使用@Enable${store}Repositories注解。如EnableMongoRepositories
, EnableJpaRepositories
1
2
3
4
5
6
7
8
9
@Configuration
@EnableJpaRepositories ("com.acme.repositories" )
class ApplicationConfiguration {
@Bean
public EntityManagerFactory entityManagerFactory () {
}
}
编程方式 1
2
RepositoryFactorySupport factory = …
UserRepository repository = factory.getRepository(UserRepository.class);
定制Repository实现 如果觉得默认的约定不够,可以定制实现。
1
2
3
4
5
6
7
8
9
10
interface UserRepositoryCustom {
public void someCustomMethod (User user);
}
class UserRepositoryImpl implements UserRepositoryCustom {
public void someCustomMethod (User user) {
}
}
注意实现名是接口名加Impl
。 不过可以定制后缀。
1
2
<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方式
1
2
3
4
5
6
* Use the standard Mongo driver API to create a com.mongodb.Mongo instance.
*/
public @Bean Mongo mongo () throws UnknownHostException {
return new Mongo("localhost" );
}
或者
1
2
3
4
5
6
7
8
* Factory bean that creates the com.mongodb.Mongo instance
*/
public @Bean MongoFactoryBean mongo () {
MongoFactoryBean mongo = new MongoFactoryBean();
mongo.setHost("localhost" );
return mongo;
}
2) XML方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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 ">
*<mongo:mongo host ="localhost" port ="27017" /> *
</beans >
可以设置更多的参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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:
1
2
3
4
5
6
7
8
9
10
11
12
@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:
1
2
3
4
5
6
<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,纯演示使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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/