MyBatis的整体分为基础支持层、核心处理层、接口。
MyBatis自身提供了相应的数据源实现,也提供了与第三方接口数据源集成的接口,这些功能都位于数据源模块之中。
MyBatis对数据库中的事务进行了抽象,其自身提供了相应的事务接口和简单的实现,在很多场景中,MyBatis会与Spring框架集成,并由Spring框架管理事务相关配置。
MyBatis中提供了一级缓存和二级缓存,而这两级缓存都依赖于基础支持层中的缓存模块实现。
开发人员无须编写自定义 Mapper接口的实现,MyBatis会自动为其创建动态代理对象,
MyBatis 通过 Binding 模块将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作。
MyBatis中专门提供了反射模块,该模块对Java原生的反射进行了一系列优化,例如缓存了类的元数据,提高了反射的性能。
该功能在为 SQL 语句绑定实参以及映射查询结果集时都会涉及,将数据由 Java类型转换成 JDBC 类型。
MyBatis除了提供详细的日志输出信息,还能够集成第三方日志框架。
SLF4J 、LOG4J 、LOG4J2
JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING
NO_LOGGING
对类加载器进行封装,确定类的使用顺序,提供加载类文件以及其他资源文件。
主要提供两个功能:
1.对XPath进行封装,为MyBatis初始化时解析mybatis-config.xml配置文件以及映射配置文件提供支持;
2.为处理动态sql语句中的占位符提供支持。
MyBatis 初始化时,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。
MyBatis 中的scripting模块会根据用户传入的实参,解析映射文件中定义的动态SQL节点,并形成数据库可执行的SQL 语句。
之后会处理 SQL 语句中的占位符,绑定用户传入的实参。
SQL语句的执行中重要的组件:Executor、StatementHandler、ParameterHandler、ResultSetHandler。
Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,将数据库相关操作委托给 StatementHandler 完成。
StatementHandler 通过 ParamHandler 完成SQL语句的实参绑定,然后通过 java.sql.Statement 对象执行SQL语句并得到结果集,
最后通过 ResultSetHandler 完成结果集的映射,得到结果对象并返回。
自定义插件也可以改变 Mybatis 的默认行为,如:可拦截 SQL 语句并对其重写。
1.SqlSession:
和数据库交互的会话,该对象中包含了执行SQL语句的所有方法。类似JDBC里的Connection。
2.Executor:
MyBatis执行器,将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。
3.MappedStatement:
该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信息。
4.ResultSetHandler:
用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型。
5.StatementHandler:
封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
6.ParameterHandler:
对用户传递的参数转换成JDBC Statement 所需要的参数。资源加载模块
7.TypeHandler:
java数据类型和jdbc数据类型之间的映射和转换。
8.SqlSource:
根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回。
9.BoundSql:
动态生成的SQL语句及相应的参数信息。
10.Configuration:
MyBatis 所有的配置信息都在 Configuration 对象之中。
操作数据库时,需要创建 SqlSession 对象,对象中用 HashMap 存储缓存数据。
一级缓存的作用域是 SqlSession 范围的,当在同一个 SqlSession 中执行两次相同的SQL语句时,第一次执行完毕会将结果保存到缓存中,第二次查询时直接从缓存中获取。
如果SqlSession执行了DML操作(insert,update,delete),Mybatis必须将缓存清空以保证数据的准确性。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
多个SqlSession使用同一个Mapper的sql语句操作数据库,数据会存在二级缓存区,用HashMap进行数据存储,多个SqlSession可以共用二级缓存。
在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
1、定义一个Configuration对象,包含数据源、事务、mapper文件资源及影响数据库行为属性设置 settings。
2、通过配置对象,则可以创建一个SqlSessionFactoryBuilder对象。
3、通过 SqlSessionFactoryBuilder 获得SqlSessionFactory 的实例。
4、SqlSessionFactory 的实例可以获得操作数据的SqlSession实例,通过这个实例对数据库进行操作。
5、用户程序调用mybatis接口层 api(即Mapper接口中的方法)。
6、SqlSession通过调用api的Statement ID找到对应的MappedStatement对象。
7、通过Executor(负责动态SQL的生成和查询缓存的维护)将MappedStatement对象进行解析,sql参数转化、动态sql拼接,生成jdbc Statement对象。
8、JDBC执行sql。
9、借助MappedStatement中的结果映射关系,将返回结果转化成HashMap、JavaBean等存储结构并返回。
执行SQL操作,底层封装了JDBC连接,可用SqlSession实例直接执行被映射的SQL语句。每个线程都有自己的SqlSession实例,不能共享,线程不安全,用完后应关闭。
Executor(执行器): 用来调度 StatementHandler、ParameterHandler、ResultHandler等来执行对应的SQL。
StatementHandler(数据库会话处理器): 使用数据库的Statement执行操作。
ParameterHandler: 对Sql参数的处理。
ResultSetHandler: 对ResultSet结果集封装返回处理的。
生成SqlSession时,DefaultSqlSessionFactory中调用openSession,又调openSessionFromDataSource方法。
生成SqlSession时,DefaultSqlSessionFactory中调用openSession,又调openSessionFromDataSource方法。
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();
}
}
Configration的newExecutor方法中根据类型判断创建那种执行器,默认使用的是SimpleExecutor:
public class Configuration {
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
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;
}
}
public interface SqlSession extends Closeable {
T selectOne(String statement);
T selectOne(String statement, Object parameter);
List selectList(String statement);
List selectList(String statement, Object parameter);
List selectList(String statement, Object parameter, RowBounds rowBounds);
Map selectMap(String statement, String mapKey);
Map selectMap(String statement, Object parameter, String mapKey);
Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
T getMapper(Class type);
Connection getConnection();
}
SELECT * FROM User WHERE ID = #{id}
insert into User (id,name) values (#{id},#{name})
update User set
username = #{username},
password = #{password}
where id = #{id}
delete from User where id = #{id}
select seq_users.nextval from dual
select(select max(u.id)+1 from t_user u)
insert into t_user values (#{id}, #{name})
id 必选属性,绑定Sql语句与Mapper接口中的方法,。
parameterType 传入参数的类全限定名或别名。MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 返回结果的类全限定名或别名。返回的是集合,则设置为集合包含的类型。
resultMap 对外部 resultMap 的命名引用。resultType 和 resultMap 之间只能同时使用一个。
flushCache 设为 true 后,只要语句被调用,会导致本地缓存和二级缓存被清空,默认值(对select):false。
useCache 设为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize 给驱动的建议值,让驱动程序每次批量返回的结果行数等于这个设置值。默认不设置(unset)(依赖驱动)。
statementType 可选 STATEMENT、PREPARED、CALLABLE。让 MyBatis 分别使用 Statement、PreparedStatement、CallableStatement,默认:PREPARED。
resultSetType 可选 FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset),默认值为 unset (依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句;如果带和不带的语句都有,不带的会被忽略。
resultOrdered 仅针对嵌套结果 select 语句: 若为 true,将会假设包含了嵌套结果集或分组,返回一个主结果行时,不会产生对前面结果集的引用。获取嵌套结果集的时不至于内存不够,默认:false。
resultSets 仅适用于多结果集的情况。返回多个结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
id 必选属性,绑定Sql语句与Mapper接口中的方法,。
parameterType 传入参数的类全限定名或别名。MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
flushCache 设为 true 后,只要语句被调用,会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout 等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType 可选 STATEMENT、PREPARED、CALLABLE。让 MyBatis 分别使用 Statement、PreparedStatement、CallableStatement,默认:PREPARED。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句;如果带和不带的语句都有,不带的会被忽略。
useGeneratedKeys (仅insert、update)会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键,默认值:false。
keyProperty (仅insert、update)指定能够唯一识别对象的属性,用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认:未设置(unset)。可用逗号分隔多个属性名。
keyColumn (仅insert、update)设置生成键值在表中的列名,当主键列不是表中的第一列的时候,必须设置。如果生成列不止一个,可用逗号分隔多个属性名。
prefix 拼接前缀。
suffix 拼接后缀。
prefixOverrides 去除sql语句前面的关键字或者字符。
suffixOverrides 去除sql语句后面的关键字或者字符。
collection 表示迭代集合的名称,可以使用@Param注解指定。
item 必选参数,集合中元素迭代时的别名。
index 在list和数组中,index是元素的序号,在map中,index是元素的key。
open foreach代码的开始符号,一般是(和close=")"合用。
close foreach代码的关闭符号,一般是)和open="("合用。
separator 元素之间的分隔符,separator=","会自动在元素中间用“,“。
keyProperty 返回目标结果的key。
resultType 结果的类型。
order 设置为BEFORE或AFTER,设置为BEFORE,会先选择主键设置keyProperty,然后执行插入语句;设为AFTER,先执行插入语句,然后设置selectKey元素。
statementType 和前面的相同,MyBatis 支持 STATEMENT、PREPARED、CALLABLE语句的映射类型,分别代表PreparedStatement、CallableStatement 类型。
#{}是占位符,有预编译处理,可防止SQL注入。
${}是拼接符,字符串替换,没有预编译处理,${} 不能防止SQL 注入。
Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号。
分页方式:逻辑分页和物理分页。
逻辑分页:使用 MyBatis 自带的 RowBounds 进行分页,它是一次性查询很多数据,然后在数据中再进行检索。
物理分页:自己手写 SQL 分页或使用分页插件 PageHelper,去数据库查询指定条数的分页数据的形式。
使用