在使用Spring的过程中, 可以定义自己的Repository接口,并不需要完成具体的实现,Spring会帮助创建具体的实例.
如下面这样的对Mongo进行Query的Repository:
@Repository
public interface UserRepository extends MongoRepository<User, String> {
Optional<User> findOneByActivationKey(String activationKey);
List<User> findAllByActivatedIsFalseAndCreatedDateBefore(Instant dateTime);
Optional<User> findOneByResetKey(String resetKey);
Optional<User> findOneByEmail(String email);
Optional<User> findOneByLogin(String login);
Page<User> findAllByLoginNot(Pageable pageable, String login);
}
问题来了,方法名应该遵循什么样的规则,Spring才能帮助实现呢。通过对Spring源码的分析,找到了对应的规则。
下面的例子是基于对Mongo的分析,Sql的基本类似,而且规则也是一样的。
源码库:spring-data-mongodb, spring-data-commons;
从MongoRepositoryFactory.java开始,这个Factory会去创建Repository的实例:
public class MongoRepositoryFactory extends RepositoryFactorySupport {
}
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
public <T> T getRepository(Class<T> repositoryInterface) {
return getRepository(repositoryInterface, null);
}
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {
...
// Create proxy
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
...
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
...
}
}
这里用ProxyFacotry实例,且添加了一个QueryExecutorMethodInterceptor. Proxy和MethodInterceptor,熟悉Javassist的都知道对应的用途, SpringAOP也有类似的实现,这里不赘述。
下面看QueryExecutorMethodInterceptor实现:
public class QueryExecutorMethodInterceptor implements MethodInterceptor {
public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformation, Object customImplementation,
Object target) {
...
QueryLookupStrategy lookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey, RepositoryFactorySupport.this.evaluationContextProvider);
...
SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory();
...
}
}
SpelAwareProxyProjectionFactory 以后再研究。 现在主要看一下QueryLookupStrategy的实现MongoQueryLookupStrategy。
private static class MongoQueryLookupStrategy implements QueryLookupStrategy {
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries) {
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, mappingContext);
String namedQueryName = queryMethod.getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
return new StringBasedMongoQuery(namedQuery, queryMethod, operations, EXPRESSION_PARSER,
evaluationContextProvider);
} else if (queryMethod.hasAnnotatedQuery()) {
return new StringBasedMongoQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
} else {
return new PartTreeMongoQuery(queryMethod, operations);
}
}
}
如果没有用注解或xml定义query. 则会创建一个PartTreeMongoQuery实例。
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations) {
super(method, mongoOperations);
this.processor = method.getResultProcessor();
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());
this.isGeoNearQuery = method.isGeoNearQuery();
this.context = mongoOperations.getConverter().getMappingContext();
}
解析创建的Repository里的方法主要是由PartTree来处理的。
PartTree里将方法分割成Subject和Predicate。以“By”为分割。
Subject源码列出来支持的操作,如”find|read|get|query|stream”, “count”,”exists”,”delete|remove”等。
而Predicate里则将字符用“Or”分割成OrPart, 而OrPart则用”And”分割成Part,Part源代码里列出了可用的关键词:
public static enum Type {
BETWEEN(2, "IsBetween", "Between"), IS_NOT_NULL(0, "IsNotNull", "NotNull"), IS_NULL(0, "IsNull", "Null"), LESS_THAN(
"IsLessThan", "LessThan"), LESS_THAN_EQUAL("IsLessThanEqual", "LessThanEqual"), GREATER_THAN("IsGreaterThan",
"GreaterThan"), GREATER_THAN_EQUAL("IsGreaterThanEqual", "GreaterThanEqual"), BEFORE("IsBefore", "Before"), AFTER(
"IsAfter", "After"), NOT_LIKE("IsNotLike", "NotLike"), LIKE("IsLike", "Like"), STARTING_WITH("IsStartingWith",
"StartingWith", "StartsWith"), ENDING_WITH("IsEndingWith", "EndingWith", "EndsWith"), NOT_CONTAINING(
"IsNotContaining", "NotContaining", "NotContains"), CONTAINING("IsContaining", "Containing", "Contains"), NOT_IN(
"IsNotIn", "NotIn"), IN("IsIn", "In"), NEAR("IsNear", "Near"), WITHIN("IsWithin", "Within"), REGEX(
"MatchesRegex", "Matches", "Regex"), EXISTS(0, "Exists"), TRUE(0, "IsTrue", "True"), FALSE(0, "IsFalse",
"False"), NEGATING_SIMPLE_PROPERTY("IsNot", "Not"), SIMPLE_PROPERTY("Is", "Equals");
...
}