介绍
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
上述是Mybatis官网介绍,个人认为Mybatis是个持久层框架,通过我们配置的XML或注解,先解析文件后通过jdbc访问数据库返回我们想要的数据。
简单使用
配置文件 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="mysql.properties"/>
<!-- 环境,可以配置多个,default:指定采用哪个环境 -->
<environments default="test">
<!-- id:唯一标识 -->
<environment id="test">
<!-- 事务管理器,JDBC类型的事务管理器 -->
<transactionManager type="JDBC" />
<!-- 数据源,池类型的数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/TestMapper.xml" />
</mappers>
</configuration>
Mapper配置 TestMapper.xml与对应TestMapper.java
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="TestMapper">
<select id="selectTest" resultType="pojo.Test">
select * from test where id = #{id}
</select>
</mapper>
public interface TestMapper {
void selectTest(@Param("id") String id);
}
Mybatis调用
public class MybatisTest {
public static void main(String[] args) throws IOException {
// 指定全局配置文件
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
System.out.println(mapper.selectTest("1"));
} finally {
sqlSession.close();
}
}
}
运行结果:
Test{id=1, name='11'}
通过Mybatis调用过程,我们可以大概猜测Mybatis运行过程:
1、解析配置文件,包括数据库配置、sql等
2、通过动态代理生成Mapper接口的实现类
3、调用Mapper实现类,填充数据,jdbc访问数据库
4、返回数据
源码解析
构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
最终调用-->
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//构造XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析配置文件成Configuration对象
//new DefaultSqlSessionFactory(config)生成SqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();//关闭流
} catch (IOException e) {
}
}
}
解析配置文件
public Configuration parse() {
//已解析过直接报错
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//parsed设为true
parsed = true;
//解析配置文件中configuration节点下内容
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//解析configuration节点
//可以看出configuration下有下面这些子节点
private void parseConfiguration(XNode root) {
try {
//配置属性,可在properties中配置数据库访问相关信息
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
//用于配置拦截器,增强功能
pluginElement(root.evalNode("plugins"));
//对象工厂,用来创建实体对象的类
objectFactoryElement(root.evalNode("objectFactory"));
//对象加工工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//设置属性,如defaultExecutorType、cacheEnabled等
settingsElement(root.evalNode("settings"));
//数据库环境配置
environmentsElement(root.evalNode("environments"));
//定义多数据库支持
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//类型处理器,通过typeHandlers完成jdbc类型和java类型的转换
//mybatis提供的类型处理器满足日常需要,不需要自定义,特殊情况下需要
typeHandlerElement(root.evalNode("typeHandlers"));
//mapper文件,一般为mapper.xml或注解
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
这边我们重点关注mapper文件的解析
下面源码可以看出,mapper可以由package或mapper标签组成,mapper标签可配置resource、url、class的一种属性,配置超过1种则无法解析。
package:扫描包下所有mapper文件
mapper:resource:具体mapper.xml文件
mapper:url:网络url,本质都为二进制流
mapper:class:解析class文件
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//循环解析所有mapper节点
for (XNode child : parent.getChildren()) {
//解析package下文件
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//分别解析 resource url class属性
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
我们选取解析resource,看下具体实现
private void configurationElement(XNode context) {
try {
//获取xml中namespace,为空则报错
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析parameterMap标签,最终解析成ParameterMapping对象
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap标签,最终解析成ResultMapping对象
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete标签,最终解析成MappedStatement对象
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
解析select|insert|update|delete标签最终会添加至Configuration类mappedStatements属性,mappedStatements为StrictMap类,是继承HashMap自实现的类,put重复key会报错。所以我们一个key即namespace+xml中select|insert|update|delete标签的id属性或者使用注解时类名+方法名,命名重复则会报错。
总之,所有配置文件最终会解析至Configuration对象。
获取sqlSession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//获取数据库环境数据
final Environment environment = configuration.getEnvironment();
//构建Transaction事务对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//获取Executor执行器
final Executor executor = configuration.newExecutor(tx, execType);
//构建SqlSession对象
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();
}
}
这边重点说下Executor,看下configuration.newExecutor方法。
根据下面源码,Executor可分为BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。
默认的executorType为ExecutorType.SIMPLE,即Executor为SimpleExecutor。
1、SimpleExecutor是最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作
2、BatchExecutor执行器,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。
3、ReuseExecutor 可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。
4、CachingExecutor,用于二级缓存,在基础的Executor上包装一层构成CachingExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//executorType未设置则使用默认的ExecutorType.SIMPLE
executorType = executorType == null ? defaultExecutorType : executorType;
//executorType任为null则用ExecutorType.SIMPLE,确保executorType有值
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据不同的executorType来创建
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);
}
// cacheEnabled 也就是我们配置的二级缓存,如果该值配置为true,则获取的是CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//通过责任链模式来生成代理对象
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
可以看出,sqlSessionFactory.openSession()时并未访问数据库,只是单纯构造SqlSession对象。
获取Mapper对象
DefaultSqlSession.getMapper --> Configuration.getMapper
–> MapperRegistry.getMapper
因为在解析阶段已将Mapper解析成Configuration属性,所以直接调用Configuration方法,最终调用下述方法,很明显最终使用JDK动态代理返回Mapper的实现类,这也是为什么我们使用Mybatis时只需要写接口类,并未写实现,但最终任然可以得出结果,因为Mybatis已经帮我们完成中间的事情。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
//调用动态代理
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//动态代理
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//JDK动态代理,生成实现类
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
动态代理生成Mapper实现类
动态代理最重要的是InvocationHandler参数,我们看下Mybatis生成Mapper实现类的InvocationHandler,即MapperProxy类。
//MapperProxy实现InvocationHandler
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;
}
//反射最终调用invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法如果定义在Object类,则直接调用Object方法,如toString,hashCode等方法
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//调用cachedMapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
//缓存MapperMethod对象
private MapperMethod cachedMapperMethod(Method method) {
//缓存中是否存在,存在则使用缓存中
//不存在则创建,然后放入缓存
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//构建MapperMethod对象
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
继续看MapperMethod.execute方法,
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//command.getType()在初始化SqlCommandType对象时生成
if (SqlCommandType.INSERT == command.getType()) {
//封装参数
Object param = method.convertArgsToSqlCommandParam(args);
//调用sqlSession.insert()
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
//调用sqlSession.update()
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
//调用sqlSession.delete()
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
//方法返回void且存在结果处理器,传入结果处理器,返回null
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {//select返回list
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {//select返回map
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
//selectOne返回对象,实际会调用sqlSession.selectList方法
//存在结果大于1报错,等于1返回list.get(0)
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
//返回null或者返回类型为基本类型
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
//生成SqlCommand对象,生成MapperMethod对象时会初始化SqlCommand
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
//根据接口名.方法名获取MappedStatement
//一个MappedStatement即对应一个select|delete|update|insert标签
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
//判断是否拿出MappedStatement对象
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) {
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
//未拿到MappedStatement报错
if (ms == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
//取出MappedStatement的id和CommandType
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
sqlSession.selectList()
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//调用Executor执行器方法
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
经过一系列流程,最后调用PreparedStatementHandler.query。这个方法是不是很眼熟,就是通过jdbc的PreparedStatement访问数据库,将查询结果传给resultSetHandler结果处理器,将结果转换成我们需要的类型,最后返回给我们。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//强转成PreparedStatement对象
PreparedStatement ps = (PreparedStatement) statement;
//调用PreparedStatement的execute方法
ps.execute();
//将结果传给结果处理器,转换结果类型
return resultSetHandler.<E> handleResultSets(ps);
}
我们再看下PreparedStatementHandler.update方法
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//PreparedStatement只需sql
ps.execute();
//获取影响条数并返回
int rows = ps.getUpdateCount();
//判断参数
Object parameterObject = boundSql.getParameterObject();
//获取KeyGenerator
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
//KeyGenerator赋值
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
总结
1、mapper在xml中分为4种,package、mapper-resource、mapper-url、mapper-class
2、Executor可分为BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor,默认使用SimpleExecutor
3、mybatis通过JDK动态代理生成接口的实现类
4、mybatis使用PreparedStatement访问数据库