|
@@ -0,0 +1,376 @@
|
|
|
+package com.baomidou.mybatisplus.core.override;
|
|
|
+
|
|
|
+import java.lang.reflect.Array;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.lang.reflect.ParameterizedType;
|
|
|
+import java.lang.reflect.Type;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import org.apache.ibatis.annotations.Flush;
|
|
|
+import org.apache.ibatis.annotations.MapKey;
|
|
|
+import org.apache.ibatis.binding.BindingException;
|
|
|
+import org.apache.ibatis.cursor.Cursor;
|
|
|
+import org.apache.ibatis.mapping.MappedStatement;
|
|
|
+import org.apache.ibatis.mapping.SqlCommandType;
|
|
|
+import org.apache.ibatis.mapping.StatementType;
|
|
|
+import org.apache.ibatis.reflection.MetaObject;
|
|
|
+import org.apache.ibatis.reflection.ParamNameResolver;
|
|
|
+import org.apache.ibatis.reflection.TypeParameterResolver;
|
|
|
+import org.apache.ibatis.session.Configuration;
|
|
|
+import org.apache.ibatis.session.ResultHandler;
|
|
|
+import org.apache.ibatis.session.RowBounds;
|
|
|
+import org.apache.ibatis.session.SqlSession;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.pagination.Page;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author ming
|
|
|
+ * @since 2018/6/9
|
|
|
+ */
|
|
|
+public class PageMapperMethod {
|
|
|
+
|
|
|
+ private final PageMapperMethod.SqlCommand command;
|
|
|
+ private final PageMapperMethod.MethodSignature method;
|
|
|
+
|
|
|
+ public PageMapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
|
|
|
+ this.command = new PageMapperMethod.SqlCommand(config, mapperInterface, method);
|
|
|
+ this.method = new PageMapperMethod.MethodSignature(config, mapperInterface, method);
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ public Object execute(SqlSession sqlSession, Object[] args) {
|
|
|
+ Object result;
|
|
|
+ switch (command.getType()) {
|
|
|
+ case INSERT: {
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ result = rowCountResult(sqlSession.insert(command.getName(), param));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case UPDATE: {
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ result = rowCountResult(sqlSession.update(command.getName(), param));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case DELETE: {
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ result = rowCountResult(sqlSession.delete(command.getName(), param));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case SELECT:
|
|
|
+ if (method.returnsVoid() && method.hasResultHandler()) {
|
|
|
+ executeWithResultHandler(sqlSession, args);
|
|
|
+ result = null;
|
|
|
+ } else if (method.returnsMany()) {
|
|
|
+ result = executeForMany(sqlSession, args);
|
|
|
+ } else if (method.returnsMap()) {
|
|
|
+ result = executeForMap(sqlSession, args);
|
|
|
+ } else if (method.returnsCursor()) {
|
|
|
+ result = executeForCursor(sqlSession, args);
|
|
|
+ } else {
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ if (Page.class.isAssignableFrom(method.getReturnType()) && args != null
|
|
|
+ && Page.class.isAssignableFrom(args[0].getClass())) {
|
|
|
+ List<Object> o = (List<Object>) executeForMany2(sqlSession, args);
|
|
|
+ result = ((Page) args[0]).setRecords(o);
|
|
|
+ } else {
|
|
|
+ result = sqlSession.selectOne(command.getName(), param);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case FLUSH:
|
|
|
+ result = sqlSession.flushStatements();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new BindingException("Unknown execution method for: " + command.getName());
|
|
|
+ }
|
|
|
+ if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
|
|
|
+ throw new BindingException("Mapper method '" + command.getName()
|
|
|
+ + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private <E> Object executeForMany2(SqlSession sqlSession, Object[] args) {
|
|
|
+ List<E> result;
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ if (method.hasRowBounds()) {
|
|
|
+ RowBounds rowBounds = method.extractRowBounds(args);
|
|
|
+ result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
|
|
|
+ } else {
|
|
|
+ result = sqlSession.<E>selectList(command.getName(), param);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object rowCountResult(int rowCount) {
|
|
|
+ final Object result;
|
|
|
+ if (method.returnsVoid()) {
|
|
|
+ result = null;
|
|
|
+ } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
|
|
|
+ result = rowCount;
|
|
|
+ } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
|
|
|
+ result = (long) rowCount;
|
|
|
+ } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
|
|
|
+ result = rowCount > 0;
|
|
|
+ } else {
|
|
|
+ throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
|
|
|
+ MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
|
|
|
+ if (!StatementType.CALLABLE.equals(ms.getStatementType())
|
|
|
+ && void.class.equals(ms.getResultMaps().get(0).getType())) {
|
|
|
+ throw new BindingException("method " + command.getName()
|
|
|
+ + " needs either a @ResultMap annotation, a @ResultType annotation,"
|
|
|
+ + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
|
|
|
+ }
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ if (method.hasRowBounds()) {
|
|
|
+ RowBounds rowBounds = method.extractRowBounds(args);
|
|
|
+ sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
|
|
|
+ } else {
|
|
|
+ sqlSession.select(command.getName(), param, method.extractResultHandler(args));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
|
|
|
+ List<E> result;
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ if (method.hasRowBounds()) {
|
|
|
+ RowBounds rowBounds = method.extractRowBounds(args);
|
|
|
+ result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
|
|
|
+ } else {
|
|
|
+ result = sqlSession.<E>selectList(command.getName(), param);
|
|
|
+ }
|
|
|
+ // issue #510 Collections & arrays support
|
|
|
+ if (!method.getReturnType().isAssignableFrom(result.getClass())) {
|
|
|
+ if (method.getReturnType().isArray()) {
|
|
|
+ return convertToArray(result);
|
|
|
+ } else {
|
|
|
+ return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
|
|
|
+ Cursor<T> result;
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ if (method.hasRowBounds()) {
|
|
|
+ RowBounds rowBounds = method.extractRowBounds(args);
|
|
|
+ result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
|
|
|
+ } else {
|
|
|
+ result = sqlSession.<T>selectCursor(command.getName(), param);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
|
|
|
+ Object collection = config.getObjectFactory().create(method.getReturnType());
|
|
|
+ MetaObject metaObject = config.newMetaObject(collection);
|
|
|
+ metaObject.addAll(list);
|
|
|
+ return collection;
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private <E> Object convertToArray(List<E> list) {
|
|
|
+ Class<?> arrayComponentType = method.getReturnType().getComponentType();
|
|
|
+ Object array = Array.newInstance(arrayComponentType, list.size());
|
|
|
+ if (arrayComponentType.isPrimitive()) {
|
|
|
+ for (int i = 0; i < list.size(); i++) {
|
|
|
+ Array.set(array, i, list.get(i));
|
|
|
+ }
|
|
|
+ return array;
|
|
|
+ } else {
|
|
|
+ return list.toArray((E[]) array);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
|
|
|
+ Map<K, V> result;
|
|
|
+ Object param = method.convertArgsToSqlCommandParam(args);
|
|
|
+ if (method.hasRowBounds()) {
|
|
|
+ RowBounds rowBounds = method.extractRowBounds(args);
|
|
|
+ result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
|
|
|
+ } else {
|
|
|
+ result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class ParamMap<V> extends HashMap<String, V> {
|
|
|
+
|
|
|
+ private static final long serialVersionUID = -2212268410512043556L;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public V get(Object key) {
|
|
|
+ if (!super.containsKey(key)) {
|
|
|
+ throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
|
|
|
+ }
|
|
|
+ return super.get(key);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class SqlCommand {
|
|
|
+
|
|
|
+ private final String name;
|
|
|
+ private final SqlCommandType type;
|
|
|
+
|
|
|
+ public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
|
|
|
+ final String methodName = method.getName();
|
|
|
+ final Class<?> declaringClass = method.getDeclaringClass();
|
|
|
+ MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
|
|
|
+ configuration);
|
|
|
+ if (ms == null) {
|
|
|
+ if (method.getAnnotation(Flush.class) != null) {
|
|
|
+ name = null;
|
|
|
+ type = SqlCommandType.FLUSH;
|
|
|
+ } else {
|
|
|
+ throw new BindingException("Invalid bound statement (not found): "
|
|
|
+ + mapperInterface.getName() + "." + methodName);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ name = ms.getId();
|
|
|
+ type = ms.getSqlCommandType();
|
|
|
+ if (type == SqlCommandType.UNKNOWN) {
|
|
|
+ throw new BindingException("Unknown execution method for: " + name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getName() {
|
|
|
+ return name;
|
|
|
+ }
|
|
|
+
|
|
|
+ public SqlCommandType getType() {
|
|
|
+ return type;
|
|
|
+ }
|
|
|
+
|
|
|
+ private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
|
|
|
+ Class<?> declaringClass, Configuration configuration) {
|
|
|
+ String statementId = mapperInterface.getName() + "." + methodName;
|
|
|
+ if (configuration.hasStatement(statementId)) {
|
|
|
+ return configuration.getMappedStatement(statementId);
|
|
|
+ } else if (mapperInterface.equals(declaringClass)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ for (Class<?> superInterface : mapperInterface.getInterfaces()) {
|
|
|
+ if (declaringClass.isAssignableFrom(superInterface)) {
|
|
|
+ MappedStatement ms = resolveMappedStatement(superInterface, methodName,
|
|
|
+ declaringClass, configuration);
|
|
|
+ if (ms != null) {
|
|
|
+ return ms;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class MethodSignature {
|
|
|
+
|
|
|
+ private final boolean returnsMany;
|
|
|
+ private final boolean returnsMap;
|
|
|
+ private final boolean returnsVoid;
|
|
|
+ private final boolean returnsCursor;
|
|
|
+ private final Class<?> returnType;
|
|
|
+ private final String mapKey;
|
|
|
+ private final Integer resultHandlerIndex;
|
|
|
+ private final Integer rowBoundsIndex;
|
|
|
+ private final ParamNameResolver paramNameResolver;
|
|
|
+
|
|
|
+ public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
|
|
|
+ Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
|
|
|
+ if (resolvedReturnType instanceof Class<?>) {
|
|
|
+ this.returnType = (Class<?>) resolvedReturnType;
|
|
|
+ } else if (resolvedReturnType instanceof ParameterizedType) {
|
|
|
+ this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
|
|
|
+ } else {
|
|
|
+ this.returnType = method.getReturnType();
|
|
|
+ }
|
|
|
+ this.returnsVoid = void.class.equals(this.returnType);
|
|
|
+ this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
|
|
|
+ this.returnsCursor = Cursor.class.equals(this.returnType);
|
|
|
+ this.mapKey = getMapKey(method);
|
|
|
+ this.returnsMap = this.mapKey != null;
|
|
|
+ this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
|
|
|
+ this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
|
|
|
+ this.paramNameResolver = new ParamNameResolver(configuration, method);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Object convertArgsToSqlCommandParam(Object[] args) {
|
|
|
+ return paramNameResolver.getNamedParams(args);
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean hasRowBounds() {
|
|
|
+ return rowBoundsIndex != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public RowBounds extractRowBounds(Object[] args) {
|
|
|
+ return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean hasResultHandler() {
|
|
|
+ return resultHandlerIndex != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ResultHandler extractResultHandler(Object[] args) {
|
|
|
+ return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getMapKey() {
|
|
|
+ return mapKey;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Class<?> getReturnType() {
|
|
|
+ return returnType;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean returnsMany() {
|
|
|
+ return returnsMany;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean returnsMap() {
|
|
|
+ return returnsMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean returnsVoid() {
|
|
|
+ return returnsVoid;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean returnsCursor() {
|
|
|
+ return returnsCursor;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
|
|
|
+ Integer index = null;
|
|
|
+ final Class<?>[] argTypes = method.getParameterTypes();
|
|
|
+ for (int i = 0; i < argTypes.length; i++) {
|
|
|
+ if (paramType.isAssignableFrom(argTypes[i])) {
|
|
|
+ if (index == null) {
|
|
|
+ index = i;
|
|
|
+ } else {
|
|
|
+ throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return index;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getMapKey(Method method) {
|
|
|
+ String mapKey = null;
|
|
|
+ if (Map.class.isAssignableFrom(method.getReturnType())) {
|
|
|
+ final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
|
|
|
+ if (mapKeyAnnotation != null) {
|
|
|
+ mapKey = mapKeyAnnotation.value();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return mapKey;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|