一、mapper 实现原理
MapperRegistry 类 getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取Mapper接口(type)对应的MapperProxyFactory
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public T newInstance(SqlSession sqlSession) {
// 根据sqlsession 获取MapperProxy
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 创建代理对象
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
最终执行mapper 里面方法 实际是执行MapperProxy invoke()方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
// 这里面截取一部分 MapperMethod方法
// 通过MappedStatement 获取要执行哪个方法 因为 insert|update|delete|select每一个标签都对应一个MappedStatement
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
// 判断要执行 哪一种操作
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
}
二、mybatis 有几级缓存,缓存原理
- 一级缓存sqlsession级别得缓存,默认开启得,不可关闭,但是可以在DML操作时 自动清除缓存,也可手动清除缓存,因为一级缓存时会话级别的所以 只有使用同一个sqlsession才会用到一级缓存,例如 spring的事务控制实在serivce层 ,当我们重复调用同一个service中得方法,是不会使用一级缓存得 因为当第二次调用的时候 sqlsession已经关闭了
- 二级缓存 mapper级别得 所有同一个namespace 下的sqlsession共享缓存,默认关闭,可手动开启
一级缓存和耳机缓存都会在DML操作时进行清空操作,防止脏数据
三、mybatis 除了insert 、select、update 、delete等等还有那些标签
动态标签 等等
四、resultMap 和resultType 区别
resultype 返回的对象类型 所有返回字段必须与相应的pojo 一一对应
resultmap 如果返回属性与对象属性不一致可以通过一个resultmap 转换 与之进行映射
五、#{} 和 ${} 区别
#{} 时占位符 ${} 是替换符号
比如在传值的时候 #{} 会被替换成 ? 之后调用PreparedStatement 进行赋值之后在外面加上单引号
但是 ${} 是传入什么值就拼接什么值
name = “zhangsan”
#{name} --> where name = ‘zhangsan’
${name} —> where name = shangsan
比如我登录时候不知道密码 但是使用的是 ${ } 进行占位的 则输入时候就可以进行sql注入
password = "111 "
where password = ‘111’ 这应该是正常情况下的sql
这时候如果使用的 ${} 我就可以进行sql 拼接
参数 password= “111 or 1=1”
where password = 111 or 1=1
六、mybatis 有哪些执行器
SimpleExecutor、ReuseExecutor,BatchExecutor
- SimpleExecutor 每一次执行sql 都会生成一个新得java.sql.statement
- ReuseExecutor 在 SimpleExecutor 基础上维护了一个map 缓存相同的sql 不会生成新的SimpleStatement,复用之前的SimpleStatement
- BatchExecutor 批量处理器 维护了一组 用于DML的SimpleStatement