您的位置:首页 > 其它

Mybatis的源码与流程解析

2018-01-02 09:43 537 查看
使用Mybatis需要配置好数据源与SqlSessionFactory,如果要使用事务则需额外配置事务管理器,我自己的小框架使用的是基于Java形式的配置,也可以使用XML文件形式配置。我把我自己的配置文件放上来,想尝试这种方式的可以借鉴下。

@Configuration
@MapperScan(value = "com.bob.mvc.mapper", markerInterface = BaseMapper.class)
public class DataAccessContextConfig {

private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
private static final String USERNAME = "root";
private static final String PASSWORD = "lanboal";

/**
* MySQL的JDBC URL编写方式:jdbc:mysql://主机名称:连接端口/数据库的名称?参数=值
* 避免中文乱码要指定useUnicode和characterEncoding
* 执行数据库操作之前要在数据库管理系统上创建一个数据库,名字自己定,
* 下面语句之前就要先创建project数据库
*/
private static final String URL = "jdbc:mysql://localhost:3306/project?useUnicode=true&characterEncoding=UTF8&useSSL=false";

@Bean(destroyMethod = "close")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(DRIVER_CLASS_NAME);
//针对mysql获取字段注释
dataSource.addConnectionProperty("useInformationSchema", "true");
//dataSource.addConnectionProperty("remarksReporting","true");  针对oracle获取字段注释
dataSource.setUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
dataSource.setMaxTotal(50);
dataSource.setMinIdle(5);
dataSource.setMaxIdle(10);
return dataSource;
}

@Bean
public DataSourceTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}

/**
* @param dataSource
* @return
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// 配置MapperConfig
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();

//当数据库集群时,配置多个数据源,通过设置不同的DatebaseId来区分数据源,同时sql语句中通过DatabaseId来指定匹配哪个数据源
//configuration.setDatabaseId("Mysql-1");

// 这个配置使全局的映射器启用或禁用缓存
configuration.setCacheEnabled(true);

// 允许 JDBC 支持生成的键,需要适合的驱动(如MySQL,SQL Server,Sybase ASE)。
// 如果设置为 true 则这个设置强制生成的键被使用,尽管一些驱动拒绝兼容但仍然有效(比如 Derby)。
// 但是在 Oracle 中一般不需要它,而且容易带来其它问题,比如对创建同义词DBLINK表插入时发生以下错误:
// "ORA-22816: unsupported feature with RETURNING clause" 在 Oracle
// 中应明确使用 selectKey 方法
//configuration.setUseGeneratedKeys(false);

// 配置默认的执行器:
// SIMPLE :> SimpleExecutor  执行器没有什么特别之处;
// REUSE :> ReuseExecutor 执行器重用预处理语句,在一个Service方法中多次执行SQL字符串一致的操作时,会复用Statement及Connection,
// 也就是说不需要再预编译Statement,不需要重新通过DataSource生成Connection及释放Connection,能大大提高操纵数据库效率;
// BATCH :> BatchExecutor 执行器重用语句和批量更新
configuration.setDefaultExecutorType(ExecutorType.REUSE);
// 全局启用或禁用延迟加载,禁用时所有关联对象都会即时加载
configuration.setLazyLoadingEnabled(false);
// 设置SQL语句执行超时时间缺省值,具体SQL语句仍可以单独设置
configuration.setDefaultStatementTimeout(5000);

SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setConfiguration(configuration);
// 匹配多个 MapperConfig.xml, 使用mappingLocation属性
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/bob/mvc/mapper/*Mapper.xml"));
return sqlSessionFactoryBean.getObject();
}
}


配置Mybatis最主要的是配置SqlSessionFactory实例,当前配置基于Java形式,通过sqlSessionFactoryBean.getObject()方法生成SqlSessionFactory 实例,在此方法中会解析指定的Mybatis Mapper文件,将XML文件内的元素节点按类别解析生不同的java类对象,存入SqlSessionFactory的Configuration中。

public class XMLMapperBuilder extends BaseBuilder {
......
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap节点,生成ResultMap对象,存入Configuraion中
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//解析增删改查节点,生成MappedStatement对象,存入Configuraion中
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}

}


如何解析,流程很繁琐,我就不一一列举了,感兴趣的可以就着XMLMapperBuilder 的方法自行查看。

解析Mybatis的Mapper文件,将生成的结果存入Conguration中,Conguration就相当于Myabtis的配置容器,Conguration又被SqlSessionFactory持有,当通过SqlSessionFactory生成SqlSession实例时,也会持有Conguration对象。

解析Mapper配置文件和扫描Mapper接口没有必然的先后顺序,这里只是先讲解解析,后讲解扫描。

通过@MapperScan注解,指定扫描的Mapper接口包。@MapperScan会引入MapperScannerRegistrar这个Bean,它会根据注解的属性相应的执行自己的逻辑。MapperScannerRegistrar扫描指定包下的所有接口,根据指定的规则对这些接口做过滤,针对这个接口注册BeanDefinition,如果注解上有自定义信息,这个将这些信息注入接口的BeanDefinition中,在通过这些BeanDefinition生成Bean实例时会从容器中寻找相应的Bean注入到接口的Bean中。这些接口的Bean实例是MapperFactoryBean对象(一个FactoryBean),真正注入Mapper对象时,实际注入的是通过MapperFactoryBean对象的getObject()方法生成的对象。

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;
}

/**第2步
* {@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();
}
}
}

/**第3步
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

}

public abstract class SqlSessionDaoSupport extends DaoSupport {

private SqlSession sqlSession;

private boolean externalSqlSession;

//第1步
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
//这步其实很关键,但是需要配合着事务来说,在下一篇博客中会重点讲解。
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}

}


MapperFactoryBean对象在实例化时 :

第一步会调用其父类SqlSessionDaoSupport 的setSqlSessionFactory()方法, 当@MapperScan指定SqlSessionFactory或者SqlSessionTemplate的BeanName时,从Spring容器中获取相应名称的Bean;当未指定时,Mapper接口的BeanDefinition的autowireMode=AUTOWIRE_BY_TYPE,Spring通过BeanDefinition实例化Bean时,会从解析BeanDefinition内未赋值的非简单属性,从容器中获取SqlSessionFactory,SqlSessionTemplate类型的Bean注入其中,当你定义了相应类型的Bean就能赋值入Mapper接口生成的Bean中。属性的赋值使用setter方法。当赋值SqlSessionFactory时将sqlSession属性初始化为一个SqlSessionTemplate对象。

第二步会调用Configuration的addMapper()方法,这个方法会将当前接口的Class和一个MapperProxyFactory对象关联映射起来,间接存入Configuration中。

通过MapperFactoryBean的getObject()方法获取Mapper接口实际注入的对象,这个方法最终是调用MapperProxyFactory的newInstance(SqlSession)方法,

先通过sqlSession及Mapper接口生成MapperProxy对象,MapperProxy是一个InvocationHandler实现类,最后通过JDK Proxy的newProxyInstance方法,以MapperProxy,Mapper接口,及ClassLoader为参数,为Mapper接口生成动态代理对象,这个对象就是实际被注入到Service层的Mapper对象,所以实际的Mybatis执行逻辑都在MapperProxy对象的invoke()方法中。

public class MapperProxyFactory<T> {

private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
......
@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<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

}


MapperProxy在执行时会先通过当前方法生成MapperMethod,在MapperMethod里会先通过此方法找到对应的Mapper.xml里的相应节点解析成的MappedStatement,接口名称对应namespace,方法名称对应节点名称,获取这个节点的sql类型,然后提取出当前方法的参数,然后通过sqlSession和方法参数执行相应的操作。

public class MapperProxy<T> implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

@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 (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;
}
......
}


执行增删改查的哪一种由sql节点的类型确定,返回单个还是多个由方法的返回值类型确定,这两个因素确定后通过sqlSession的相应方法执行,获取结果。

这里的sqlSession实际上是一个DefaultSqlSession对象,SqlSessionTemplate持有一个sqlSession类型的属性,名称是sqlSessionProxy,自身调用方法均会传递给此属性执行,这个属性是通过JDK动态代理生成的,代理了DefaultSqlSession对象,DefaultSqlSession对象时每个线程独立的,当没有事务时立即通过sessionFactory实时生成一个,当有事务时可以复用此DefaultSqlSession对象。

DefaultSqlSession在执行时会先通过从Configuration中找到当前方法对应的MappedStatement对象,然后MappedStatement根据当前方法的参数,动态的生成BoundSql对象,基本是通过当前参数集合,与DynamicSqlSource的MixSqlNode相匹配(循环递归式),如果匹配上,就加上SqlNode里的sql字符串,匹配完之后就生成一个sql字符串,然后将里面的参数”#{}”替换为”?”,同时生成有序的List< ParameterMapping > ,这个顺序一定要和?的顺序相同。之后将替换过的sql字符串和参数集合放进一个BoundSql对象里返回。

当得到了BoundSql对象时,就可以去执行了,此时需要执行器对象,默认配置是SimpleExecutor实例。

一般的MappedStatement的StatementType是PREPARED,也就是说会有预编译的语句类型,每一个sql在执行前会预编译(若sql存在字符拼接或者不存在参数则不预编译),这样就能防止sql注入。

SimpleExecutor先用PreparedStatementHandler去预编译sql语句,生成Statement,如果当前方法配置了事务,且执行器配置为ReuserExexutor,则这个Statement可以实现复用,若没有则不能复用。

PreparedStatementHandler之后将参数设置到编译后的sql中,形成当前参数下的Statement。

最后PreparedStatementHandler执行此Statement,解析结果,将得到的结果返回。

关键点总结:

通过配置文件或者Configuration的配置实例化SqlSessionFactory。

在实例化SqlSessionFactory时解析Mapper.xml文件,按节点类型不同生成不同的Java对象存入Configuration中。

扫描指定包下的Mapper接口,注册BeanDefinition,最后这些Bean被注入到Service层时是通过JDK Proxy动态生成的接口代理类对象。MapperProxy是InvocationHandler,Mapper的执行逻辑都在其Invoke方法中。

MapperProxy根据当前方法生成MapperMethod。

MapperMethod在实例化时会确定当前方法的执行类型,时增删改查的哪种,然后提取参数,最后由SqlSession去执行此操作。

SqlSession执行时,先找到当前方法对应的MapperStatement,然后由Executor去执行。

Executor在执行时,先根据当前参数从MapperStatement生成BoundSql,然后由StatementHandler根据BoundSql生成Statement,MappedStatement的StatementType是PREPARED,也就是在Statement生成后会预编译sql语句,这样能防止sql注入。

StatementHandler执行相应的Statement,解析结果,返回。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mybatis