解決mapper接口無法映射mapper.xml的問題
解決mapper接口無法映射mapper.xml
錯(cuò)誤信息:22-Mar-2019 15:15:53.542 嚴(yán)重 [http-nio-8080-exec-4] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [springmvc] in context with path [] threw exception [Request processing failed; nested exception is org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.hn.mapper.UserMapper.findNameByUser] with root cause
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.hn.mapper.UserMapper.findNameByUser
第一:在spring-mybatis.xml 配置文件中把mybatis主配置文件和接口映射文件配置好
?? ?<!--注冊(cè)sql工廠--> ?? ?<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> ?? ??? ?<property name="dataSource" ref="dataSource"/> ?? ??? ?<!-- 加載mybatis.xml配置文件 --> ?? ??? ?<property name="configLocation" value="classpath:spring/mybatis.xml" /> ?? ??? ?<property name="mapperLocations" value="classpath:com/hn/mapper/*.xml" /> ?? ?</bean>
第二:把xml中的入?yún)㈩愋腿サ?/p>
?? ?<select id="findNameByUser" resultType="com.hn.pojo.User"> ? ? ?SELECT * FROM user WHERE userName = #{userName} ? ? </select>
第三:確認(rèn)實(shí)體類中不能有“有參構(gòu)造”方法
第四:mybatis主配置文件中的配置不能和spring-mybatis中的配置重復(fù).xml
mapper文件與接口的映射
我們?cè)谑褂?Mybatis 的時(shí)候、只需要定義一個(gè) Mapper xml 文件和一個(gè)對(duì)應(yīng)的 Mapper 接口、并需要實(shí)現(xiàn)該接口、即可在程序中使用該 Mapper 接口、調(diào)用里面的方法對(duì)其進(jìn)行查詢(當(dāng)然 xml 中的 namespace 要關(guān)聯(lián)接口)。
那么這個(gè)功能是怎么實(shí)現(xiàn)的呢 ?
MapperRegistry
我們知道在使用 Mybatis 的時(shí)候都會(huì)去創(chuàng)建一個(gè) Configuration 類、而在這個(gè)類中、則會(huì)創(chuàng)建一個(gè) MapperRegistry
public class MapperRegistry { private final Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); public MapperRegistry(Configuration config) { this.config = config; } @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } public <T> boolean hasMapper(Class<T> type) { return knownMappers.containsKey(type); } public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } public Collection<Class<?>> getMappers() { return Collections.unmodifiableCollection(knownMappers.keySet()); } public void addMappers(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); for (Class<?> mapperClass : mapperSet) { addMapper(mapperClass); } } public void addMappers(String packageName) { addMappers(packageName, Object.class); } }
我們知道注冊(cè)一個(gè) Mapper 類會(huì)加入到 knownMappers 這個(gè) Map 里面、key 為對(duì)應(yīng)的 Mapper 類型、value 為 MapperProxyFactory
在 getMapper 方法中我們看到 mapperProxyFactory.newInstance(sqlSession);
MapperProxyFactory
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethodInvoker> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
我們看到這里創(chuàng)建了一個(gè)名為 MapperProxy 的代理類、哦吼、這里也是代理模式
MapperProxy
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ? try { ? ? if (Object.class.equals(method.getDeclaringClass())) { ? ? ? return method.invoke(this, args); ? ? } else { ? ? ? return cachedInvoker(method).invoke(proxy, method, args, sqlSession); ? ? } ? } catch (Throwable t) { ? ? throw ExceptionUtil.unwrapThrowable(t); ? } }
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { ? try { ? ? // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372 ? ? // It should be removed once the fix is backported to Java 8 or ? ? // MyBatis drops Java 8 support. See gh-1929 ? ? MapperMethodInvoker invoker = methodCache.get(method); ? ? if (invoker != null) { ? ? ? return invoker; ? ? } ? ? return methodCache.computeIfAbsent(method, m -> { ? ? ? if (m.isDefault()) { ? ? ? ? try { ? ? ? ? ? if (privateLookupInMethod == null) { ? ? ? ? ? ? return new DefaultMethodInvoker(getMethodHandleJava8(method)); ? ? ? ? ? } else { ? ? ? ? ? ? return new DefaultMethodInvoker(getMethodHandleJava9(method)); ? ? ? ? ? } ? ? ? ? } catch (IllegalAccessException | InstantiationException | InvocationTargetException ? ? ? ? ? ? | NoSuchMethodException e) { ? ? ? ? ? throw new RuntimeException(e); ? ? ? ? } ? ? ? } else { ? ? ? ? return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); ? ? ? } ? ? }); ? } catch (RuntimeException re) { ? ? Throwable cause = re.getCause(); ? ? throw cause == null ? re : cause; ? } }
正常來說我們創(chuàng)建的接口都不使用 default 修飾、如果使用了、那就代表其有方法的實(shí)現(xiàn)、那么就直接調(diào)用該方法、而不會(huì)再走 Mybatis 的邏輯。 如果走正常邏輯的話就會(huì)創(chuàng)建 MapperMethod
MapperMethod
public class MapperMethod { ? private final SqlCommand command; ? private final MethodSignature method; ? public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { ? ? this.command = new SqlCommand(config, mapperInterface, method); ? ? this.method = new MethodSignature(config, mapperInterface, method); ? } ....... }
我們首先來看看 SqlCommand
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); ? ? ? } ? ? } ? } ...... ? 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; ? } }
非常簡(jiǎn)單的邏輯、根據(jù)被代理的接口的名稱和調(diào)用方法名稱、組裝成一個(gè) statementId(namespace + sqlId)、去 Configuration 對(duì)象中查找是否存在對(duì)應(yīng)的 MappedStatement
SqlCommandType 則是
public enum SqlCommandType { ? UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH }
再來看看 MethodSignature
public static class MethodSignature { ? private final boolean returnsMany; ? private final boolean returnsMap; ? private final boolean returnsVoid; ? private final boolean returnsCursor; ? private final boolean returnsOptional; ? private final Class<?> returnType; ? private final String mapKey; ? private final Integer resultHandlerIndex; ? private final Integer rowBoundsIndex; ? private final ParamNameResolver paramNameResolver; ? ......
主要是對(duì)方法進(jìn)行一系列的解釋、返回值啊、對(duì)參數(shù)的解釋
再回到 MapperMethod 類中、我們知道它的 invoke 方法被 MapperProxy 調(diào)用
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); ? ? ? ? result = sqlSession.selectOne(command.getName(), param); ? ? ? ? if (method.returnsOptional() ? ? ? ? ? ? && (result == null || !method.getReturnType().equals(result.getClass()))) { ? ? ? ? ? result = Optional.ofNullable(result); ? ? ? ? } ? ? ? } ? ? ? 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; }
如果是 Insert、Update、Delete 的話、那么則先進(jìn)行 convertArgsToSqlCommandParam 然后則調(diào)用 rowCountResult 處理參數(shù)
public Object convertArgsToSqlCommandParam(Object[] args) { ? return paramNameResolver.getNamedParams(args); } public Object getNamedParams(Object[] args) { ? final int paramCount = names.size(); ? if (args == null || paramCount == 0) { ? ? return null; ? } else if (!hasParamAnnotation && paramCount == 1) { ? ? Object value = args[names.firstKey()]; ? ? return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null); ? } else { ? ? final Map<String, Object> param = new ParamMap<>(); ? ? int i = 0; ? ? for (Map.Entry<Integer, String> entry : names.entrySet()) { ? ? ? param.put(entry.getValue(), args[entry.getKey()]); ? ? ? // add generic param names (param1, param2, ...) ? ? ? final String genericParamName = GENERIC_NAME_PREFIX + (i + 1); ? ? ? // ensure not to overwrite parameter named with @Param ? ? ? if (!names.containsValue(genericParamName)) { ? ? ? ? param.put(genericParamName, args[entry.getKey()]); ? ? ? } ? ? ? i++; ? ? } ? ? return param; ? } }
這里主要是將參數(shù)放進(jìn)到 Map 中、這里還額外增加了一些參數(shù)、比如我們經(jīng)常在 mapper 文件中使用的 param1 、param2…
就是在這里實(shí)現(xiàn)的
wrapToMapIfCollection 則是對(duì)集合數(shù)組進(jìn)行處理的
public static Object wrapToMapIfCollection(Object object, String actualParamName) { ? if (object instanceof Collection) { ? ? ParamMap<Object> map = new ParamMap<>(); ? ? map.put("collection", object); ? ? if (object instanceof List) { ? ? ? map.put("list", object); ? ? } ? ? Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object)); ? ? return map; ? } else if (object != null && object.getClass().isArray()) { ? ? ParamMap<Object> map = new ParamMap<>(); ? ? map.put("array", object); ? ? Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object)); ? ? return map; ? } ? return object; }
再回到 rowCountResult、則是對(duì)返回值進(jìn)行處理的、邏輯比較簡(jiǎn)單。比如說返回布爾值的、則判斷其影響值是否大于 0
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; }
至于如果 sql 類型是 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); ? ? result = sqlSession.selectOne(command.getName(), param); ? ? if (method.returnsOptional() ? ? ? ? && (result == null || !method.getReturnType().equals(result.getClass()))) { ? ? ? result = Optional.ofNullable(result); ? ? } ? } ? break;
針對(duì)存在 ResultHandler 的、判斷是否存在 RowBounds
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)); ? } }
針對(duì)返回集合的
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.selectList(command.getName(), param, rowBounds); ? } else { ? ? result = sqlSession.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; }
針對(duì)返回 Map 的
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.selectMap(command.getName(), param, method.getMapKey(), rowBounds); ? } else { ? ? result = sqlSession.selectMap(command.getName(), param, method.getMapKey()); ? } ? return result; }
針對(duì)返回游標(biāo)的
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.selectCursor(command.getName(), param, rowBounds); ? } else { ? ? result = sqlSession.selectCursor(command.getName(), param); ? } ? return result; }
針對(duì) selectOne 的
Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() ? ? && (result == null || !method.getReturnType().equals(result.getClass()))) { ? result = Optional.ofNullable(result); }
可以看到 MapperMethod 也是依賴 Mybatis 的 SqlSession 去幫我們執(zhí)行而已、并沒有太高端的技術(shù)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決IDEA使用Spring Initializr創(chuàng)建項(xiàng)目時(shí)無法連接到https://start.spring.io的問
這篇文章主要介紹了解決IDEA使用Spring Initializr創(chuàng)建項(xiàng)目時(shí)無法連接到https://start.spring.io的問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Java8新特性Stream流中anyMatch和allMatch和noneMatch的區(qū)別解析
這篇文章主要介紹了Java8新特性Stream流中anyMatch和allMatch和noneMatch的區(qū)別解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01Spring Boot前后端分離開發(fā)模式中的跨域問題及解決方法
本文介紹了解決Spring Boot前端Vue跨域問題的實(shí)戰(zhàn)經(jīng)驗(yàn),并提供了后端和前端的配置示例,通過配置后端和前端,我們可以輕松解決跨域問題,實(shí)現(xiàn)正常的前后端交互,需要的朋友可以參考下2023-09-09使用JWT創(chuàng)建解析令牌及RSA非對(duì)稱加密詳解
這篇文章主要介紹了JWT創(chuàng)建解析令牌及RSA非對(duì)稱加密詳解,JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌,一種情況是webapi,類似之前的阿里云播放憑證的功能,另一種情況是多web服務(wù)器下實(shí)現(xiàn)無狀態(tài)分布式身份驗(yàn)證,需要的朋友可以參考下2023-11-11