原理性问题

MyBatis 自动生成Dao层的原理

  • Mybatis 使用动态代理技术,读取配置文件反射生成包含sql的实例对象。
  • Dao 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行MappedStatement所代表的 sql,然后将 sql 执行结果返回。

MyBatis - 图1

  • 需要注意Dao接口与mapper文件的映射关系

1、Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径。
2、Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同;
3、Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同;
4、Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同;

  • Dao接口与mapper文件中的方法不存在重载,因为使用全限名+方法名的保存和寻找策略。

MyBatis 优缺点

Mybaits 的优点
1、基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML标签,支持编写动态 SQL 语句,并可重用。
2、与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接;
3、很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要JDBC 支持的数据库 MyBatis 都支持)。
4、能够与 Spring 很好的集成;
5、提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

MyBatis 框架的缺点
1、SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL 语句的功底有一定要求。
2、SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

MyBatis 框架适用场合
1、MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案。
2、对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis 将是不错的选择。

MyBatis 的简单运行流程

  • SqlSessionFactoryBuilder

读取配置文件,使用 Builder 模式去生成 SqlSessionFactory
可以接受 XML 配置文件的 Reader 或 InputStream 输入流,也可以传入 environment 指定环境或传入 Properties 作为属性

  • SqlSessionFactory

支持从 DataSource 数据源和一个给定的连接 Connection 中创建 SqlSession,底层实现大致分为5步:
1、从 Configuration 对象中获取环境配置 Environment;
2、根据环境配置得到事务工厂 TransactionFactory;
3、从事务工厂得到事务 Transaction,Transaction 包装了数据库连接,处理数据库连接的创建、准备、提交、回滚和关闭;
4、创建执行器 Executor;
5、创建 SqlSession,返回 DefaultSqlSession 的实例。

数据库的增删改查和事务的提交回滚都是通过 Executor 执行的。Executor 有 3 种类型 SIMPLE、REUSE、BATCH

  • SIMPLE 默认使用简易执行器:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
  • REUSE 类型执行器重用预处理语句:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map内,供下一次使用。简言之,就是重复使用 Statement 对象。
  • BATCH 类型执行器重用预处理语句和批量更新:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。
  • SqlSession

Executor 在执行过程中,会用到 StatementHandler、ParameterHandler 和 ResultHandler,
StatementHandler 封装了 java.sql.Statement 的相关操作(也实现了一级缓存);
ParameterHandler 封装了 SQL 对参数的处理;
ResultHandler 封装了对返回数据集的处理。

  • Mapper

Mapper 是通过 JDK 动态代理实现的,在 MapperProxyFactory 中创建 MapperProxy 并进行接口代理封装。对 Mapper接口的调用实际上是由 MapperProxy 实现的。

插件运行原理

Mybatis插件又称拦截器,Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为(诸如SQL重写之类的),MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  1. // 拦截执行器
  2. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  3. // 拦截参数处理
  4. ParameterHandler (getParameterObject, setParameters)
  5. // 拦截sql语句构建
  6. StatementHandler (prepare, parameterize, batch, update, query)
  7. // 拦截结果集处理
  8. ResultSetHandler (handleResultSets, handleOutputParameters)

Mybatis的插件实现要实现Interceptor接口。Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法。

  • 分页插件
    1. PageHelper.startPage(0,10);
    2. UserMapper userMapper = session.getMapper(UserMapper.class);
    1、创建了Page分页对象,并存储在 ThreadLocal 中
    2、PageHelper拦截了 Executor 接口的query()方法,从 ThreadLocal 中取出Page对象,对sql语句进行改写
    3、从 ThreadLocal 中删除 Page对象

延迟加载原理

Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟 加载 lazyLoadingEnabled=true|false

MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力。MyBatis 的延迟加载只是对关联对象的查询有迟延设置,对于主加载对象都是直接执行查询语句的。

  • 直接加载:执行完对主加载对象的 select 语句,马上执行对关联对象的 select 查询。
  • 侵入式延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情属性时,就会马上执行关联对象的select查询。
  • 深度延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。

一、二级缓存

1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

具体使用

实体类和表中字段的对应

第一种是使用 标签,逐一定义列名和对象属性名之间的映射关系。
第二种是使用 sql 列的别名功能,自动将表中字段和实体类中的属性映射。但是列名不区分大小写,Mybatis 会忽略列名大小写

MyBatis 常用标签 / 动态参数拼接

  • 常用标签

9个动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind
- 动态拼接 xml <!--where if 动态拼接--> <select id="queryUserByCondition" resultType="user"> select * from easybuy_user <where> <if test="id != null"> and id = #{id} </if> <if test="userName != null and userName!=&quot;&quot;"> and userName like #{userName} </if> <if test="email != null and email!=&quot;&quot;"> and email = #{email} </if> </where> </select> - 关联查询 ```xml

  1. <a name="bYnLc"></a>
  2. ## 如何在 mapper 传递多个参数
  3. - 使用 `@param` 注解
  4. ```java
  5. public interface usermapper {
  6. User selectuser(
  7. @param(“username”) string username,
  8. @param(“hashedpassword”) string hashedpassword);
  9. }
<select id="selectuser" resultType="User">
    select * from t_user where username = #{username} and password = #{password}
</select>
  • 使用 Map 集合封装多个参数
    Map<String, Object> map = new HashMap();
    map.put("username", username);
    map.put("password", password);
    
    <select id="selectuser" parameterType="map" resultType="User">
      select * from t_user where username = #{username} and password = #{password}
    </select>
    

一对一/多对多的关联查询

关联对象查询,有两种实现方式:

  • 一种是单独发送一个 sql 去查询关联对象,赋给主对象,然后返回主对象(需要两条sql)。
  • 另一种是使用嵌套查询,嵌套查询的含义为使用 join 查询,一部分列是 A 对象的属性值, 另外一部分列是关联对象 B 的属性值,好处是只发一个 sql 查询,就可以把主对象和其关联对象查出来。

需要使用<association>标签进行关联查询,若出现一对多查询,则需要在<association>中嵌套<collection>标签