Mongodb Metadata based Mapping

来自ling
跳转至: 导航搜索

http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/

Metadata based Mapping

@Document
@CompoundIndexes({
    @CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
})
#lastName in ascending order and age in descending order:
public class Person<T extends Address> {

  @Id
  private String id;

  @Indexed(unique = true)
  private Integer ssn;

  @Field("fName")
  private String firstName;

  @Indexed
  private String lastName;

  private Integer age;

  @Transient
  private Integer accountTotal;

  @DBRef
  private List<Account> accounts;

  private T address;


  public Person(Integer ssn) {
    this.ssn = ssn;
  }

  @PersistenceConstructor
  public Person(Integer ssn, String firstName, String lastName, Integer age, T address) {
    this.ssn = ssn;
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.address = address;
  }

  public String getId() {
    return id;
  }

  // no setter for Id.  (getter is only exposed for some unit testing)

  public Integer getSsn() {
    return ssn;
  }

// other getters/setters omitted

@Id - applied at the field level to mark the field used for identity purpose.

@Document - applied at the class level to indicate this class is a candidate for mapping to the database. You can specify the name of the collection where the database will be stored.

@DBRef - applied at the field to indicate it is to be stored using a com.mongodb.DBRef.

@Indexed - applied at the field level to describe how to index the field.

@CompoundIndex - applied at the type level to declare Compound Indexes

@GeoSpatialIndexed - applied at the field level to describe how to geoindex the field.

@TextIndexed - applied at the field level to mark the field to be included in the text index.

@Language - applied at the field level to set the language override property for text index.

@Transient - by default all private fields are mapped to the document, this annotation excludes the field where it is applied from being stored in the database

@PersistenceConstructor - marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved DBObject.

@Value - this annotation is part of the Spring Framework . Within the mapping framework it can be applied to constructor arguments. This lets you use a Spring Expression Language statement to transform a key’s value retrieved in the database before it is used to construct a domain object. In order to reference a property of a given document one has to use expressions like: @Value("#root.myProperty") where root refers to the root of the given document.

@Field - applied at the field level and described the name of the field as it will be represented in the MongoDB BSON document thus allowing the name to be different than the fieldname of the class.

@Version - applied at field level is used for optimistic locking and checked for modification on save operations. The initial value is zero which is bumped automatically on every update.

jpa

  • Repository
  • CrudRepository
  • PagingAndSortingRepository
  • @NoRepositoryBean

@NoRepositoryBean Spring Data JPA: 为所有Repository添加自定义方法 Make sure you add that annotation to all repository interfaces that Spring Data should not create instances for at runtime

interface MyRepository extends JpaRepository<User, Long> { }

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
  …
}

interface UserRepository extends MyBaseRepository<User, Long> {
  …
}
  • @RepositoryDefinition
  • @EnableJpaRepositories @EnableMongoRepositories
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }

@DomainEvents @AfterDomainEventsPublication

 class AnAggregateRoot {

    @DomainEvents 
    Collection<Object> domainEvents() {
        // … return events you want to get published here
    }

    @AfterDomainEventsPublication 
    void callbackMethod() {
       // … potentially clean up domain events list
    }
 }

QueryDslPredicateExecutor

interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {

}
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
	.and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);

@EnableSpringDataWebSupport

As you can see the method receives a User instance directly and no further lookup is necessary. The instance can be resolved by letting Spring MVC convert the path variable into the id type of the domain class first and eventually access the instance through calling findOne(…) on the repository instance registered for the domain type.

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration { }

@Controller
@RequestMapping("/users")
public class UserController {

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {

    model.addAttribute("user", user);
    return "userForm";
  }
}

The configuration snippet above also registers a PageableHandlerMethodArgumentResolver as well as an instance of SortHandlerMethodArgumentResolver. The registration enables Pageable and Sort being valid controller method arguments

@Controller
@RequestMapping("/users")
public class UserController {

  @Autowired UserRepository repository;

  @RequestMapping
  public String showUsers(Model model, Pageable pageable) {

    model.addAttribute("users", repository.findAll(pageable));
    return "users";
  }
}

Request parameters evaluated for Pageable instances

  • page Page you want to retrieve, 0 indexed and defaults to 0.
  • size Size of the page you want to retrieve, defaults to 20.
  • sort Properties that should be sorted by in the format property,property(,ASC|DESC). Default sort direction is ascending. Use multiple sort parameters if you want to switch directions, e.g. ?sort=firstname&sort=lastname,asc.

SpringDataWebConfiguration

To customize this behavior extend either SpringDataWebConfiguration or the HATEOAS-enabled equivalent and override the pageableResolver() or sortResolver() methods and import your customized configuration file instead of using the @Enable-annotation.

In case you need multiple Pageable or Sort instances to be resolved from the request (for multiple tables, for example) you can use Spring’s @Qualifier annotation to distinguish one from another. The request parameters then have to be prefixed with ${qualifier}_. So for a method signature like this:

public String showUsers(Model model,

     @Qualifier("foo") Pageable first,
     @Qualifier("bar") Pageable second) { … }

you have to populate foo_page and bar_page etc.

The default Pageable handed into the method is equivalent to a new PageRequest(0, 20) but can be customized using the @PageableDefaults annotation on the Pageable parameter.

PagedResourcesAssembler

@Controller
class PersonController {

  @Autowired PersonRepository repository;

  @RequestMapping(value = "/persons", method = RequestMethod.GET)
  HttpEntity<PagedResources<Person>> persons(Pageable pageable,
    PagedResourcesAssembler assembler) {

    Page<Person> persons = repository.findAll(pageable);
    return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
  }
}

{ "links" : [ { "rel" : "next",
                "href" : "http://localhost:8080/persons?page=1&size=20 }
  ],
  "content" : [
     … // 20 Person instances rendered here
  ],
  "pageMetadata" : {
    "size" : 20,
    "totalElements" : 30,
    "totalPages" : 2,
    "number" : 0
  }
}

@QuerydslPredicate

?firstname=Dave&lastname=Matthews

-->

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))

Resolve query string arguments to matching Predicate for User.

@Controller
class UserController {

  @Autowired UserRepository repository;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,    
          Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {

    model.addAttribute("users", repository.findAll(predicate, pageable));

    return "index";
  }
}

Object on simple properties as eq.

Object on collection like properties as contains.

Collection on simple properties as in.

QuerydslBinderCustomizer QueryDslPredicateExecutor

interface UserRepository extends CrudRepository<User, String>,
                                 QueryDslPredicateExecutor<User>,                
                                 QuerydslBinderCustomizer<QUser> {               

  @Override
  default public void customize(QuerydslBindings bindings, QUser user) {

    bindings.bind(user.username).first((path, value) -> path.contains(value))    
    bindings.bind(String.class)
      .first((StringPath path, String value) -> path.containsIgnoreCase(value)); 
    bindings.excluding(user.password);                                           
  }
}
  • QueryDslPredicateExecutor provides access to specific finder methods for Predicate.
  • QuerydslBinderCustomizer defined on the repository interface will be automatically picked up and shortcuts @QuerydslPredicate(bindings=…​).
  • Define the binding for the username property to be a simple contains binding.
  • Define the default binding for String properties to be a case insensitive contains match.
  • Exclude the password property from Predicate resolution.

@RequestParam MultiValueMap<String, String> parameters

@Version

  • Intially insert document. version is set to 0.
  • Load the just inserted document version is still 0.
  • Update document with version = 0. Set the lastname and bump version to 1.
  • Try to update previously loaded document sill having version = 0 fails with OptimisticLockingFailureException as the current version is 1.

query

List<Person> result = mongoTemplate.find(query(where("age").lt(50).and("accounts.balance").gt(1000.00d)), Person.class);
BasicQuery query = new BasicQuery("{ age : { $lt : 50 }, accounts.balance : { $gt : 1000.00 }}");
List<Person> result = mongoTemplate.find(query, Person.class);

Methods for the Criteria class

Criteria all (Object o) Creates a criterion using the $all operator

Criteria and (String key) Adds a chained Criteria with the specified key to the current Criteria and returns the newly created one

Criteria andOperator (Criteria…​ criteria) Creates an and query using the $and operator for all of the provided criteria (requires MongoDB 2.0 or later)

Criteria elemMatch (Criteria c) Creates a criterion using the $elemMatch operator

Criteria exists (boolean b) Creates a criterion using the $exists operator

Criteria gt (Object o) Creates a criterion using the $gt operator

Criteria gte (Object o) Creates a criterion using the $gte operator

Criteria in (Object…​ o) Creates a criterion using the $in operator for a varargs argument.

Criteria in (Collection<?> collection) Creates a criterion using the $in operator using a collection

Criteria is (Object o) Creates a criterion using the $is operator

Criteria lt (Object o) Creates a criterion using the $lt operator

Criteria lte (Object o) Creates a criterion using the $lte operator

Criteria mod (Number value, Number remainder) Creates a criterion using the $mod operator

Criteria ne (Object o) Creates a criterion using the $ne operator

Criteria nin (Object…​ o) Creates a criterion using the $nin operator

Criteria norOperator (Criteria…​ criteria) Creates an nor query using the $nor operator for all of the provided criteria

Criteria not () Creates a criterion using the $not meta operator which affects the clause directly following

Criteria orOperator (Criteria…​ criteria) Creates an or query using the $or operator for all of the provided criteria

Criteria regex (String re) Creates a criterion using a $regex

Criteria size (int s) Creates a criterion using the $size operator

Criteria type (int t) Creates a criterion using the $type operator

There are also methods on the Criteria class for geospatial queries. Here is a listing but look at the section on GeoSpatial Queries to see them in action.

Criteria within (Circle circle) Creates a geospatial criterion using $geoWithin $center operators.

Criteria within (Box box) Creates a geospatial criterion using a $geoWithin $box operation.

Criteria withinSphere (Circle circle) Creates a geospatial criterion using $geoWithin $center operators.

Criteria near (Point point) Creates a geospatial criterion using a $near operation

Criteria nearSphere (Point point) Creates a geospatial criterion using $nearSphere$center operations. This is only available for MongoDB 1.7 and higher.

Criteria minDistance (double minDistance) Creates a geospatial criterion using the $minDistance operation, for use with $near.

Criteria maxDistance (double maxDistance) Creates a geospatial criterion using the $maxDistance operation, for use with $near.

The Query class has some additional methods used to provide options for the query.

Methods for the Query class

Query addCriteria (Criteria criteria) used to add additional criteria to the query

Field fields () used to define fields to be included in the query results

Query limit (int limit) used to limit the size of the returned results to the provided limit (used for paging)

Query skip (int skip) used to skip the provided number of documents in the results (used for paging)

Query with (Sort sort) used to provide sort definition for the results

9.6.2. Methods for querying for documents

The query methods need to specify the target type T that will be returned and they are also overloaded with an explicit collection name for queries that should operate on a collection other than the one indicated by the return type.

findAll Query for a list of objects of type T from the collection.

findOne Map the results of an ad-hoc query on the collection to a single instance of an object of the specified type.

findById Return an object of the given id and target class.

find Map the results of an ad-hoc query on the collection to a List of the specified type.

findAndRemove Map the results of an ad-hoc query on the collection to a single instance of an object of the specified type. The first document that matches the query is returned and also removed from the collection in the database.

GeoSpatial 地理

GeoJSON

[1]

Full Text Queries

Query query = TextQuery.searching(new TextCriteria().matchingAny("coffee", "cake")).sortByScore();
List<Document> page = template.find(query, Document.class);
// search for 'coffee' and not 'cake'
TextQuery.searching(new TextCriteria().matching("coffee").matching("-cake"));
TextQuery.searching(new TextCriteria().matching("coffee").notMatching("cake"));
// search for phrase 'coffee cake'
TextQuery.searching(new TextCriteria().matching("\"coffee cake\""));
TextQuery.searching(new TextCriteria().phrase("coffee cake"));

Query by Example QueryByExampleExecutor

Person person = new Person();                         
person.setFirstname("Dave");                          

Example<Person> example = Example.of(person); 


Person person = new Person();                          
person.setFirstname("Dave");                           

ExampleMatcher matcher = ExampleMatcher.matching()     
  .withIgnorePaths("lastname")                         
  .withIncludeNullValues()                             
  .withStringMatcherEnding();                          

Example<Person> example = Example.of(person, matcher);

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

StringMatcher

Matching Logical result

DEFAULT (case-sensitive)

{"firstname" : firstname}

DEFAULT (case-insensitive)

{"firstname" : { $regex: firstname, $options: 'i'}}

EXACT (case-sensitive)

{"firstname" : { $regex: /^firstname$/}}

EXACT (case-insensitive)

{"firstname" : { $regex: /^firstname$/, $options: 'i'}}

STARTING (case-sensitive)

{"firstname" : { $regex: /^firstname/}}

STARTING (case-insensitive)

{"firstname" : { $regex: /^firstname/, $options: 'i'}}

ENDING (case-sensitive)

{"firstname" : { $regex: /firstname$/}}

ENDING (case-insensitive)

{"firstname" : { $regex: /firstname$/, $options: 'i'}}

CONTAINING (case-sensitive)

{"firstname" : { $regex: /.*firstname.*/}}

CONTAINING (case-insensitive)

{"firstname" : { $regex: /.*firstname.*/, $options: 'i'}}

REGEX (case-sensitive)

{"firstname" : { $regex: /firstname/}}

REGEX (case-insensitive)

{"firstname" : { $regex: /firstname/, $options: 'i'}}

Index and Collection management

public interface IndexOperations {

  void ensureIndex(IndexDefinition indexDefinition);

  void dropIndex(String name);

  void dropAllIndexes();

  void resetIndexCache();

  List<IndexInfo> getIndexInfo();
}
mongoTemplate.indexOps(Person.class).ensureIndex(new Index().on("name",Order.ASCENDING));
mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location"));
template.indexOps(Person.class).ensureIndex(new Index().on("age", Order.DESCENDING).unique(Duplicates.DROP));

List<IndexInfo> indexInfoList = template.indexOps(Person.class).getIndexInfo();

// Contains
// [IndexInfo [fieldSpec={_id=ASCENDING}, name=_id_, unique=false, dropDuplicates=false, sparse=false],
//  IndexInfo [fieldSpec={age=DESCENDING}, name=age_-1, unique=true, dropDuplicates=true, sparse=false]]

Methods for working with a Collection

DBCollection collection = null; if (!mongoTemplate.getCollectionNames().contains("MyNewCollection")) {

   collection = mongoTemplate.createCollection("MyNewCollection");

}

mongoTemplate.dropCollection("MyNewCollection");

getCollectionNames Returns a set of collection names.

collectionExists Check to see if a collection with a given name exists.

createCollection Create an uncapped collection

dropCollection Drop the collection

getCollection Get a collection by name, creating it if it doesn’t exist.

Methods for executing commands

You can also get at the MongoDB driver’s DB.command( ) method using the executeCommand(…) methods on MongoTemplate. These will also perform exception translation into Spring’s DataAccessException hierarchy.

CommandResult executeCommand (DBObject command) Execute a MongoDB command.

CommandResult executeCommand (String jsonCommand) Execute the a MongoDB command expressed as a JSON string.

Lifecycle Events

public class BeforeConvertListener extends AbstractMongoEventListener<Person> {
  @Override
  public void onBeforeConvert(BeforeConvertEvent<Person> event) {
    ... does some auditing manipulation, set timestamps, whatever ...
  }
}

public class BeforeSaveListener extends AbstractMongoEventListener<Person> {
  @Override
  public void onBeforeSave(BeforeSaveEvent<Person> event) {
    … change values, delete them, whatever …
  }
}

onBeforeConvert - called in MongoTemplate insert, insertList and save operations before the object is converted to a DBObject using a MongoConveter.

onBeforeSave - called in MongoTemplate insert, insertList and save operations before inserting/saving the DBObject in the database.

onAfterSave - called in MongoTemplate insert, insertList and save operations after inserting/saving the DBObject in the database.

onAfterLoad - called in MongoTemplate find, findAndRemove, findOne and getCollection methods after the DBObject is retrieved from the database.

onAfterConvert - called in MongoTemplate find, findAndRemove, findOne and getCollection methods after the DBObject retrieved from the database was converted to a POJO.

Remodelling data

interface RenamedProperty {    

  String getFirstName();       

  @Value("#{target.lastName}")
  String getName();            
}

interface FullNameAndCountry {

  @Value("#{target.firstName} #{target.lastName}")
  String getFullName();

  @Value("#{target.address.country}")
  String getCountry();
}

interface PasswordProjection {
  @Value("#{(target.password == null || target.password.empty) ? null : '******'}")
  String getPassword();
}

Auditing

We provide @CreatedBy, @LastModifiedBy to capture the user who created or modified the entity as well as @CreatedDate and @LastModifiedDate to capture the point in time this happened.

class SpringSecurityAuditorAware implements AuditorAware<User> {

  public User getCurrentAuditor() {

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication == null || !authentication.isAuthenticated()) {
      return null;
    }

    return ((MyUserDetails) authentication.getPrincipal()).getUser();
  }
}


@Configuration
@EnableMongoAuditing
class Config {

  @Bean
  public AuditorAware<AuditableUser> myAuditorProvider() {
      return new AuditorAwareImpl();
  }
}

MappingMongoConverter LoggingEventListener MongoMappingEvent UserCredentials

@Configuration
public class GeoSpatialAppConfig extends AbstractMongoConfiguration {

  @Bean
  public Mongo mongo() throws Exception {
    return new Mongo("localhost");
  }

  @Override
  public String getDatabaseName() {
    return "database";
  }

  @Override
  public String getMappingBasePackage() {
    return "com.bigbank.domain";
  }

  // the following are optional


  @Bean
  @Override
  public CustomConversions customConversions() throws Exception {
    List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
    converterList.add(new org.springframework.data.mongodb.test.PersonReadConverter());
    converterList.add(new org.springframework.data.mongodb.test.PersonWriteConverter());
    return new CustomConversions(converterList);
  }

  @Bean
  public LoggingEventListener<MongoMappingEvent> mappingEventsListener() {
    return new LoggingEventListener<MongoMappingEvent>();
  }
}

Logging support

level, name, applicationId, timestamp, properties, traceback, and message

log4j.rootCategory=INFO, mongo

log4j.appender.mongo.username = admin
log4j.appender.mongo.password = test
log4j.appender.mongo.authenticationDatabase = logs

log4j.appender.mongo=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender
log4j.appender.mongo.layout=org.apache.log4j.PatternLayout
log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.mongo.host = localhost
log4j.appender.mongo.port = 27017
log4j.appender.mongo.database = logs
log4j.appender.mongo.collectionPattern = %X{year}%X{month}
log4j.appender.mongo.applicationId = my.application
log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE

log4j.category.org.apache.activemq=ERROR
log4j.category.org.springframework.batch=DEBUG
log4j.category.org.springframework.data.document.mongodb=DEBUG
log4j.category.org.springframework.transaction=INFO