解決mapper接口無(wú)法映射mapper.xml的問(wèn)題
解決mapper接口無(wú)法映射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)㈩?lèi)型去掉
?? ?<select id="findNameByUser" resultType="com.hn.pojo.User">
? ? ?SELECT * FROM user WHERE userName = #{userName}
? ? </select>第三:確認(rèn)實(shí)體類(lèi)中不能有“有參構(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)行查詢(xún)(當(dāng)然 xml 中的 namespace 要關(guān)聯(lián)接口)。
那么這個(gè)功能是怎么實(shí)現(xiàn)的呢 ?
MapperRegistry
我們知道在使用 Mybatis 的時(shí)候都會(huì)去創(chuàng)建一個(gè) Configuration 類(lèi)、而在這個(gè)類(lèi)中、則會(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 類(lèi)會(huì)加入到 knownMappers 這個(gè) Map 里面、key 為對(duì)應(yīng)的 Mapper 類(lèi)型、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 的代理類(lèi)、哦吼、這里也是代理模式
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;
? }
}正常來(lái)說(shuō)我們創(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);
? }
.......
}我們首先來(lái)看看 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ù)被代理的接口的名稱(chēng)和調(diào)用方法名稱(chēng)、組裝成一個(gè) statementId(namespace + sqlId)、去 Configuration 對(duì)象中查找是否存在對(duì)應(yīng)的 MappedStatement
SqlCommandType 則是
public enum SqlCommandType {
? UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
}再來(lái)看看 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 類(lèi)中、我們知道它的 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)單。比如說(shuō)返回布爾值的、則判斷其影響值是否大于 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 類(lèi)型是 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 也是依賴(lài) Mybatis 的 SqlSession 去幫我們執(zhí)行而已、并沒(méi)有太高端的技術(shù)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決IDEA使用Spring Initializr創(chuàng)建項(xiàng)目時(shí)無(wú)法連接到https://start.spring.io的問(wèn)
這篇文章主要介紹了解決IDEA使用Spring Initializr創(chuàng)建項(xiàng)目時(shí)無(wú)法連接到https://start.spring.io的問(wèn)題,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Java8新特性Stream流中anyMatch和allMatch和noneMatch的區(qū)別解析
這篇文章主要介紹了Java8新特性Stream流中anyMatch和allMatch和noneMatch的區(qū)別解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
Spring Boot前后端分離開(kāi)發(fā)模式中的跨域問(wèn)題及解決方法
本文介紹了解決Spring Boot前端Vue跨域問(wèn)題的實(shí)戰(zhàn)經(jīng)驗(yàn),并提供了后端和前端的配置示例,通過(guò)配置后端和前端,我們可以輕松解決跨域問(wèn)題,實(shí)現(xiàn)正常的前后端交互,需要的朋友可以參考下2023-09-09
使用JWT創(chuàng)建解析令牌及RSA非對(duì)稱(chēng)加密詳解
這篇文章主要介紹了JWT創(chuàng)建解析令牌及RSA非對(duì)稱(chēng)加密詳解,JWT是JSON Web Token的縮寫(xiě),即JSON Web令牌,是一種自包含令牌,一種情況是webapi,類(lèi)似之前的阿里云播放憑證的功能,另一種情況是多web服務(wù)器下實(shí)現(xiàn)無(wú)狀態(tài)分布式身份驗(yàn)證,需要的朋友可以參考下2023-11-11

