一、Mybatis 基本构成 

MyBatis的整体分为基础支持层、核心处理层、接口。

Mybatis 小结-LMLPHP

1.1、基础支持层 

1.1.1、数据源模块 

MyBatis自身提供了相应的数据源实现,也提供了与第三方接口数据源集成的接口,这些功能都位于数据源模块之中。

1.1.2、事务管理模块 

MyBatis对数据库中的事务进行了抽象,其自身提供了相应的事务接口和简单的实现,在很多场景中,MyBatis会与Spring框架集成,并由Spring框架管理事务相关配置。

1.1.3、缓存模块 

MyBatis中提供了一级缓存和二级缓存,而这两级缓存都依赖于基础支持层中的缓存模块实现。

1.1.4、Binding模块 

开发人员无须编写自定义 Mapper接口的实现,MyBatis会自动为其创建动态代理对象,
MyBatis 通过 Binding 模块将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作。

1.1.5、反射模块 

MyBatis中专门提供了反射模块,该模块对Java原生的反射进行了一系列优化,例如缓存了类的元数据,提高了反射的性能。

1.1.6、类型转换模块 

该功能在为 SQL 语句绑定实参以及映射查询结果集时都会涉及,将数据由 Java类型转换成 JDBC 类型。

1.1.7、日志模块 

MyBatis除了提供详细的日志输出信息,还能够集成第三方日志框架。
SLF4J 、LOG4J 、LOG4J2
JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING 
NO_LOGGING

1.1.8、资源加载模块 

对类加载器进行封装,确定类的使用顺序,提供加载类文件以及其他资源文件。

1.1.9、解析器模块 

主要提供两个功能:
1.对XPath进行封装,为MyBatis初始化时解析mybatis-config.xml配置文件以及映射配置文件提供支持;
2.为处理动态sql语句中的占位符提供支持。

1.2、核心处理层 

1.2.1、配置解析 

MyBatis 初始化时,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。

1.2.2、参数映射-SQL解析 

MyBatis 中的scripting模块会根据用户传入的实参,解析映射文件中定义的动态SQL节点,并形成数据库可执行的SQL 语句。
之后会处理 SQL 语句中的占位符,绑定用户传入的实参。

1.2.3、SQL执行 

SQL语句的执行中重要的组件:Executor、StatementHandler、ParameterHandler、ResultSetHandler。
Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,将数据库相关操作委托给 StatementHandler 完成。
StatementHandler 通过 ParamHandler 完成SQL语句的实参绑定,然后通过 java.sql.Statement 对象执行SQL语句并得到结果集,
最后通过 ResultSetHandler 完成结果集的映射,得到结果对象并返回。

Mybatis 小结-LMLPHP

1.2.4、插件

自定义插件也可以改变 Mybatis 的默认行为,如:可拦截 SQL 语句并对其重写。

1.3、MyBatis主要构件 

 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 对象之中。

1.4、MyBatis 的缓存 

1.4.1.一级缓存:SqlSession级别,默认开启,且不能关闭。

操作数据库时,需要创建 SqlSession 对象,对象中用 HashMap 存储缓存数据。
一级缓存的作用域是 SqlSession 范围的,当在同一个 SqlSession 中执行两次相同的SQL语句时,第一次执行完毕会将结果保存到缓存中,第二次查询时直接从缓存中获取。
如果SqlSession执行了DML操作(insert,update,delete),Mybatis必须将缓存清空以保证数据的准确性。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

1.4.2.二級緩存:Mapper级别,默认关闭,可以开启。

多个SqlSession使用同一个Mapper的sql语句操作数据库,数据会存在二级缓存区,用HashMap进行数据存储,多个SqlSession可以共用二级缓存。
在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。

二、原理 

2.1、Mybatis 的基本流程 

Mybatis 小结-LMLPHP

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等存储结构并返回。

2.2、SqlSession(会话)

执行SQL操作,底层封装了JDBC连接,可用SqlSession实例直接执行被映射的SQL语句。每个线程都有自己的SqlSession实例,不能共享,线程不安全,用完后应关闭。

2.2.1 ​​SqlSession四大对象

Executor(执行器):  用来调度 StatementHandler、ParameterHandler、ResultHandler等来执行对应的SQL。
StatementHandler(数据库会话处理器): 使用数据库的Statement执行操作。
ParameterHandler: 对Sql参数的处理。
ResultSetHandler: 对ResultSet结果集封装返回处理的。
生成SqlSession时,DefaultSqlSessionFactory中调用openSession,又调openSessionFromDataSource方法。

2.2.2 生成SqlSession

生成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;
	}
}

2.2.3 SqlSession接口 

public interface SqlSession extends Closeable {
	  <T> T selectOne(String statement);
	  <T> T selectOne(String statement, Object parameter);
	  <E> List<E> selectList(String statement);
	  <E> List<E> selectList(String statement, Object parameter);
	  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
	  <K, V> Map<K, V> selectMap(String statement, String mapKey);
	  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
	  <K, V> Map<K, V> 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<BatchResult> flushStatements();
	  void close();
	  void clearCache();
	  Configuration getConfiguration();
	  <T> T getMapper(Class<T> type);
	  Connection getConnection();
}

三、日常的使用 

3.1、常用标签 及 标签属性

3.1.1、常用标签

3.1.1.1、select标签
<select id="findUserById" parameterType="int" resultMap="userResultMap">
	  <include refid="sqlMsg"><property name="id" /></include>
</select>

<sql id="sqlMsg"> SELECT * FROM User WHERE ID = #{id}</sql>

<resultMap id="userResultMap" type="User">
	 <id property="id" column="user_id" />
	 <result property="username" column="user_name"/>
	 <result property="password" column="hashed_password"/>
</resultMap>
3.1.1.2、insert标签
<insert id="addUser">
  insert into User (id,name)  values (#{id},#{name})
</insert>
3.1.1.3、update标签
<update id="updateUser">
  update User set
	username = #{username},
	password = #{password}
  where id = #{id}
</update>
3.1.1.4、delete标签
<delete id="deleteUser">
  delete from User where id = #{id}
</delete>
3.1.1.5、if 标签
<select id="findUser" >
  SELECT * FROM User
  WHERE state = 0
  <if test="name != null and name!=''">
	AND name = #{name}
  </if>
</select>
3.1.1.6、choose、when、otherwise(类似于switch)标签:
<select id="findUser" >
  SELECT * FROM User WHERE state = 0
  <choose>
	<when test="name != null">
	   AND name like #{name}
	</when>
	<when test="age != null">
	   AND age = #{age}
	</when>
	<otherwise>
	   AND name like ''
	</otherwise>
  </choose>
</select>
3.1.1.7、trim、where标签 
<select id="getUser" resultType="user">
 select * from user
	 <trim prefix="where" prefixOverrides="and">
		  <if test="lastName != null">
		   name = #{name}
		  </if>
		  <if test="age != null">
		   and age = #{age}
		  </if>
		  <if test="phone != null">
		   and phone = #{phone}
		  </if>
	 </trim>
</select>
3.1.1.8、foreach标签
<select id="queryUsers" >
	  select * from user where id in
	 <foreach collection="array" item="item" open="(" separator="," close=")" index="index">
		  #{item}
	  </foreach>
</select>
 3.1.1.9、bind标签
<!--使用bind元素进行模糊查询-->
<select id="queryUserByName" resultType="com.MyUser" parameterType= "com.MyUser">
   select * from user
		   <trim prefix="where" prefixOverrides="and">
				<if test="name != null">]
					<bind name="bindName" value="'%' + name + '%'"/>
					 name like #{bindName}
				</if>
		   </trim>
</select>
 3.1.1.10、selectKey标签
<insert id="insert">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
		  <!-- Oracle中用Sequence获取主键 -->
		<if test="_databaseId == 'oracle'">
		  select seq_users.nextval from dual
		</if>
		   <!-- Mysql直接用sql查询最大主键 -->
		<if test="_databaseId == 'mysql'">
		  select(select max(u.id)+1 from t_user u)
		</if>
  </selectKey>
		insert into t_user values (#{id}, #{name})
</insert>
 3.1.1.11、typeAliases标签
<typeAliases>
	<typeAlias alias="myAuthor" type="com.bean.Author"/>
	<typeAlias alias="myUser" type="com.bean.User"/>
	<typeAlias alias="myCar" type="com.bean.Car"/>
	<typeAlias alias="myHouse" type="com.bean.House"/>
</typeAliases>
<select id="selectUser" lang="myUser">
  SELECT * FROM user
</select>
3.1.2、标签属性 
3.1.2.1、select标签属性 
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   仅适用于多结果集的情况。返回多个结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
3.1.2.2、insert、update、delete标签属性
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)设置生成键值在表中的列名,当主键列不是表中的第一列的时候,必须设置。如果生成列不止一个,可用逗号分隔多个属性名。
3.1.2.3、trim标签属性
prefix          拼接前缀。
suffix          拼接后缀。
prefixOverrides     去除sql语句前面的关键字或者字符。
suffixOverrides     去除sql语句后面的关键字或者字符。
3.1.2.4、foreach标签属性
collection  表示迭代集合的名称,可以使用@Param注解指定。
item        必选参数,集合中元素迭代时的别名。
index       在list和数组中,index是元素的序号,在map中,index是元素的key。
open        foreach代码的开始符号,一般是(和close=")"合用。
close       foreach代码的关闭符号,一般是)和open="("合用。
separator   元素之间的分隔符,separator=","会自动在元素中间用“,“。
3.1.2.5、selectKey标签属性
keyProperty    返回目标结果的key。
resultType     结果的类型。
order          设置为BEFORE或AFTER,设置为BEFORE,会先选择主键设置keyProperty,然后执行插入语句;设为AFTER,先执行插入语句,然后设置selectKey元素。
statementType  和前面的相同,MyBatis 支持 STATEMENT、PREPARED、CALLABLE语句的映射类型,分别代表PreparedStatement、CallableStatement 类型。

3.2、 Mybatis 常见问题

3.2.1、${} 与 #{} 的区别

#{}是占位符,有预编译处理,可防止SQL注入。
${}是拼接符,字符串替换,没有预编译处理,${} 不能防止SQL 注入。
Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号。

3.2.2、Mybatis的分页

分页方式:逻辑分页和物理分页。
逻辑分页:使用 MyBatis 自带的 RowBounds 进行分页,它是一次性查询很多数据,然后在数据中再进行检索。
物理分页:自己手写 SQL 分页或使用分页插件 PageHelper,去数据库查询指定条数的分页数据的形式。

3.2.3、转义符号

使用<![CDATA[内容]]>
<select id="selectById" resultMap="brandResultMap">
    select *
    from tb_brand where id <![CDATA[  >   ]]> #{id};
</select>
08-13 16:45