1. 程式人生 > >spring data mongodb學習以及為repository提供可擴充套件的自定義方法

spring data mongodb學習以及為repository提供可擴充套件的自定義方法

Spring Data 概述

Spring Data : Spring 的一個子專案。用於簡化資料庫訪問,支援NoSQL 和 關係資料儲存。其主要目標是使資料庫的訪問變得方便快捷。
SpringData 專案所支援 NoSQL 儲存:
MongoDB (文件資料庫)
Neo4j(圖形資料庫)
Redis(鍵/值儲存)
Hbase(列族資料庫)
SpringData 專案所支援的關係資料儲存技術:
JDBC
JPA

Spring Data mongodb 概述

Spring Data mongodb  : 致力於減少資料訪問層 (DAO) 的開發量. 開發者唯一要做的,就只是宣告持久層的介面,其他都交給 Spring Data mongodb 來幫你完成!
框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個 customerRepository.findByNameAndAddressNumberAndAccountsAccountName(name, number,accountName)  這樣一個方法宣告,大致應該能判斷出這是根據給定條件 查詢出滿足條件的 User  物件。Spring Data mongodb 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。

使用 Spring Data JPA 進行持久層開發需要的四個步驟:

1、配置 Spring 整合 Mongodb

package com.dhb.springmvc.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * Created by ${denghb} on 2016/7/31.
 */
public class DhbWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // return new Class<?>[0];
        return new Class [] { WebConfig.class, C3P0DataSourceBuilder.class, MongodbConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}


2、讓 Spring 為宣告的介面建立代理物件。Spring 初始化容器時將會掃描 base-package  指定的包目錄及其子目錄,為繼承 Repository 或其子介面的介面建立代理物件,並將代理物件註冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該物件。

package com.dhb.springmvc.config;

import com.dhb.springmvc.base.support.CustomMongoRepositoryFactoryBean;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

/**
 * Created by ${denghb} on 2016/8/5.
 */
@EnableMongoRepositories(
        basePackages = {"com.dhb.springmvc"},
        repositoryFactoryBeanClass = CustomMongoRepositoryFactoryBean.class
)
@EnableMongoAuditing
public class MongodbConfig extends AbstractMongoConfiguration {
    @Override
    protected String getDatabaseName() {
        return "business";
    }

    @Override
    public Mongo mongo() throws Exception {
        return new MongoClient("127.0.0.1");
    }
}
在這裡沒有配置MongoTemplate,但是在後續的repository裡面我們卻可以注入進來,原因是繼承了AbstractMongoConfiguration ,該抽象類對其進行了實現。

MongoTemplate是資料庫和程式碼之間的介面,對資料庫的操作都在它裡面。
注:MongoTemplate是執行緒安全的。
MongoTemplate實現了interface MongoOperations。

MongoDB documents和domain classes之間的對映關係是通過實現了MongoConverter這個interface的類來實現的。
MongoTemplate提供了非常多的操作MongoDB的方法。 它是執行緒安全的,可以在多執行緒的情況下使用。
MongoTemplate實現了MongoOperations介面, 此介面定義了眾多的操作方法如"find", "findAndModify", "findOne", "insert", "remove", "save", "update" and "updateMulti"等。
它轉換domain object為DBObject,並提供了Query, Criteria, and Update等流式API。
預設轉換類為MongoMappingConverter。
3、宣告持久層的介面,該介面繼承  Repository
Repository 是一個標記型介面,它不包含任何方法,如必要,Spring Data 可實現 Repository 其他子介面,其中定義了一些常用的增刪改查,以及分頁相關的方法。
在介面中宣告需要的方法

package com.dhb.springmvc.base.repository;

import com.dhb.springmvc.base.entity.BaseEntity;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;

/**
 * Created by ${denghb} on 2016/8/5.
 */
@NoRepositoryBean
public interface BaseRepository<T extends BaseEntity, ID extends Serializable>
        extends MongoRepository<T, ID>, BaseRepositoryEnhance<T, ID> {
}

下面是可擴張的repository
package com.dhb.springmvc.base.repository;

import com.dhb.springmvc.base.entity.BaseEntity;

import java.io.Serializable;

/**
 * Created by ${denghb} on 2016/8/5.
 */
public interface BaseRepositoryEnhance<T extends BaseEntity, ID extends Serializable> {

    T softDelete(ID id);
}

package com.dhb.springmvc.base.repository.impl;

import com.dhb.springmvc.base.entity.BaseEntity;
import com.dhb.springmvc.base.repository.BaseRepositoryEnhance;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;

import java.io.Serializable;

/**
 * Created by ${denghb} on 2016/8/5.
 */
public class BaseRepositoryImpl<T extends BaseEntity, ID extends Serializable>
        extends SimpleMongoRepository<T, ID>
        implements BaseRepositoryEnhance<T, ID> {

    private final MongoOperations mongoOperations;

    public BaseRepositoryImpl(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
        super(metadata, mongoOperations);
        this.mongoOperations = mongoOperations;
    }

    @Override
    public T softDelete(ID id) {
        return null;
    }
}

public BaseRepositoryImpl(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
        super(metadata, mongoOperations);
        this.mongoOperations = mongoOperations;
    }

這段程式碼必須實現,因為父類有一個有參的構造方法,沒有無參的構造方法。


父類沒有無參建構函式時,子類繼承時,建構函式中必須顯式呼叫父類構造方法,並且傳遞對應所需要的引數。 一個類如果顯式的定義了帶參建構函式,那麼預設無參建構函式自動失效 。

4、Spring Data 將根據給定的策略(具體策略稍後講解)來為其生成實現程式碼。

package com.dhb.springmvc.base.support;

import com.dhb.springmvc.base.entity.BaseEntity;
import com.dhb.springmvc.base.repository.impl.BaseRepositoryImpl;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.mongodb.repository.support.QueryDslMongoRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import java.io.Serializable;

import static org.springframework.data.querydsl.QueryDslUtils.QUERY_DSL_PRESENT;

/**
 * 用於生成自擴充套件的Repository方法,比如softDelete
 * Created by ${denghb} on 2016/8/5.
 */
public class CustomMongoRepositoryFactoryBean<T extends MongoRepository<S, ID>, S extends BaseEntity, ID extends Serializable>
        extends MongoRepositoryFactoryBean<T, S, ID> {

    @Override
    protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
        return new LCRRepositoryFactory(operations);
    }

    private static class LCRRepositoryFactory<S extends BaseEntity, ID extends Serializable> extends MongoRepositoryFactory {
        private final MongoOperations mongoOperations;

        public LCRRepositoryFactory(MongoOperations mongoOperations) {
            super(mongoOperations);
            this.mongoOperations = mongoOperations;
        }

        @Override
        protected Object getTargetRepository(RepositoryMetadata metadata) {
            Class<?> repositoryInterface = metadata.getRepositoryInterface();
            MongoEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
            if (isQueryDslRepository(repositoryInterface)) {
                return new QueryDslMongoRepository(entityInformation, mongoOperations);
            } else {
                return new BaseRepositoryImpl<S, ID>((MongoEntityInformation<S, ID>) entityInformation, this.mongoOperations);
            }
        }

        private static boolean isQueryDslRepository(Class<?> repositoryInterface) {
            return QUERY_DSL_PRESENT && QueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface);
        }

        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return isQueryDslRepository(metadata.getRepositoryInterface()) ? QueryDslMongoRepository.class
                    : BaseRepositoryImpl.class;
        }
    }
}

Repository 介面概述
Repository 介面是 Spring Data 的一個核心介面,它不提供任何方法,開發者需要在自己定義的介面中宣告需要的方法 
    public interface Repository<T, ID extends Serializable> { } 
Spring Data可以讓我們只定義介面,只要遵循 Spring Data的規範,就無需寫實現類。  
與繼承 Repository 等價的一種方式,就是在持久層介面上使用 @RepositoryDefinition 註解,併為其指定 domainClass 和 idClass 屬性。如下兩種方式是完全等價的
Repository 的子介面
基礎的 Repository 提供了最基本的資料訪問功能,其幾個子介面則擴充套件了一些功能。它們的繼承關係如下: 
Repository: 僅僅是一個標識,表明任何繼承它的均為倉庫介面類
CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法 
PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法 
MongoRepository: 繼承 PagingAndSortingRepository,實現一組 mongodb規範相關的方法 
自定義的 XxxxRepository 需要繼承 MongoRepository,這樣的 XxxxRepository 介面就具備了通用的資料訪問控制層的能力。

entity實體類:

package com.dhb.springmvc.entity;

import com.dhb.springmvc.base.entity.BaseEntity;
import com.dhb.springmvc.entity.support.Account;
import com.dhb.springmvc.entity.support.Address;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.List;

/**
 * Created by ${denghb} on 2016/8/5.
 */
@Document
public class Customer extends BaseEntity {
    private String name;
    private List<Account> accounts;
    private Address address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

package com.dhb.springmvc.entity.support;

import com.dhb.springmvc.base.entity.BaseEntity;

/**
 * Created by ${denghb} on 2016/8/5.
 */
public class Account extends BaseEntity {
    private String accountName;

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }
}

package com.dhb.springmvc.entity.support;

import com.dhb.springmvc.base.entity.BaseEntity;

/**
 * Created by ${denghb} on 2016/8/5.
 */
public class Address extends BaseEntity {
    private String number;
    private String street;
    private String town;
    private String postcode;

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getTown() {
        return town;
    }

    public void setTown(String town) {
        this.town = town;
    }

    public String getPostcode() {
        return postcode;
    }

    public void setPostcode(String postcode) {
        this.postcode = postcode;
    }
}

repository類:
package com.dhb.springmvc.repository;

import com.dhb.springmvc.entity.Customer;

import java.util.List;

/**
 * Created by ${denghb} on 2016/8/8.
 */
public interface CustomerRepositoryEnhance {
    public List<Customer> search(String keyword, String direction, String sort, int page, int size);
}

package com.dhb.springmvc.repository.impl;

import com.dhb.springmvc.entity.Customer;
import com.dhb.springmvc.repository.CustomerRepositoryEnhance;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import javax.annotation.Resource;
import java.util.List;

/**
 * Created by ${denghb} on 2016/8/8.
 */
public class CustomerRepositoryImpl implements CustomerRepositoryEnhance {

    @Resource
    private MongoTemplate mongoTemplate;

    @Override
    public List<Customer> search(String keyword, String direction, String sort, int page, int size) {
        Query query = new Query();
        Criteria c = new Criteria();
        query.addCriteria(Criteria.where("name").is(keyword));
        query.with(new Sort(Sort.Direction.valueOf(direction), sort));
        query.with(new PageRequest(page - 1, size));
        return mongoTemplate.find(query, Customer.class);
    }
}

package com.dhb.springmvc.repository;

import com.dhb.springmvc.base.repository.BaseRepository;
import com.dhb.springmvc.entity.Customer;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * Created by ${denghb} on 2016/8/5.
 */
@Repository
public interface CustomerRepository extends BaseRepository<Customer, String>, CustomerRepositoryEnhance {

    List<Customer> findByNameAndAddressNumberAndAccountsAccountName(
            String name, String number, String accountName);
}

service類:
package com.dhb.springmvc.service;

import com.dhb.springmvc.entity.Customer;
import com.dhb.springmvc.repository.CustomerRepository;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * Created by ${denghb} on 2016/8/5.
 */
@Service
public class CustomerService {

    @Resource
    private CustomerRepository customerRepository;

    public void insertCustomer(Customer customer) {
        customerRepository.save(customer);
    }

    public List<Customer> findAllCustomers() {
        return customerRepository.findAll();
    }

    public void dropCustomerCollection() {
        customerRepository.deleteAll();
    }

    public List<Customer> findByNameAndAddressNumberAndAccountsAccountName(String name, String number, String accountName) {
        return customerRepository.findByNameAndAddressNumberAndAccountsAccountName(name, number,accountName);
    }

    public List<Customer> search(String keyword, String direction, String sort, int page, int size) {
        return customerRepository.search(keyword, direction, sort, page, size);
    }
}

restController類:
package com.dhb.springmvc.controller;

import com.dhb.springmvc.entity.Customer;
import com.dhb.springmvc.service.CustomerService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
 * Created by ${denghb} on 2016/8/5.
 */
@RestController
@RequestMapping(value = "/v0.1/customer")
public class CustomerController {
    @Resource
    private CustomerService customerService;

    @RequestMapping(value = "/get_all", method = RequestMethod.GET)
    public Object findAllCustomerDetail() {
        return customerService.findAllCustomers();
    }

    @RequestMapping(value = "/get_by/{name}/{number}/{accountName}", method = RequestMethod.GET)
    public Object findByNameAndAddressNumberAndAccountsAccountName(@PathVariable String name, @PathVariable String number, @PathVariable String accountName) {
        return customerService.findByNameAndAddressNumberAndAccountsAccountName(name, number, accountName);
    }
    @RequestMapping(value = "/search_by", method = RequestMethod.GET)
    public List<Customer> search(@RequestParam(value= "query", defaultValue = "") String keyword,
                                 @RequestParam(value= "direction", defaultValue = "DESC") String direction,
                                 @RequestParam(value = "sort", defaultValue = "name") String sort,
                                 @RequestParam(value = "page", defaultValue = "1") int page,
                                 @RequestParam(value = "size", defaultValue = "30") int size) {

        return customerService.search(keyword, direction, sort, page, size);
    }
}

1、為某一個 Repository 上新增自定義方法

1)定義一個介面: 宣告要新增的自實現的方法

2)提供該介面的實現類: 類名需在要宣告的 Repository 後新增 Impl, 並實現方法

3)宣告 Repository 介面, 並繼承 1) 宣告的介面

注意: 預設情況下, Spring Data 會在 base-package 中查詢 "介面名Impl" 作為實現類. 也可以通過 repository-impl-postfix 聲明後綴. 

2、為所有的 Repository 都新增自實現的方法

1)宣告一個介面, 在該介面中宣告需要自定義的方法

2)提供 1) 所宣告的介面的實現類. 且繼承 SimpleJpaRepository, 並提供方法的實現

3)宣告 Repository 介面, 並繼承 1) 宣告的介面, 且該介面需要繼承 Spring Data 的 Repository.

4)定義 MongoRepositoryFactoryBean的實現類, 使其生成 1) 定義的介面實現類的物件

5)修改 mongodb repository節點的 factory-class 屬性指向 3) 的全類名

注意: 全域性的擴充套件實現類不要用 Imp 作為字尾名, 或為全域性擴充套件介面新增 @NoRepositoryBean 註解告知  Spring Data: Spring Data 不把其作為 Repository

專案工程目錄如下(部分配置適用於別的測試,可以無視):