MyBatis 是基于 Java 的数据持久层框架,主要解决数据和对象的映射关系。相较于 Hibernate,因为其简单轻量,所以使用也比较广泛,是主流的 ORM 框架之一。与 hibernate 相比,mybatis 不能完全算得上是一个完全的 ORM 框架。因为他只完成了一半。不过话说回来,之所以大家喜欢用它,很大一部分原因就是基于它的轻量便捷。
闲言少叙,开刀问斩。首先我们谈一谈传统方式是怎样使用的。传统意义上讲,连接数据库的方式比较常用 JDBC 大致如下:
......
try {
// 加载驱动
Class.forName(driver);
// 获取connection连接
Connection conn = DriverManager.getConnection(url,user,password);
// 获取statement对象
Statement statement = conn.createStatement();
try {
// 控制事务提交
conn.setAutoCommit(false);
// 执行SQL语句
statement.executeUpdate("UPDATE USER SET AGE=1 WHERE ID=1");
// 事务提交
conn.commit();
} catch (Exception e) {
// 异常回滚
conn.rollback();
throw e;
} finally {
// 关闭statement
if (statement != null) {
statement.close();
statement = null;
}
// 关闭connection
if (conn != null) {
conn.close();
conn = null;
}
}
相较于传统的 JDBC 方式,我们使用 mybatis 略有不同,不过本质还是一样使用 JDBC。使用 mybatis 我们不在手动获取 Connection 连接,而是将它交由 mybatis 来管理。在 mybatis 中使用 SqlSession 来管理资源的连接与关闭,同时通过 SqlSession 来进行各种增删改查操作。
具体如何实现呢,我们需要使用 SqlSessionFactory 来管理 SqlSession,SqlSessionFactory 接口主要是用于打开 session 获取 SqlSession 对象的,具体的实现方法在实现类对象中实现
// SqlSessionFactory接口
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
那 SqlSession 究竟是如创建的呢?我们以 spring 容器管理为例,通过该 SqlSessionFactoryBean 创建 SqlSessionFactory,通过 SqlSessionFactory 获取 SqlSession:
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean,
ApplicationListener<ApplicationEvent> {
实现了 InitializingBean 接口,追踪到 afterPropertiesSet()方法
@Override
public void afterPropertiesSet() throws Exception {
......
this.sqlSessionFactory = buildSqlSessionFactory();
}
这里的 sqlSessionFactory 通过 buildSqlSessionFactory 方法创建
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
// configuration不为空,直接加载
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
// 否则通过configLocation加载
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
// 以上均不能加载,则默认new一个Configuration对象塞进去
} else {
LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// 注册类型别名包
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType)
.forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
// 注册类型别名
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
// 注册各种插件
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
// 注册类型处理包以及类型处理器
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream()
.filter(clazz -> !clazz.isInterface())
.filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
// 根据databaseIdProvider获取数据库类型
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// 注册事务
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
// 通过mapperLocations加载**.Mapper.xml文件,解析mapper文件存入Configuration对象的属性中
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.");
}
// 通过上面的配置创建SqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
// 这里返回的是SqlSessionFactory默认实现类DefaultSqlSessionFactory,通过SqlSessionFactory我们就可以拿到sqlSession了
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
总结:
如果我们自己在单个项目中使用 mybatis 也是很简单的,通过上面的代码可以看到,我们无非就是想获取一个 SqlSessionFactory,通过 SqlSessionFactory 进一步获取 SqlSession,从而执行具体的增删改查。所以我们可以按照上述思路,构建好 Configuration 对象,直接 new 出 SqlSessionFactory。下一节,我们将分析一下 SqlSession。