MyBatis普通过程中的设计模式
代码案例
以下是一个没有使用Spring框架整合后的MyBatis简单使用流程:
public void insertUser() throws IOException {
//获取核心配置文件输入流
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder工厂构建对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//工厂创建SqlSessionFactory
//需要读取配置文件,来调参创造SqlSession
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
//创建SqlSession
SqlSession sqlSession = build.openSession();
//使用SqlSession的getMapper方法,使用反射机制实现动态代理,返回实例化对象
//--------代理模式-------------
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper中的insertUser方法
int count = userMapper.insertUser();
//提交事务.Mybatis配置文件中environment标签下默认存在事务配置transactionManager标签,每次sql都需要存在事务的提交操作
sqlSession.commit();
//打印
System.out.println("Affected rows:"+count);
}
我们定义了一个POJO类、一个Mapper映射接口,一个Mappertaining映射xml文件,和核心配置文件。我们只分析上述的代码。
建造者模式
//获取核心配置文件输入流
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder工厂构建对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//工厂创建SqlSessionFactory
//需要读取配置文件,来调参创造SqlSession
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
以上的代码中需要从MyBatis的核心配置文件中获取参数来得到一个SqlSessionFactory工厂。对于一个SqlSessionFactory来说,MyBatis核心配置文件就是它其中的属性,对于我们业务的不同,MyBatis核心配置文件有些标签是必选的,有些标签是不必须的,就比如environments、mappers标签都是必须的,而properties标签和objectFactory标签则是可选的,所以SqlSessionFactory也是如此,而对于这种多属性的复杂对象来说,建造者模式无疑是最好的选择。
我们从SqlSessionFactoryBuilder的build方法作为入口。
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
build方法返回了一个SqlSessionFactory对象,同时有调用了重载的build方法,传入其他两个空参值。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException var13) {
}
}
return var5;
}
在这个重载的build方法中,首先就声明了一个SqlSessionFactory对象,其次传入输入流和其他两个参数来读取配置文件创建XMLConfigBuilder对象,之后再次调用另一个重载方法,传入XMLConfigBuilder对象parser方法的返回值,一个Configuration对象。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
而在重载方法build中使用创建匿名对象的方式创建了SqlSessionFactory的子类DefaultSqlSessionFactory,并传入Configuration对象。
再进入DefaultSqlSessionFactory的构造函数之前,我们可以先看一下Configuration对象,Configuration对象作为DefaultSqlSessionFactory函数的参数,我们需要知道它做了什么,和它有些什么字段。
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel;
protected boolean cacheEnabled;
protected boolean callSettersOnNulls;
protected boolean useActualParamName;
protected boolean returnInstanceForEmptyRow;
protected boolean shrinkWhitespacesInSql;
protected boolean nullableOnForEach;
protected boolean argNameBasedConstructorAutoMapping;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
protected LocalCacheScope localCacheScope;
protected JdbcType jdbcTypeForNull;
protected Set<String> lazyLoadTriggerMethods;
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ResultSetType defaultResultSetType;
protected ExecutorType defaultExecutorType;
protected AutoMappingBehavior autoMappingBehavior;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
protected Properties variables;
protected ReflectorFactory reflectorFactory;
protected ObjectFactory objectFactory;
protected ObjectWrapperFactory objectWrapperFactory;
protected boolean lazyLoadingEnabled;
protected ProxyFactory proxyFactory;
protected String databaseId;
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry;
protected final InterceptorChain interceptorChain;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final TypeAliasRegistry typeAliasRegistry;
protected final LanguageDriverRegistry languageRegistry;
protected final Map<String, MappedStatement> mappedStatements;
protected final Map<String, Cache> caches;
protected final Map<String, ResultMap> resultMaps;
protected final Map<String, ParameterMap> parameterMaps;
protected final Map<String, KeyGenerator> keyGenerators;
protected final Set<String> loadedResources;
protected final Map<String, XNode> sqlFragments;
protected final Collection<XMLStatementBuilder> incompleteStatements;
protected final Collection<CacheRefResolver> incompleteCacheRefs;
protected final Collection<ResultMapResolver> incompleteResultMaps;
protected final Collection<MethodResolver> incompleteMethods;
protected final Map<String, String> cacheRefMap;
我们进入Configuration类后,就会发现,它的属性足足有53条,除去近一半使用final修饰的"最终属性",还有近30多条,而这些属性名恰恰与我们MyBatis核心配置文件中的标签名近似相同,比如environment、objectFactory、databaseId等等,其中一部分的属性名像是配置文件中的标签名+属性名组合的方式。这也恰恰说明Configuration存储的是核心配置文件中的信息,XMLConfigBuilder对象读取核心配置文件后,构建了Configuration对象。
然后我们继续探究DefaultSqlSessionFactory,首先我们查看DefaultSqlSessionFactory中的属性:
private final Configuration configuration;
只有一个Configuration类型的属性,而刚刚则查看了Configuration的作用:Configuration负责存储核心配置文件中的配置信息;而对于建造者模式而言,主要负责构建一些比较复杂的对象,复杂的对象对应的就应该是:多元的、可变的、较多的属性;但是DefaultSqlSessionFactory只有一个Configuration类型的属性,这里就是MyBatis使用了巧妙的方法,**将建造者模式需要建造的属性进行封装
,单独构建一个配置类出来,这样就实现了建造者建造功能和属性的分离。**到这里就使用建造者模式构建出了一个复杂的对象。
工厂模式
//创建SqlSession
SqlSession sqlSession = build.openSession();
通常来说MyBatis的执行需要SqlSession会话对象,但是MyBatis中的SqlSession代表的是对sql的会话,对于不同的会话所需要的参数也都不尽相同,而为了避免暴露创建一个会话的逻辑参数,MyBatis选择使用工厂模式进行抽象,于是有了SqlSessionFactory对SqlSession进行构建。
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
我们使用DefaultSqlSessionFactory中openSession方法后,会传入configuration属性的defaultExecutorType字段去调用openSessionFromDataSource构建SqlSession会话。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
再openSessionFromDataSource方法中,首先声明了两个变量,
- Transaction接口,其中封装执行原生JDBC操作的一些方法和连接对象:
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
其中有Connection数据库连接对象,和事务的提交回滚、连接关闭、耗时计算方法。
- DefaultSqlSession对象,它是SqlSession接口的实现,来进行sql会话操作。
之后使用configuration.getEnvironment()获取环境配置,传入参数创建TransactionFactory对象,之后就是通过参数传递,创建出DefaultSqlSession对象。
类图:
代理模式
主要分析下面一行代码:
//使用SqlSession的getMapper方法,使用反射机制实现动态代理,返回实例化对象
//--------代理模式-------------
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
在上述的源码中,我们可以得出,sqlSession对象返回的其实是它的实现类DefaultSqlSession,我们直接去看DefaultSqlSession的源码。
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
getMapper方法使用泛型和反射机制,调用Configuration中的getMapper方法,并传入自身的实例化对象;
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
而Configuration中的getMapper方法又调用mapperRegistry属性的getMapper方法;
在MapperRegstry类中,它的结构是这样的。
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);
}
}
}
首先MapperRegistry有knownMappers属性,是HashMap类型,用来获取mapperProxyFactory工厂类
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
之后会进行判断,是否有这个类工厂,这个类工厂添加Mapper集合的方法对应Configuration中addMapper()方法,还记得之前的XMLConfigBuilder类吗,其中parse方法会被用来加载Configuration对象,加载时会调用parseConfiguration方法,而parseConfiguration方法就是加载配置文件中所有的标签的。
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
//加载mappers标签,调用mapperElements方法。
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
而在加载标签时,mappers中的标签会根据package和mapper标签不同来进行不同方式的加载
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
try {
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} catch (Throwable var12) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var10) {
var12.addSuppressed(var10);
}
}
throw var12;
}
if (inputStream != null) {
inputStream.close();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
try {
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} catch (Throwable var13) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var11) {
var13.addSuppressed(var11);
}
}
throw var13;
}
if (inputStream != null) {
inputStream.close();
}
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
其中都会使用this.configuration.addMapper
这个方法将其加入到MapperRegistry中的knownMappers集合中。
所以到这里,getMapper方法的作用已经很明确了,就是用来判断有没有这个类对应的mapperProxyFactory对象,有则返回,没有则返回null。
如果有之后则会去调用mapperProxyFactory中newInstance方法通过反射获取实体对象。
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
之后就是java.lang包下的Proxy类的底层了。
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)