基本介绍
我们知道Mybatis是ORM框架,即对象关系映射,其实本质是对JDBC的封装,我们首先看一下原生的JDBC操作数据库的代码示例:
public class Test {
// 驱动器路径
private static final String DRIVER = "com.mysql.jdbc.Driver";
//连接数据库地址
private static final String URL = "jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false&characterEncoding=UTF8";
//数据库用户名
private static final String USER_NAME = "root";
//数据库密码
private static final String USER_PASSWORD = "root";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 加载驱动
Class.forName(DRIVER);
// 建立和数据库的连接
Connection conn = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
// 创建SQL命令发送器
Statement stmt = conn.createStatement();
// 使用SQL命令发送器发送SQL命令并得到结果
String sql = "insert into student values(1,'小黄',18,'男','海南省文章市')";
int n = stmt.executeUpdate(sql);
// 处理结果
if (n > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
// 关闭数据库资源
stmt.close();
conn.close();
}
}
总结上面的步骤,就简单的四步,如下:
- 加载
Driver驱动 - 创建数据库连接
Connection - 创建数据库操作对象
Statement - 通过
Statement发送SQL命令并得到结果
其实我们仔细一看就知道,上面四个步骤都是固定的,代码都是固定的,只有一个东西是随机变化的,就是SQL!!!
四大组件
Executor
每一个SqlSession对象都会被分配一个执行器对象。主要负责Connection对象的获取和Statement对象的管理方案。
Statement对象的管理方式如下:
- 简单方案:一个
Statemnt接口对象只执行一次,执行完毕就会将Stament对象销毁,即SimpleExecutor简单执行器 - 可重用方案:使用一个
Map集合,关键字就是一条Sql语句,对应的value就是Statemnt对象,即ReuseExecutor可重用执行器,等到SqlSession再次接收到相同的SQL命令时,就会从Map集合中找到对象的Statement进行使用。Map.put("select * from order",statement1) - 批量处理管理方案:将多个
Statement包含的SQL语句,交给一个Statement对象输送到数据库中,进行批处理操作,即BatchExecutor批处理执行器
我们来看一下Executor的继承结构
BaseExecutor是一个抽象类,它有三个子类,SimpleExecutor简单执行器(默认执行器)、ReuseExecutor可重用执行器和BatchExecutor批量处理执行器CachingExecutor缓存执行器:当接收到查询任务时,该执行器先去缓存中查找是否有该铲鲟对象的结果,如果有就直接返回,否则就交给其他的执行器执行,即BaseExecutor的子类去执行。MyBatis默认情况下都会配置一个缓存执行器,来提高查询效率的,并且其中delegate变量存储另外一个执行器对象
Excecutor对象创建
执行器对象是由 Configuration对象负责创建的,Configuration 对象会根据得到 ExecutorType 创建对应的 Excecutor对象, 并把这个Excecutor对象传给 SqlSession 对象
StatementHandler
- 负责创建
Statement或者PreparedStatement或者CallableStatement对象, - 负责
PreparedStatement或者CallableStatement的SQL语句中占位符的赋值任务 - 此外还负责数据库操作对象的行为,是执行
executeUpdate更新还是执行executeQuery查询。
我们来看一下StatementHandler接口,如下:
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;
}
prepare方法:创建Statement对象或者PreparedStatement或者CallableStatement。parameterize方法:针对PreparedStatement或者CallableStatement关联的预编译SQL语句中占位符的赋值操作,该方法是通过ParameterHandler中的setParameter方法给参数赋值的。update方法:主要负责数据库操作对象中的更新行为,即执行executeUpdate。query方法:主要负责数据库操作对象中的查询行为,即执行executeQuery。
我们来看一下StatementHandler的继承结构
BaseStatementHandler一共有三个子类,即分别针对三个数据库操作对象
SimpleStatementHandler:创建Statement对象并初始化,进行简单的查询操作PreparedStatementHandler:创建PreparedStatement对象并初始化,进行预编译SQL,更新数据库操作。CallableStatementHandler:创建CallableStatement对象并初始化,处理存储过程。
RoutingStatementHandler:相当于SimpleStatementHandler,PreparedStatementHandler以及CallableStatementHandler的调度器。RoutingStatementHandler构造方法,将会根据Executor的类型决定创建SimpleStatementHandler,PreparedStatementHandler以及CallableStatementHandler
注意:StatementHandler对象是在SqlSession对象接收到命令时,由Configuration中的newStatementHandler方法创建的。
StatementHandler对象创建
StatementHandler对象是在SqlSession对象接收到操作命令时, 由Configuraion中newStatementHandler 方法负责调用的。
RoutingStatementHandler构造方法 , 将会根据Executor的类型决定创建SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler实例对象。
StatementHandler接口方法
prepare方法
prepare方法用于创建一个 (Statement or PreparedStatement or CallableStatement) 对象 , 并设置 Statement 对象的最大工作时间和一次性读取的最大数据量让后将生成的 Statement 对象返回
prepare 方法只在BaseStatementHandler被实现 在其三个子类中没有被重写 用于三个子类调用获得对应的 Statement接口对象 ,prepare 方法依靠instantiateStatement(connection)方法来返回具体Statement接口对象 ,这个方法是BaseStatementHandle中定义的抽象方法 , 由三个子类来具体实现
SimpleHandler中的instantiateStatement方法如下:
PreparedStatementHandler中的instantiateStatement方法如下:
CallableStatementHandler中的instantiateStatement方法如下:
parameterize方法
主要为PreparedStatement和CallableStatement传参.因此只在PreparedStatementHandler和CallableStatementHandler中被重写。即设置SQL占位符的
PreparedStatementHandler中的parameterize
CallableStatementHandler中的parameterize
在这两个方法中,可以看到都是ParameterHandler对象进行参数赋值的。
query方法
输送查询查询语句,并将查询结果转换对应的实体类对象
SimpleStatementHandler 中的 query方法
PreparedStatementHandler中的query方法
CallableStatementHandler中的query方法
ParameterHandler
为了PreparedStatement和CallableStatement的输送的SQL语句中占位符进行赋值操作。
-
getParameterObject方法:读取开发人员提供参数内容(占位符对应的参数值) -
setParameters方法: 通过反射机制,负责将参数内容设置到数据库操作对象中输送的sql语句的占位符上
我们来看一下ParameterHandler的继承结构,它只有一个实现类DefaultParameterHandler
ParameterHandler对象创建
参数处理器对象是在创建StatementHandler对象同时被创建的.由Configuration对象负责创建.
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 获得 Configuration 对象
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
// 获得 TypeHandlerRegistry 和 ObjectFactory 对象
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
// 如果 boundSql 非空,一般是写类操作,例如:insert、update、delete ,则先获得自增主键,然后再创建 BoundSql 对象
if (boundSql == null) { // issue #435, get the key before calculating the statement
// 获得自增主键
generateKeys(parameterObject);
// 创建 BoundSql 对象
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 创建 ParameterHandler 对象
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 创建 ResultSetHandler 对象
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
ResultSetHandler
将对数据库操作的结果映射为一个Java对象
我们来看一下ResultSetHandler的继承结构,它只有一个实现类 DefaultResultSetHandler
Mybatis源码剖析
解析配置文件
创建SqlSession对象
MapperProxy创建dao接口代理对象
protected T newInstance(MapperProxy<T> mapperProxy) {
//第一个参数为类加载器,第二个参数为被代理类的接口数据,第三个参数为InvocationHandler的实现类
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
MapperProxy中的Invoke方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
//根据dao接口方法类型执行不同的逻辑,比如查询就是SELECT
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;
//由于findAll方法的返回值是List,所以会执行下面的方法
} 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;
}
}
创建StatementHandler对象
创建ParameterHandler对象和ResultSetHandler对象
创建数据库操作对象
protected Statement instantiateStatement(Connection connection) throws SQLException {
//创建数据库操作对象Statement对象
return this.mappedStatement.getResultSetType() != null ? connection.createStatement(this.mappedStatement.getResultSetType().getValue(), 1007) : connection.createStatement();
}
执行数据库操作
插件
原理
Mybatis作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象。
MyBatis所允许拦截的方法如下:
-
执行器
Executor(update、query、commit、rollback等方法); -
tatementHandler(prepare、parameterize、batch、updates query等方 法); -
参数处理器
ParameterHandler(getParameterObject、setParameters方法); -
结果集处理器
ResultSetHandler(handleResultSets、handleOutputParameters等方法);
在四大组件对象创建的时候,每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler); ,获取到所有的Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target);返回 target 包装后的对象
插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说
public ParameterHandler newParameterHandler(MappedStatement mappedStatement,Object object, BoundSql sql, InterceptorChain interceptorChain){
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
parameterHandler = (ParameterHandler)interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
/**
* @author Clinton Begin
*/
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
-
interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调用拦截器链中的拦截器依次的对目标进行拦截或增强。 -
interceptor.plugin(target)中的target就可以理解为mybatis中的四大组件对象。返回的target是被重重代理后的对象。
自定义插件
数据权限插件例子如下:
/**
* @Auther: huangshuai
* @Date: 2024/2/24 23:29
* @Description:给sql后面添加where查询条件
* @Version:
*/
/**
* 开发自己的插件
* OneInterceptor相当于代理实现类
* typ表示对哪个神器进行拦截
* method:神器的具体方法
* args:方法的参数类型
*/
@Intercepts(value = @Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
))
public class OneInterceptor implements Interceptor {
private final String key = "factory_code";
private final List<String> value = Arrays.asList("111", "222");
/**
* 该方法的作用:【任务】为当前目标对象生成代理对象
* <p>
* 监听对象(代理对象)=Proxy.newProxyInstance(类加载器,interfaces,代理实现里)
* 上面的代码可以用Plugin工具类来完成,因为该工具类中的wrap方法封装了上一行代码
* <p>
* target就是四大神器的实例对象
* 返回值就是四大神器的监听对象即代理对象
*/
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 相当于InvocationHandler中的Invoke方法
* invoke(Object proxy,Method method,Object[] args);
* <p>
* 该方法中的参数Invocation invocation,封装的是被拦截的神器对象,还封装了被拦截神器的行为,即神器的方法
* 例如封装了StatementHandler对象,封装了该对象中的prepare方法
* <p>
* 该方法的返回值就是神器的被拦截的方法的返回值
*/
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
metaObject.setValue("delegate.boundSql.sql", rewriteSql(metaObject));
}
return invocation.proceed();
}
private String rewriteSql(MetaObject metaObject) throws Exception {
String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
CCJSqlParserManager sqlParserManager = new CCJSqlParserManager();
Select select = (Select) sqlParserManager.parse(new StringReader(originalSql));
SelectBody selectBody = select.getSelectBody();
if (selectBody instanceof PlainSelect) {
this.setWhere((PlainSelect) selectBody, key, value);
} else {
SetOperationList operationList = (SetOperationList) selectBody;
List<SelectBody> selectBodyList = operationList.getSelects();
selectBodyList.forEach(s -> {
try {
this.setWhere((PlainSelect) s, key, value);
} catch (Exception e) {
}
});
}
return select.toString();
}
private void setWhere(PlainSelect plainSelect, String key, List<String> value) throws Exception {
Table fromItem = (Table) plainSelect.getFromItem();
Alias alias = fromItem.getAlias();
String mainTableName = alias == null ? fromItem.getName() : alias.getName();
ExpressionList expressionList = new ExpressionList(value.stream().map(StringValue::new).collect(Collectors.toList()));
InExpression inExpression = new InExpression(new Column(mainTableName + "." + key), expressionList);
if (plainSelect.getWhere() == null) {
plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression(inExpression.toString()));
} else {
plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), CCJSqlParserUtil.parseExpression(inExpression.toString())));
}
}
/**
* 该方法的任务:为拦截器对象的属性进行赋值的
*
* <plugins>
* <plugin interceptor="com.kaikeba.interceptor.OneInterceptor">
* <!-- 该属性的值,可以在拦截器中的setProperties方法中进行读取 -->
* <property name="driver" value="com.mysql.jdbc.Driver"/>
* </plugin>
* </plugins>
*/
public void setProperties(Properties properties) {
String property = properties.getProperty("driver");
System.out.println("获得属性值是:" + property);
}
}