MyBatis学习笔记
Mybatis架构图
Mybatis.png
图中可能有错误,欢迎评论指正。
1.# Mybatis 中$与#的区别
Mybatis中#是预编译处理,将传入的值替换为字符串(如果data_id = 112233,则使用where id = #{data_id},如果为where id = '112233'),Mybatis处理时会将#{}替换为?,调用PreparedStatement的set方法来赋值,防止了sql注入攻击。
Mybatis中$是字符串替换,将传入的值替换成变量(如果data_id = 112233,则where id = ${data_id}替换为where id = 112233),不能有效的防止sql注入攻击。
一般尽量使用#,当#不可用时,才考虑使用$。
2.Mybatis缓存
Mybatis缓存.png2.1一级缓存,SqlSession隔离
一级缓存存在于SqlSession生命周期中,同一个SqlSession中查询时,会把执行的方法和参数通过算法生成缓存的key值,然后将key值和查询结果保存到一个map中。当多次调用同一个Mapper的同一个方法和同样的参数时,只会在进行第一次DB查询,以后直接从缓存中获取数据,而不需要查询DB。
另外一级缓存默认是开启的,不同的SqlSession的缓存是隔离的,任何Insert、update和delete操作都会清空缓存。
2.2 二级缓存,SqlSession共享
二级缓存可以理解为存在于SqlSessionFactory的生命周期中,故SqlSession之间是缓存数据共享的,二级缓存可以通过XXXMapper.xml或者XXXMapper.java配置(可以指定回收策略(LRU/FIFO/SOFT/WEAK等),刷新间隔,引用数目,只读或者可读可写(只读缓存只可读,不可写,而可读可写的缓存要求对象实现Serializable接口,通过序列化返回对象的复制))。
当调用 close 方法关闭 SqlSession 时, SqlSession 才会保存查询数据到 级缓存中在这之后二级缓存才有了缓存数据。
2.3 Mybatis外部缓存
由于Mybatis的缓存是基于Map的内存缓存实现,对缓存大量数据的情况支持性有限,但目前Mybatis已经支持Redis或者EhCache作为其二级缓存,来支持大数据量的缓存。
2.4 二级缓存脏数据
通常情况下每个 Mapper 映射文件都拥有自己的二级缓存,不同的Mapper二级缓存互不影响 。但是当遇到多表联合查询时,二级缓存会把查询结果放到某个命名空间下的Mapper映射文件中,但是涉及这几张表的增/删/改可能不在该Mapper映射文件中,即在不同的命名空间,当数据发生变更时,多表查询的缓存有可能会没有清空,导致脏数据的产生。当多个表适用同一个二级缓存时,可以解决脏数据问题,但是并不是所有的关联都可以通过这种方式解决。
2.5 二级缓存适用场景
a.查询为主的业务,很少使用增删改操作;
b.绝大多是单表操作,很少使用关联操作。
3.Mybatis分页方式
a.使用limit
即物理分页,实用性强;
b.使用拦截器分页
如PageHelper,将page信息保存到ThreadLocal中,利用interceptor接口拦截器,获取ThreadLocal中的Page变量,重新拼装sql,即 new_sql = select * from ( select tmp_page.*, rownum row_id from ( old_sql ) where rownum <= ? ) where row_id > ?。
c.RowBounds分页
即逻辑分页,需要将所有结果读入到内存,不适合数据量很大时。
4.Mybatis延迟加载
针对数据库多表查询的情况,先从单表查询、需要时再从关联表去关联查询,由于单表查询效率比多表关联查询效率高很多,故大大提高数据库查询性能。
5.Mybatis的Executor执行器
a.ExecutorType.SIMPLE
为每个语句创建一个PreparedStatement.
b.ExecutorType.REUSE
重复使用PreparedStatements.
b.ExecutorType.BATCH
批量更新.
6.Mybatis Interceptor拦截器
Mybatis Interceptor拦截器支持的类有Executor,ParameterHandler ,ResultSetHandler ,StatementHandler共四个类,并不是这些类的所有方法都支持,具体支持的方法如下:
6.1 org.apache.ibatis.executor.Executor 类的9个方法:
package org.apache.ibatis.executor;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
/**
* @author Clinton Begin
*/
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
/**
其他不支持的方法省略
**/
....
}
6.2 org.apache.ibatis.executor.parameter.ParameterHandler类的2个方法:
package org.apache.ibatis.executor.parameter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* A parameter handler sets the parameters of the {@code PreparedStatement}.
*
* @author Clinton Begin
*/
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
6.3 org.apache.ibatis.executor.resultset.ResultSetHandler 类的2个方法:
package org.apache.ibatis.executor.resultset;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import org.apache.ibatis.cursor.Cursor;
/**
* @author Clinton Begin
*/
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
/**
其他不支持的方法省略
**/
....
}
6.4 org.apache.ibatis.executor.statement.StatementHandler 类的5个方法:
package org.apache.ibatis.executor.statement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.ResultHandler;
/**
* @author Clinton Begin
*/
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
void parameterize(Statement statement)
throws SQLException;
void batch(Statement statement)
throws SQLException;
int update(Statement statement)
throws SQLException;
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
/**
其他不支持的方法省略
**/
....
}
6.5 拦截器接口 org.apache.ibatis.plugin.Interceptor
package org.apache.ibatis.plugin;
import java.util.Properties;
/**
* @author Clinton Begin
*/
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
三个方法的执行顺序为:
a.default void setProperties(Properties properties);
b. default Object plugin(Object target);
c. Object intercept(Invocation invocation) ;
6.6 自定义拦截器 MyInterceptor:
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import java.util.Properties;
/**
* 拦截器签名,告诉MyBatis当前插件用来拦截哪个对象的哪个方法
* type:要拦截的类 StatementHandler
* method:拦截的方法 parameterize
* args:方法的入参 java.sql.Statement.
* */
@Intercepts({
@Signature(type= StatementHandler.class,
method="parameterize", args=java.sql.Statement.class
)
})
public class MyInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
// 拦截的后加入的业务逻辑代码
}
public Object plugin(Object target) {
// 调用插件
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
/** 添加属性
* /
}
}