Mybatis
什么是Mybatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程、高级映射以及动态SQL。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 、半XML半注解和全注解的方式,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
Mybatis的结构层次
我们把Mybatis的功能架构分为三层:
1.API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
2.数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
3.基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
Mybatis的工作流程
1.通过SqlSessionFactoryBuilder API读取配置文件(mybatis-config.xml等),创建SqlSessionFactory工厂对象DefaultSqlSessionFactory,DefaultSqlSessionFactory是SqlSessionFactory的实现类。
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//XPathParser的createDocument方法对配置文件进行解析
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//将解析出来的文件存放到Configuration对象中
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
//使用带有Configuration的构造方法发返回一个DefautSqlSessionFactory
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
2.然后SqlSessionFactory创建SqlSession对象,其实是DefaultSqlSession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//环境数据对象
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//获取事务管理器,分为JdbcTransactionFactory与ManagedTransactionFactory
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//默认使用SimpleExecutor,还有ReuseExecutor、BatchExecutor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
3.SqlSession接口调用Dao层,通过反射创建代理对象,代理对象调用SqlSession的具体实现子类DefaultSqlSession的方法,在方法内部调用Executor接口的实现子类(由Configuration接口创建)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//默认使用SimpleExecutor,
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) interceptorChain.pluginAll(executor);
return executor;
}
4.SimpleExecutor调用底层的JDBC接口操作数据库。
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
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.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
Mybatis中用到的几种设计模式
Builder 建造者模式
Builder模式的定义是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。一般用于构建复杂对象,所构建的对象往往需要多步初始化或赋值才能完成。使用Builder模式就是用来替代多参数构造函数。
在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xml和所有的*Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) { /..}
public SqlSessionFactory build(Reader reader, String environment) { /..}
public SqlSessionFactory build(Reader reader, Properties properties) { /.. }
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { /.. }
...
XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取*Mapper文件
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
...
public XMLConfigBuilder(Reader reader) { /.. }
public XMLConfigBuilder(Reader reader, String environment) { /.. }
public XMLConfigBuilder(Reader reader, String environment, Properties props) { /.. }
...
通过以上两种情况我们可以总结出:
当使用无参构造函数,用setter方法来设置对象的初始值需要一长串的setter方法,
或者使用带参构造函数,而参数数量较多,需要的构造函数指数上升,
我们可以使用builder模式来创建对象,或者使用接下来说到的工厂模式。
Factory 工厂模式
前文说过工厂模式也是创建对象的一种方法,SqlSessionFactory使用的就是一种简单工厂模式,也叫静态工厂模式。
//接口SqlSessionFactory
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
...
//实现DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
openSessionFromDataSource方法先从configuration读取对应的环境配置,然后初始化TransactionFactory获得一个Transaction对象,然后通过Transaction获取一个Executor对象,最后通过configuration、Executor、autoCommit生成DefaultSqlSession对象。
Singleton 单例模式
单例模式为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法,它的核心结构中只包含一个被称为单例的特殊类,即一个类只有一个对象实例。
一般实现要求一下几点:
1.私有的构造方法,例类不能在其它类中实例化,只能被自身实例化;
2.拥有一个保存类的实例的静态成员变量;
3.一个静态的公共方法用于实例化这个类,并访问这个类的实例;
4.private的clone方法(可选);
在Mybatis中有两个地方用到单例模式,ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息.
public class ErrorContext {
//保存类的实例的静态成员变量
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
//私有的构造方法
private ErrorContext() {
}
//静态的公共方法用于实例化这个类
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
}
在本例中静态实例变量使用了ThreadLocal修饰,代表说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。
LogFactory单例模式
public class LogFactory {
private static Constructor logConstructor;
private static void tryImplementation(String testClassName, String implClassName) {
if (logConstructor == null) {
try {
Resources.classForName(testClassName);
Class implClass = Resources.classForName(implClassName);
logConstructor = implClass.getConstructor(class$java$lang$Class == null
? (class$java$lang$Class = class$("java.lang.Class"))
: class$java$lang$Class);
} catch (Throwable var3) {
;
}
}
}
}
Proxy 代理模式
代理模式是为某一个对象提供一个代理对象,并由代理对象控制对原对象的访问,代理对象起到中介作用,可去掉功能服务或增加额外的服务。代理模式是Mybatis的核心模式之一,它支撑我们只需要编写Mapper.java接口,而不需要实现,由Mybatis后台帮我们完成具体SQL的执行。
MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果。
以下是对JDK动态代理的简单运用,仅供参考!
/**
* JDK动态代理模式
* JDK动态代理的实现关键在于java.lang.reflect.Proxy类
* 其newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)方法是整个JDK动态代理的核心,用于生成指定接口的代理对象。
* 这个方法有三个参数,ClassLoader表示加载动态生成的代理类的类加载器,interfaces代理类需要实现的接口,InvocationHandler调用处理器InvocationHandler
*/
//使用房主、中介、租客三位角色,手写调用处理器
class JDKDynamicProxyHandler implements InvocationHandler {
private Person householder;
public JDKDynamicProxyHandler(Person householder) {
this.householder = householder;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
method.invoke(householder, args);
after();
return null;
}
public void before(){
System.out.println("考察一下");
}
public void after(){
System.out.println("决定了");
}
}
//实例
class JDKDynamicProxy {
public static void main(String[] args) {
Person houseHolder = new Householder();
/**
* 通过
* Person.class.getClassLoader() 类加载器 生成代理类的字节码文件
* new Class[]{Person.class}共同实现Person接口 保证真实角色与代理角色的强一致性
* new JDKDynamicProxyHandler(houseHolder)手写的调用处理器 最终调用invoke()进行处理
* 获得代理对象
*/
Person proxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new JDKDynamicProxyHandler(houseHolder));
proxy.rent();
}
}
其中最主要的一行代码就是Proxy.newProxyInstance();
在MapperProxyFactory这个类中我们也能看到这样一行代码
public class MapperProxyFactory<T> {
//...
@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);
}
}
其中的mapperInterface.getClassLoader()可以很明显的看出这就时在获取dao接口的类加载器,进而用这个方法创建它的代理对象。
再看一下MapperProxy这个类
public class MapperProxy<T> implements InvocationHandler, Serializable {
//...
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
从代码不难看出,它是先通过cachedMapperMethod方法找到与要执行的dao接口方法对应的MapperMethod,再调用MapperMethod的execute方法来实现数据库的操作。
与君共勉!!