Mybatis的初始化过程,其实就是加载配置的过程; mybatis的配置方式有两种:xml和java api config,本文以java api为例,跟踪mybatis的初始化过程;
先来一段使用代码:
// 数据源
public static DataSource initDataSource() {
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://xxx.xxx.xxx.xxx:3306/mybatis?characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// 构建数据库事务方式
public static TransactionFactory initTransactionFactory() {
return new JdbcTransactionFactory();
}
// 数据库运行环境
public static Environment initEnvironment() {
DataSource dataSource = initDataSource();
TransactionFactory transactionFactory = initTransactionFactory();
return new Environment.Builder("development").dataSource(dataSource).transactionFactory(transactionFactory).build();
}
public static Configuration initConfiguration() {
// 配置对象
Configuration configuration = new Configuration(initEnvironment());
// 注册别名
//configuration.getTypeAliasRegistry().registerAlias("map", Map.class);
configuration.getTypeAliasRegistry().registerAliases("pub.tbc.stu.mybatis.domain");
// 映射器
configuration.addMappers("pub.tbc.stu.mybatis.mappers");
// 添加插件
configuration.addInterceptor(new TimerInterceptor());
return configuration;
}
// 使用Configuration构建SessionFactory
public static SqlSessionFactory initAndReturnSqlSessionFactory() {
Configuration configuration = initConfiguration();
// return new SqlSessionFactoryBuilder().build(configuration);
return new DefaultSqlSessionFactory(configuration);
}
@Test
public void test() {
// 获取SqlSession,线程不安全
SqlSession sqlSession = initAndReturnSqlSessionFactory().openSession();
// 通过SqlSession获取Mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行
userMapper.addUser(User.builder().name("zhangsan").age(18).build());
List<User> users = userMapper.users();
}
可以看出,在正式运行我们的逻辑代码(test()方法)之前,这段代码做了几件事情(附对应的XML配置):
- 定义数据源 =>
<dataSource type="POOLED"><property name="driver"......</dataSource>
- 定义TransactionFactory事务工厂 =>
<transactionManager type="JDBC"/>
- 以数据源和事务工厂构造Environment =>
<environment>...</environment>
) - 创建Configuration,传入Environment对象;
- 注册别名 =>
<typeAliases><package name="pub.tbc.stu.mybatis.domain" /></typeAliases>
- 指定映射器扫描目录 =>
<mappers> <package name="pub.tbc.stu.mybatis.mappers" /> </mappers>
- 添加插件 =>
<plugins><plugin interceptor=pub.tbc.stu.mybatis.extend.plugin.TimerInterceptor"></plugin></plugins>
- 创建SqlSessionFactory接口的默认实现DefaultSqlSessionFactory,传入Configuration对象; 至此,配置已完成,接下来,就可以在方法中获取SqlSessionFactory工厂并获取SqlSession使用了;
注:
- 实际中一般SqlSessionFactory需要保持全局单例;
- 实际项目中一般会使用第三方的数据库连接池构建数据源,并使用第三方事务管理器(spring大法好)
- 附XML方式进行配置时,这样创建SqlSessionFactory:
// 使用XML配置的方式,创建SqlSessionFactory
public static SqlSessionFactory xmlInit() throws IOException {
String resource = "mybatisConfiguration.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
/ 分割线 //
下面将结合源码,分析以上代码创建 SqlSessionFactory 的过程中,mybatis做了什么 (其实以上纯java api的方式配置mybatis,已经基本可以看出mybatis的初始化过程了);
我们先来看数据源DataSource和事务管理器TransactionFactory, 这两个都是接口,基于面向接口编程原则,可以很方便的基本成别的实现,DataSource来源于javax.sql包,属于java jdbc的一部分; mybatis中默认的实现是PooledDataSource和JdbcTransactionFactory(实际中通常使用第三方的实现),这两个都是使用new实例然后设置属性的普通方法来创建,并没有什么牛逼的设计和模式,不必多说;
再来看看Environment,由于上面的示例是使用构建器方式创建的,先看一下Environment.Builder的build()方法:
public Environment build() {
return new Environment(this.id, this.transactionFactory, this.dataSource);
}
OK,仍然是new,那么继续看Environment构造方法:
public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
if (id == null) {
throw new IllegalArgumentException("Parameter 'id' must not be null");
}
if (transactionFactory == null) {
throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
}
this.id = id;
if (dataSource == null) {
throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
}
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}
只是把参数设置为属性,备用而已,什么时候用且不管,继续看大而全的Configuration;
上面示例中使用new Configuration(initEnvironment());
创建 Configuration,看构造方法:
public Configuration(Environment environment) {
this();
this.environment = environment;
}
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
可以看出,Configuration内部维护了一个Environment属性,构造时赋值,并且调用无参构造器,在无参构造器中,使用typeAliasRegistry注册了一系列别名,typeAliasRegistry是Configuration内部维护的别名注册器:TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
然后,我们可以使用Configuration对象进行一些配置,如:
- 注册别名,实际是调用内部维护的别名注册器进行注册,可以注单个类的别名映射,也可以指定包名,由系统自己扫描包下的全部类;
- 添加映射器,跟别名一样,通过调用内部的映射器注册器
mapperRegistry
来进行注册,同样可以指定包名的方式由系统自己扫描 ; - 添加插件,通过
configuration.addInterceptor(new TimerInterceptor());
来添加插件,看一下该方法源码:
// from class org.apache.ibatis.session.Configuration
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
可以看到,实际是调用内部InterceptorChain
对象的addInterceptor
方法,该方法内部是把拦截器(插件)添加到内部的列表中:
// from class org.apache.ibatis.plugin.InterceptorChain
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
- 除以上示例外,我们还可以通过Configuration对象进行其它配置,所以在xml中支持的配置,都可以通过Configuration进行编程;
有必要首重补充下,mybatis中所有的配置都是通过加载到Configuration对象中进行保存以备使用的,如setting标签中的所以配置项,都可以通过configuration.set...sertter方法进行配置,Configuration对象内部维护了大量属性,mybatis初始化加载配置的过程,就是填充这些属性(部分调用属性的对应方法,如别名、映射器等):
protected Environment environment;
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected boolean useActualParamName = true;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected Class <? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
XML方式进行配置,初始化方式也差不多,用XMLConfigBuilder构建Configuration,看代码:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 这一句,创建XMLConfigBuilder 对象,调用parse方法生成Configuration
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
那么,关键应该就在于parse方法了:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
具体解析不看,猜也猜的到,挨个儿读取配置中的节点,然后设置到configuration对象中,跟通过代码的方式配置没有什么不同;
很简单,初始化就做了这些工作,然后new一个默认的Session工厂,传入Configuration对象,即可用来获取SqlSession了;
注: SqlSession是线程不安全的,而xxxMapper的代理对象又来自于SqlSession,也是线程不安全的,因此要在方法中使用; 但是,实现项目中,通常是使用注入的方式,将mapper对象注入到自己的应用中,纳尼?怎么个情况呢?事实就是注入的SqlSession或者Mapper是定制的,比如spring-mybatis,相当于扩展了 MyBatis,在其中单独提供了线程安全的mapper注入到应用,可以放心使用,跟通常情况下自己单独使用mybatis是不一样的;
后面补充所有属性的意义,未完待续...