MybatisPlusAutoConfiguration
@Configuration 将该类加入到Spring容器中
sqlSessionFactory和SqlSessionFactoryBean类的依赖关系必须存在
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
DataSource类的实例必须存在
@ConditionalOnSingleCandidate(DataSource.class)
加载配置到MyBatisPlusProperties中
@EnableConfigurationProperties({MybatisPlusProperties.class})
在其他类加载完成之后在加载DataSourceAutoConfiguration
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
底层源码分析
# 获取执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
#判断执行器类型,如果配置文件中没有配置执行器类型,则采用默认执行类型ExecutorType.SIMPLE
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
# 根据执行器类型返回不同类型的执行器
# 器有三种,分别是 BatchExecutor、SimpleExecutor和CachingExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
#跟执行器绑定拦截器插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
默认的执行器过程
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
# 根据SQL的ID到配置信息中找对应的MappedStatement,
#在之前配置被加载初始化的时候我们看到了系统会把配置文件中的SQL块解析并放到一个MappedStatement里面,
#并将MappedStatement对象放到一个Map里面进行存放,Map的key值是该SQL块的ID
MappedStatement ms = this.configuration.getMappedStatement(statement);
# 调用执行器的query方法,传入MappedStatement对象、SQL参数对象、范围对象(此处为空)和结果处理方式
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
底层是如何执行的
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
# 获取配置信息对象
Configuration configuration = ms.getConfiguration();
# 通过配置对象获取一个新的StatementHandler,该类主要用来处理一次SQL操作
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
# 预处理StatementHandler对象,得到Statement对象
stmt = this.prepareStatement(handler, ms.getStatementLog());
# 传入Statement和结果处理对象,通过StatementHandler的query方法来执行SQL,并对执行结果进行处理
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
#根据相关的参数获取对应的StatementHandler对象。
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
#为StatementHandler对象绑定拦截器插件
StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
//从连接中获取Statement对象
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
//处理预编译的传入参数
return stmt;
}
首先我们从最原始的jdbc操作数据库开始
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/dynamic?serverTimezone=GMT&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=utf8";
//获取驱动
Class.forName("com.mysql.jdbc.Driver");
//获取于与数据库连接
Connection connection = DriverManager.getConnection(url, "root","123456");
//定义sql语句
String sqlConnect = "SELECT id,name,age,email FROM student";
//获取执行的对象
PreparedStatement statement = connection.prepareStatement(sqlConnect);
//执行
ResultSet set = statement.executeQuery();
while (set.next()){
String string = set.getString(2);
System.out.println("获取用户名称---->"+string);
}
}
PreparedStatement是Statement的子接口,
获取PrepareStatement对象是,必须传入一个待封装的SQL语句
什么是sql注入
先看代码
String param = "test or 1=1";
//什么是sql注入
sqlConnect = sqlConnect + param;
PreparedStatement preparedStatement = connection.prepareStatement(sqlConnect);
//执行sql语句
ResultSet resultSet = preparedStatement.executeQuery();
boolean next = resultSet.next();
System.out.println(next); //结果返回的是true
上面的去数据查询的
select * from student where name = 'test' or 1=1 条件成立把所有数据都查询出来
String param = "test or 1=1";
PreparedStatement preparedStatement = connection.prepareStatement(sqlConnect);
preparedStatement.setString(1,param);
//执行sql语句
ResultSet resultSet = preparedStatement.executeQuery();
boolean next = resultSet.next();
System.out.println(next); //false
这段代码执行的查询sql语句: select file from file where name = '\'test\' or 1=1'
我们可以看到输出的SQL文是把整个参数用引号包起来,并把参数中的引号作为转义字符,从而避免了参数也作为条件的一部分
还是研究源码
public void setString(int parameterIndex, String x) throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
if (x == null) {
this.setNull(parameterIndex, 1);
} else {
this.checkClosed();
//获取参数的长度
int stringLength = x.length();
//字符串缓冲区
StringBuilder buf;
if (this.connection.isNoBackslashEscapesSet()) {
// 判断是否包含特殊符号
boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength);
Object parameterAsBytes;
byte[] parameterAsBytes;
if (!needsHexEscape) {
parameterAsBytes = null;
buf = new StringBuilder(x.length() + 2);
buf.append('\'');
buf.append(x);
buf.append('\'');
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
} else {
parameterAsBytes = StringUtils.getBytes(buf.toString());
}
this.setInternal(parameterIndex, parameterAsBytes);
} else {
parameterAsBytes = null;
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
} else {
parameterAsBytes = StringUtils.getBytes(x);
}
this.setBytes(parameterIndex, parameterAsBytes);
}
return;
}
String parameterAsString = x;
boolean needsQuoted = true;
if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) {
needsQuoted = false;
buf = new StringBuilder((int)((double)x.length() * 1.1D));
buf.append('\'');
for(int i = 0; i < stringLength; ++i) {
char c = x.charAt(i);
switch(c) {
case '\u0000':
buf.append('\\');
buf.append('0');
break;
case '\n':
buf.append('\\');
buf.append('n');
break;
case '\r':
buf.append('\\');
buf.append('r');
break;
case '\u001a':
buf.append('\\');
buf.append('Z');
break;
case '"':
if (this.usingAnsiMode) {
buf.append('\\');
}
buf.append('"');
break;
case '\'':
buf.append('\\');
buf.append('\'');
break;
case '\\':
buf.append('\\');
buf.append('\\');
break;
case '¥':
case '₩':
if (this.charsetEncoder != null) {
CharBuffer cbuf = CharBuffer.allocate(1);
ByteBuffer bbuf = ByteBuffer.allocate(1);
cbuf.put(c);
cbuf.position(0);
this.charsetEncoder.encode(cbuf, bbuf, true);
if (bbuf.get(0) == 92) {
buf.append('\\');
}
}
buf.append(c);
break;
default:
buf.append(c);
}
}
buf.append('\'');
parameterAsString = buf.toString();
}
buf = null;
byte[] parameterAsBytes;
if (!this.isLoadDataQuery) {
if (needsQuoted) {
parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
} else {
parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
}
} else {
parameterAsBytes = StringUtils.getBytes(parameterAsString);
}
this.setInternal(parameterIndex, parameterAsBytes);
this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12;
}
}
}
上面代码就是判断参数是否包含特殊的符号,如果包含进行转义,把整个参数用引号包含起来
操作数据库的参数
Connection 是通过数据库的url,username,password获取与数据库连接的接口
创建好连接可以向数据库发送sql语句
Statement作用就是执行静态的sql
statement.execute()返回的是boolean类型,我们只知道sql是否执行成功,但是获取不到数据
如果想要拿到数据,需要执行executeQuery()方法,该方法的返回值是ResultSet
在日常操作时候我们需要用PreparedStatement代替Statement来执行sql语句
为了防止sql注入(就是把字符串类型的参数用引号包裹起来,当成一个参数传入)
了解以上可以开始mybatis的流程分析
SqlSessionFactroyBulider 读取Mybatis-config.xml的配置文件,通过bulider()方法构建一个
SqlSessionFactroy;
获取SqlSessionFacotry之后,可以通过SqlSession对象
**SqlSession可以对数据库进行CRUD操作**
MapperProxy
mapperProxy动态代理mapper(就是操作数据库的层面)
//获取代理对象
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
Configuration提供的方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
接着调用MapperRegistry的方法去获取代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//获取工厂类
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);
}
}
}
具体方法代码如下
protected T newInstance(MapperProxy<T> mapperProxy) {
//底层就是我们JDK自带的动态代理
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
怎么具体执行sql语句的
MapperProxy在执行时会触发此方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
兜兜转转代码如下
private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return this.mapperMethod.execute(sqlSession, args);
}
}
interface MapperMethodInvoker {
Object invoke(Object var1, Method var2, Object[] var3, SqlSession var4) throws Throwable;
}
最后代码交给MapperMethod来执行
//先判断CRUD类型,然后根据类型去选择到底执行sqlSession中的哪个方法,绕了一圈,又转回sqlSession了
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
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);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
}
选择其中一个方法分析
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//CRUD实际上是交给Excetor去处理, excutor其实也只是穿了个马甲而已,小样,别以为穿个马甲我就不认识你嘞!
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
通过一层一层的调用,最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//StatementHandler封装了Statement, 让 StatementHandler 去处理
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement)
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = this.boundSql.getSql();
statement.execute(sql);
return this.resultSetHandler.handleResultSets(statement);
}
到这里已经分析结束