封装了原生jdbc
DataSource dataSource = YamlShardingDataSourceFactory.createDataSource(yamlFile);
String sql = "SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.user_id=? AND o.order_id=?";
try (
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
preparedStatement.setInt(1, 10);
preparedStatement.setInt(2, 1001);
try (ResultSet rs = preparedStatement.executeQuery()) {
while(rs.next()) {
System.out.println(rs.getInt(1));
System.out.println(rs.getInt(2));
}
}
}
MySQL默认是开启自动提交的,即每一条DML(增删改)语句都会被作为一个单独的事务进行隐式提交。
sqlSession是mybatis对jdbc的抽象,实现了上面原生jdbc的功能
使用xml初始化SqlSessionFactory
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//解析mybatis配置文件,主要解析mapperElement(root.evalNode("mappers"));
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
(不使用xml初始化SqlSessionFactory
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
)
SqlSession sqlSession = sqlSessionFactory.openSession();
动态代理xxxMapper,执行xxxMapper函数的时候发送sql命令
xxxMapper mapper = session.getMapper(xxxMapper.class);
mapper.select();
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
}
xml配置enviroment,根据type属性,反射实例化TransactionFactory、DataSourceFactory
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
别名池
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
用环境environment封装事务transactionFactory和数据源datasource
通过指定mapper xml的路径,Mybatis使用XMLMapperBuilder类来加载解析mapper xml文件。最后会使用mapperRegistry.addMapper(type)来加载,这个type就是mapper xml的namespace属性。
指定Mapper接口,Mybatis使用方法mapperRegistry.addMapper(type)来加载,在这个方法内部会使用类MapperAnnotationBuilder来解析Mapper接口上的注解信息,首先是加载Mapper接口对应的mapper xml文件,然后在解析注解信息。其中怎么找到Mapper接口的mapper xml文件呢?将类名转为xml路径,比如:cre.mybatis.mapper.BookMapper -> cre/mybatis/mapper/BookMapper.xml。
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse -> org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
处理mapper xml文件,为每个sql方法生成MappedStatement,并放入mappedStatements map,key为namespace+id,namespace是类全限定名,id是方法名,通过这个key可以使statement与类相对应
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
// 能根据输入参数,生成发送给数据库的sql
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}
org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
加载namespace即类名
调用mapper接口函数的时候,会调用org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand生成sql命令
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 增强方法
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行方法
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
org.apache.ibatis.binding.MapperMethod#MapperMethod->
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 找到对应的sql语句
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
//找到对应的statement
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
普通transaction
public class JdbcTransaction implements Transaction
spring管理的transaction
public class SpringManagedTransaction implements Transaction
xml配置
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
*
* @author Clinton Begin
*/
public interface SqlSession extends Closeable {
/**
* Retrieve a single row mapped from the statement key.
* @param <T> the returned object type
* @param statement
* @return Mapped object
*/
<T> T selectOne(String statement);
/**
* Retrieve a single row mapped from the statement key and parameter.
* @param <T> the returned object type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return Mapped object
*/
<T> T selectOne(String statement, Object parameter);
/**
* Retrieve a list of mapped objects from the statement key and parameter.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @return List of mapped object
*/
<E> List<E> selectList(String statement);
/**
* Retrieve a list of mapped objects from the statement key and parameter.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter);
/**
* Retrieve a list of mapped objects from the statement key and parameter,
* within the specified row bounds.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param rowBounds Bounds to limit object retrieval
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
/**
* The selectMap is a special case in that it is designed to convert a list
* of results into a Map based on one of the properties in the resulting
* objects.
* Eg. Return a of Map[Integer,Author] for selectMap("selectAuthors","id")
* @param <K> the returned Map keys type
* @param <V> the returned Map values type
* @param statement Unique identifier matching the statement to use.
* @param mapKey The property to use as key for each value in the list.
* @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, String mapKey);
/**
* The selectMap is a special case in that it is designed to convert a list
* of results into a Map based on one of the properties in the resulting
* objects.
* @param <K> the returned Map keys type
* @param <V> the returned Map values type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param mapKey The property to use as key for each value in the list.
* @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
/**
* The selectMap is a special case in that it is designed to convert a list
* of results into a Map based on one of the properties in the resulting
* objects.
* @param <K> the returned Map keys type
* @param <V> the returned Map values type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param mapKey The property to use as key for each value in the list.
* @param rowBounds Bounds to limit object retrieval
* @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
* @param <T> the returned cursor element type.
* @param statement Unique identifier matching the statement to use.
* @return Cursor of mapped objects
*/
<T> Cursor<T> selectCursor(String statement);
/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
* @param <T> the returned cursor element type.
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return Cursor of mapped objects
*/
<T> Cursor<T> selectCursor(String statement, Object parameter);
/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
* @param <T> the returned cursor element type.
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param rowBounds Bounds to limit object retrieval
* @return Cursor of mapped objects
*/
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
/**
* Retrieve a single row mapped from the statement key and parameter
* using a {@code ResultHandler}.
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param handler ResultHandler that will handle each retrieved row
*/
void select(String statement, Object parameter, ResultHandler handler);
/**
* Retrieve a single row mapped from the statement
* using a {@code ResultHandler}.
* @param statement Unique identifier matching the statement to use.
* @param handler ResultHandler that will handle each retrieved row
*/
void select(String statement, ResultHandler handler);
/**
* Retrieve a single row mapped from the statement key and parameter
* using a {@code ResultHandler} and {@code RowBounds}.
* @param statement Unique identifier matching the statement to use.
* @param rowBounds RowBound instance to limit the query results
* @param handler ResultHandler that will handle each retrieved row
*/
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
/**
* Execute an insert statement.
* @param statement Unique identifier matching the statement to execute.
* @return int The number of rows affected by the insert.
*/
int insert(String statement);
/**
* Execute an insert statement with the given parameter object. Any generated
* autoincrement values or selectKey entries will modify the given parameter
* object properties. Only the number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @param parameter A parameter object to pass to the statement.
* @return int The number of rows affected by the insert.
*/
int insert(String statement, Object parameter);
/**
* Execute an update statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @return int The number of rows affected by the update.
*/
int update(String statement);
/**
* Execute an update statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @param parameter A parameter object to pass to the statement.
* @return int The number of rows affected by the update.
*/
int update(String statement, Object parameter);
/**
* Execute a delete statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @return int The number of rows affected by the delete.
*/
int delete(String statement);
/**
* Execute a delete statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @param parameter A parameter object to pass to the statement.
* @return int The number of rows affected by the delete.
*/
int delete(String statement, Object parameter);
/**
* Flushes batch statements and commits database connection.
* Note that database connection will not be committed if no updates/deletes/inserts were called.
* To force the commit call {@link SqlSession#commit(boolean)}
*/
void commit();
/**
* Flushes batch statements and commits database connection.
* @param force forces connection commit
*/
void commit(boolean force);
/**
* Discards pending batch statements and rolls database connection back.
* Note that database connection will not be rolled back if no updates/deletes/inserts were called.
* To force the rollback call {@link SqlSession#rollback(boolean)}
*/
void rollback();
/**
* Discards pending batch statements and rolls database connection back.
* Note that database connection will not be rolled back if no updates/deletes/inserts were called.
* @param force forces connection rollback
*/
void rollback(boolean force);
/**
* Flushes batch statements.
* @return BatchResult list of updated records
* @since 3.0.6
*/
List<BatchResult> flushStatements();
/**
* Closes the session.
*/
@Override
void close();
/**
* Clears local session cache.
*/
void clearCache();
/**
* Retrieves current configuration.
* @return Configuration
*/
Configuration getConfiguration();
/**
* Retrieves a mapper.
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> type);
/**
* Retrieves inner database connection.
* @return Connection
*/
Connection getConnection();
}
这个接口能执行sql命令、获取代理后的mapper、管理事务
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
............
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
org.apache.ibatis.binding.MapperRegistry#getMapper
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 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);
}
}
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
默认SIMPLE
封装数据库的地址、用户名、密码、driver、自动提交、隔离配置和获取数据库连接等
dataSource封装在Configuration的Environment里
dataSource可以换成其他连接池的实现比如DruidDataSource(要实现javax.sql.DataSource接口)
驱动query
org.apache.ibatis.executor.SimpleExecutor#doQuery
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
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 用handler把参数拼接到sql
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
newStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 生成statementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 把插件放入statementHandler
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
// 生成statementHandler代理
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
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);
}
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 将插件放入parameterHandler
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 将插件放入resultSetHandler
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
插件还能放入executor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
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 = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
prepareStatement
// 用handler把参数拼接到sql
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
1. 根据xml或者无xml初始化SqlSessionFactory
SqlSessionFactoryBuilder().build
2. 打开SqlSession对象
sqlSessionFactory.openSession()
3. 动态代理mapper
sqlSession.getMapper()
4. 执行代理后的对象mapperProxy的invoke
sqlSession.select/update
5. BaseExecutor/CachingExecutor(BaseExecutor的代理)
Executor执行query/queryFromDatabase
6. 初始化prepareStatement并给#{}赋值
SimpleExecutor.doQuery
7. 执行sql语句
StatementHandler.query
8. 封装结果集
ResultHandler handleResultSets
boundSql是把xml的#{}变成?之后的sql
PerpetualCache:作为最基础的缓存类,底层实现比较简单,直接使用了HashMap。
装饰器
SynchronizedCache:同步Cache,实现比较简单,直接使用synchronized修饰方法。 LoggingCache:日志功能,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。
SerializedCache:序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的Copy,用于保存线程安全。 LruCache:采用了Lru算法的Cache实现,移除最近最少使用的Key/Value。
LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值 FIFO(先进先出): 按对象进入缓存的顺序来移除它们
SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象
WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象
PerpetualCache: 作为为最基础的缓存类,底层实现比较简单,直接使用了HashMap
根据method获得returnType
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
根据method获得returnType
private final SortedMap<Integer, String> names;
public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
描述sql语句,输入参数与返回参数。什么时候初始化?
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
MappedStatement() {
// constructor disabled
}
}
执行器
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());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
数据库链接
封装connection,执行事务
/**
* Wraps a database connection.
* Handles the connection lifecycle that comprises: its creation, preparation, commit/rollback and close.
*
* @author Clinton Begin
*/
/**
* {@code SpringManagedTransaction} handles the lifecycle of a JDBC connection. It retrieves a connection from Spring's
* transaction manager and returns it back to it when it is no longer needed.
* <p>
* If Spring's transaction handling is active it will no-op all commit/rollback/close calls assuming that the Spring
* transaction manager will do the job.
* <p>
* If it is not it will behave like {@code JdbcTransaction}.
*
* @author Hunter Presnall
* @author Eduardo Macarron
*/
public class SpringManagedTransaction implements Transaction {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);
private final DataSource dataSource;
private Connection connection;
private boolean isConnectionTransactional;
private boolean autoCommit;
public SpringManagedTransaction(DataSource dataSource) {
notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
/**
* Gets a connection from Spring transaction manager and discovers if this {@code Transaction} should manage
* connection or let it to Spring.
* <p>
* It also reads autocommit setting because when using Spring Transaction MyBatis thinks that autocommit is always
* false and will always call commit/rollback so we need to no-op that calls.
*/
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
+ (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
}
/**
* {@inheritDoc}
*/
@Override
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
this.connection.commit();
}
}
/**
* {@inheritDoc}
*/
@Override
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
this.connection.rollback();
}
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}
/**
* {@inheritDoc}
*/
@Override
public Integer getTimeout() {
ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (holder != null && holder.hasTimeout()) {
return holder.getTimeToLiveInSeconds();
}
return null;
}
}
封装transactionFactory
public class SpringManagedTransactionFactory implements TransactionFactory {
/**
* {@inheritDoc}
*/
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
/**
* {@inheritDoc}
*/
@Override
public Transaction newTransaction(Connection conn) {
throw new UnsupportedOperationException("New Spring transactions require a DataSource");
}
/**
* {@inheritDoc}
*/
@Override
public void setProperties(Properties props) {
// not needed in this version
}
}
配置类
驱动类
/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
*
* @author Clinton Begin
*/
MappedStatement ms = configuration.getMappedStatement(statement);
// executor + mappedStatement
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
Executor
驱动statementHandler
StatementHandler
用statement执行sql语句,parameterHandler、resultHandler在statementHandler中
parameterHandler
把输入参数拼接到sql
resultHandler
处理结果,结果也在statement中
在实例化四大对象的时候,设置插件 interceptorChain.pluginAll(object);
实现Interceptor接口,可以通过动态代理对四大对象进行拦截
分页插件
//args:你需要mybatis传入什么参数, type:你需要拦截的对象,method:要拦截的方法
@Intercepts(@Signature(type = StatementHandler.class,method ="prepare",args = {Connection.class,Integer.class}))
public class MyPagePlugin implements Interceptor {
String databaseType = "";
String pageSqlId = "";
public String getDatabaseType() {
return databaseType;
}
public void setDatabaseType(String databaseType) {
this.databaseType = databaseType;
}
public String getPageSqlId() {
return pageSqlId;
}
public void setPageSqlId(String pageSqlId) {
this.pageSqlId = pageSqlId;
}
//我们自己拦截器里面的逻辑
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// statementHandler.getDelegate().getmappedStatement().getId();
// Field delegate = StatementHandler.class.getDeclaredField("delegate");
// delegate.setAccessible(true);
// Object o = delegate.get(statementHandler);
// o.getClass().getDeclaredField("mappedStatement").
MetaObject metaObject = MetaObject.forObject(
statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
String sqlId = (String)metaObject.getValue("delegate.mappedStatement.id");
//判断一下是否是分页
// <!--第一步 执行一条couunt语句-->
//1.1拿到连接
//1.2 预编译SQL语句 拿到绑定的sql语句
//1.3 执行 count语句 怎么返回你执行count的结果
// <!--第二部 重写sql select * from luban_product limit start,limit -->
//2.1 ? 怎么知道 start 和limit
//2.2拼接start 和limit
//2.3 替换原来绑定sql
//拿到原来应该执行的sql
if (sqlId.matches(pageSqlId)){
ParameterHandler parameterHandler = statementHandler.getParameterHandler();
//原来应该执行的sql
String sql = statementHandler.getBoundSql().getSql();
//sql= select * from product select count(0) from (select * from product) as a
//select * from luban_product where name = #{name}
//执行一条count语句
//拿到数据库连接对象
Connection connection = (Connection) invocation.getArgs()[0];
String countSql = "select count(0) from ("+sql+") a";
System.out.println(countSql);
//渲染参数
PreparedStatement preparedStatement = connection.prepareStatement(countSql);
//条件交给mybatis
parameterHandler.setParameters(preparedStatement);
ResultSet resultSet = preparedStatement.executeQuery();
int count =0;
if (resultSet.next()) {
count = resultSet.getInt(1);
}
resultSet.close();
preparedStatement.close();
//获得你传进来的参数对象
Map<String, Object> parameterObject = (Map<String, Object>) parameterHandler.getParameterObject();
//limit page
PageUtil pageUtil = (PageUtil) parameterObject.get("page");
//limit 1 ,10 十条数据 总共可能有100 count 要的是 后面的100
pageUtil.setCount(count);
//拼接分页语句(limit) 并且修改mysql本该执行的语句
String pageSql = getPageSql(sql, pageUtil);
metaObject.setValue("delegate.boundSql.sql",pageSql);
System.out.println(pageSql);
}
// 推进拦截器调用链
return invocation.proceed();
}
public String getPageSql(String sql,PageUtil pageUtil){
if(databaseType.equals("mysql")){
return sql+" limit "+pageUtil.getStart()+","+pageUtil.getLimit();
}else if(databaseType.equals("oracle")){
//拼接oracle的分语句
}
return sql+" limit "+pageUtil.getStart()+","+pageUtil.getLimit();
}
//返回一个动态代理后的对象 target :StatementHandler
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
//会传入配置文件内容 用户可根据配置文件自定义
@Override
public void setProperties(Properties properties) {
}
}
// 把statementHandler.class的插件串成一条链
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 如果target.getClass有被拦截,返回代理对象
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
// 获得插件挂载的类接口
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 目标handler的类
Class<?> type = target.getClass();
// 查找目标handler类的所有接口是否存在和插件挂载的类接口一致,有则放入interfaces
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class PageInterceptor implements Interceptor
有很多mapper接口,怎么代理所有接口?
怎么用一个InvocationHandler,代理所有的接口方法
public class MapperProxy<T> implements InvocationHandler, Serializable {
@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 if (method.isDefault()) {
// 下面两个invoke采用了java方法句柄的方式调用方法
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 每个接口方法,对应一个方法执行
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
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);
}
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;
}
}
/**
* @param config : mybatis从xml中解析到全局配置
* @param method :执行的目标方法
*/
public ParamNameResolver(Configuration config, Method method) {
//通过反射获取参数列表的运行时类型
final Class<?>[] paramTypes = method.getParameterTypes();
//通过反射获取参数列表中每个参数对应的注解数组
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
int paramCount = paramAnnotations.length;
//遍历参数列表,获取其参数名
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
//如果当前参数用@Param修饰,直接使用@Param的值作为参数名
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
//如果没有使用@Param或者@Param没有赋值,默认使用真实的参数名
if (name == null) {
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
//如果isUseActualParamName = false,则直接使用参数索引作为参数名
if (name == null) {
name = String.valueOf(map.size());
}
}
//key:参数的索引。value:上述解析到的参数Name
map.put(paramIndex, name);
}
//通过Collections进行包装
names = Collections.unmodifiableSortedMap(map);
}
public Object getNamedParams(Object[] args) {
//names就是上面用Collections包装过的TreeMap
final int paramCount = names.size();
//根据TreeMap判断,如果没有参数列表,返回null,如果只有一个参数,直接返回数组中第一个元素。
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
//当有两个及两个以上个参数时,构建一个ParamMap。关于ParamMap,大家可以先把它当做一个HashMap,
//会在下面介绍。
final Map<String, Object> param = new MapperMethod.ParamMap<Object>();
int i = 0;
//遍历TreeMap,相当于按序遍历方法的参数列表。
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//args数组与TreeMap对于参数列表而言,是一一对应的,将参数名、参数值放入ParamMap中
param.put(entry.getValue(), args[entry.getKey()]);
//再将Param0、Param1...及对应value放入ParamMap中
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
//返回ParamMap
return param;
}
}
mybatis sqlsession
mybatis-sprint sqlsessiontemplate
@transacitonal 动态代理sqlsessointemplate
1.
注入datasource,生成SqlSessionFactory
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
return factory.getObject();
}
org.mybatis.spring.SqlSessionFactoryBean#getObject
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
targetConfiguration.setEnvironment(new Environment(this.environment,
// 如果没有设置transactionFactory,则使用spring管理的事务工厂
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
....
// 导入mapper sql语句
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
2.
对于每个mapper,设置configuration的addMapper
Configuration configuration = getSqlSession().getConfiguration(); configuration.addMapper(this.mapperInterface);
步骤
1.
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
默认导入org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.Hikari
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
2.
datasource存在哪里?
org.mybatis.spring.boot.autoconfigure.MybatisProperties#resolveMapperLocations
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory
init SqlSessionFactoryBean
mappper.xml
第一步解析@MapperScan ->
@Import(MapperScannerRegistrar.class)
org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.core.annotation.AnnotationAttributes, org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.String)
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
注册MapperScannerConfigurer的beanDefinition到容器->
ClassPathMapperScanner.doScan扫描mapper,得到mapper的所有bd ->
processBeanDefinitions处理所有mapper( setBeanClass(this.mapperFactoryBean.getClass())改变了bd的BeanClass,为mapperFactoryBean;definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);自动装配有setXXX函数的属性,不需要@autoWire注解,主要是为了注入DefaultSqlSessionFactory这个SqlSessionFactory, SqlSessionDaoSupport里有setSqlSessionFactory函数,函数里this.sqlSession = new SqlSessionTemplate(sqlSessionFactory),完成SqlSessionTemplate实例,其中this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class },new SqlSessionInterceptor());又生成一个代理,InvocationHandler是SqlSessionInterceptor,执行完sql之后closeSqlSession)
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
// intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
// ------------- mutators --------------
/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface
* class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* Return the mapper interface of the MyBatis mapper
*
* @return class of the interface
*/
public Class<T> getMapperInterface() {
return mapperInterface;
}
/**
* If addToConfig is false the mapper will not be added to MyBatis. This means it must have been included in
* mybatis-config.xml.
* <p>
* If it is true, the mapper will be added to MyBatis in the case it is not already registered.
* <p>
* By default addToConfig is true.
*
* @param addToConfig
* a flag that whether add mapper to MyBatis or not
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* Return the flag for addition into MyBatis config.
*
* @return true if the mapper will be added to MyBatis in the case it is not already registered.
*/
public boolean isAddToConfig() {
return addToConfig;
}
}
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
/** 如果有设置SqlSessionTemplate和SqlSessionFactory,会优先用SqlSessionTemplate,如果没有设置SqlSessionTemplate,则会根据SqlSessionFactory自动创建sqlSessionTemplate
* Set MyBatis SqlSessionFactory to be used by this DAO. Will automatically create SqlSessionTemplate for the given
* SqlSessionFactory.
*
* @param sqlSessionFactory
* a factory of SqlSession
*/
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
/**
* Create a SqlSessionTemplate for the given SqlSessionFactory. Only invoked if populating the DAO with a
* SqlSessionFactory reference!
* <p>
* Can be overridden in subclasses to provide a SqlSessionTemplate instance with different configuration, or a custom
* SqlSessionTemplate subclass.
*
* @param sqlSessionFactory
* the MyBatis SqlSessionFactory to create a SqlSessionTemplate for
* @return the new SqlSessionTemplate instance
* @see #setSqlSessionFactory
*/
@SuppressWarnings("WeakerAccess")
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* Return the MyBatis SqlSessionFactory used by this DAO.
*
* @return a factory of SqlSession
*/
public final SqlSessionFactory getSqlSessionFactory() {
return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
}
/**
* Set the SqlSessionTemplate for this DAO explicitly, as an alternative to specifying a SqlSessionFactory.
*
* @param sqlSessionTemplate
* a template of SqlSession
* @see #setSqlSessionFactory
*/
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
/**
* Users should use this method to get a SqlSession to call its statement methods This is SqlSession is managed by
* spring. Users should not commit/rollback/close it because it will be automatically done.
*
* @return Spring managed thread safe SqlSession
*/
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
/**
* Return the SqlSessionTemplate for this DAO, pre-initialized with the SessionFactory or set explicitly.
* <p>
* <b>Note: The returned SqlSessionTemplate is a shared instance.</b> You may introspect its configuration, but not
* modify the configuration (other than from within an {@link #initDao} implementation). Consider creating a custom
* SqlSessionTemplate instance via {@code new SqlSessionTemplate(getSqlSessionFactory())}, in which case you're
* allowed to customize the settings on the resulting instance.
*
* @return a template of SqlSession
*/
public SqlSessionTemplate getSqlSessionTemplate() {
return this.sqlSessionTemplate;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
getBean -> MapperFactoryBean.getObject -> getSqlSession().getMapper(this.mapperInterface)(mapperInterface即mapper接口)-> SqlSessionTemplate.getMapper -> MapperRegistry.getMapper(mapperProxyFactory.newInstance(sqlSession)创建mapper接口的代理对象)-> 反射出代理对象Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy),InvocationHandler为mapperProxy
@Bean sqlSessionFactoryBean
实例化的时候,将sql放入mappedStatement,MapperFactoryBean extends SqlSessionDaoSupport extentds DaoSupport implements InitializingBean(实现InitializingBean接口,能在bean实例化之后,进入afterPropertiesSet函数,然后调用父类的初始化过程,将sql语句放入sqlMapperedStatements字典)
mapper.method -> MapperProxy.invoke -> sqlSession.select -> SqlSessionTemplate.select -> sqlSessionFactory.select -> sqlSessionProxy.select -> DefaultSqlSession.select -> MappedStatement ms = configuration.getMappedStatement(statement); prepareStatement
sqlSessionTemplate增强defaultSqlSession,执行语句之后,会commit,会关闭session,一级缓存会失效