Mybatis入门
Mybatis构成
- SqlSessionFactoryBuilder(构造器):根据配置信息或者代码生成SqlSessionFactory
- SqlSessionFactory:依靠工厂来生成SqlSession会话
- SqlSession是一个可以发送sql去执行并返回结果,也可以获取Mapper的接口
- SqlMapper:它是一个由Java接口或者XML文件构成的,给出对应的SQL和映射规则,它复杂发送SQL去执行,返回结果,
SqlSession的作用
- 获取映射器,让映射器通过命名空间和方法查找对应的sql,发送给数据库并执行返回结果
- 直接通过命名信息去执行SQL返回结果,SqlSession通过支持事务,通过commit,rollback方法提交或者回滚事务。
映射器
- 应设计是由Java接口和XML共同组成,作用
- 定义参数类型
- 描述缓存
- 描述SQL语句
- 定义POJO和查询结果的关系
Mybatis映射原理,使用Java语言的动态代理java.lang.reflect实现。Mybtais上下文中描述接口,Mybatis为这个接口生成代理对象,代理对象根据命名空间+方法名匹配找到对应的XML文完成所需要的任务,返回需要的结果。
生命周期
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder是利用XML或者Java编码获得资源来构建SqlSessionFactory的,通过它可以构建多个SessionFactory。它的作用就是一个构造器,一旦构建了SqlSessionFactory。它的作用也就结束。所以它的作用限于方法的局部,它的作用就是生成SqlSessionFactory对象。
SqlSessionFactory
SqlSessionFactory的作用是创建SqlSession,而SqlSession是一个会话,相当于JDBC的Connection对象。每次程序访问数据库都需要通过SqlSessionFactory创建Sqlsession。所以SqlSessionFactory应该在整个Mybtais的生命周期中。
SqlSession
SqlSession是一个会话,不是一个线程安全的对象,它的生命周期在于请求数据库处理事务的过程中。创建的SqlSession必须及时关闭,长时间存活会使数据库的连接池的活动资源减少。
Mapper
Mapper是一个接口,而没有任何实现类,它的作用是发送SQL,然后返回我们需要的结果。因此它应该在一个SqlSession事务方法之类,是一个方法级别的东西
配置
properties元素
properties是一个配置属性的元素,配置文见当中使用该元素。mybatis提供三种配置方式
- property子元素
- properties配置文件
- 程序参数传递
Mybatis支持的配置方式可能同时出现,所以MyBatis按照顺序去加载
- 在properties元素体内指定的属性首先被读取
- 首选的方式是使用properties文件
- 如果需要对其进行加密或其它的加工满足特殊需求,可以使用配置方式和编码方式结合。
别名
别名(typeAliases)是一个指代的名称。因为我们的类全路径太长,所以采用一个简短的名称去指定。Mybatis中别名是不分大小写。一个typeAliases的实例是在解析配置文件时生成的,然后长期保存在Configuration对象当中。
系统定义别名
Mybatis 当中已经注册了一些自定义的系统别名 如Integer=int,arraylist=ArrayList等一系列自定义注册信息.位于org.apache.ibatis.type.TypeAliasRegistry包当中
自定义别名
系统自定义的别名是肯定不够用的,因为不同的应用有着不同的需要,不同的实体等。所以Mybatis允许自定义别名。
<!-- 定义别名 --> <typeAliases> <typeAlias alias="user" type="com.zyzpp.model.User"/> </typeAliases>
<!-- 扫描形式 --> <typeAliases> <package name="com.zyzpp.model" /> </typeAliases>
//Java注解心事 @Alias("user") public class User{ //... }
typeHandler类型处理器
MyBatis在预处理语句当中设置一个参数,或者从结果集当中取出一个值,都会使用到typeHandler的处理。用于数据库的字段映射到PoJo当中一个类型转换。
自定义typeHandler
- 配置XML文件当中需要处理什么类型参数
<typeHandlers> //处理JDBC 为varchar类型的数据 javaType=String类型的参数 用该Handler执行 <typeHandler handler="com.example.demo.util.StringHandler" jdbcType="VARCHAR" javaType="String"/> </typeHandlers>
- 自定义配置项去完成转换
//映射JDBC类型为varchar @MappedJdbcTypes({JdbcType.VARCHAR}) //映射类型为String @MappedTypes({String.class}) public class StringHandler extends BaseTypeHandler<String> { //处理该参数 塞到数据库当中运用到 @Override public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException { System.out.println("setNonNullParameter"); preparedStatement.setString(i,s); } //根据roleName获取值 @Override public String getNullableResult(ResultSet resultSet, String s) throws SQLException { System.out.println("getNullableResult roleName"); return resultSet.getString(s); } //下标获取值 @Override public String getNullableResult(ResultSet resultSet, int i) throws SQLException { System.out.println("getNullableResult index"); return resultSet.getString(i); } //存储过程当中获取值 @Override public String getNullableResult(CallableStatement callableStatement, int i) throws SQLException { System.out.println("getNullableResult callableStatement"); return callableStatement.getString(i); } }
//自定义typeHandler里用注解配置JdbcType和JavaType。JdbcType需要满足ibatis.JdbcType 的枚举类型。
标识哪些参数或者结果类型去用typehandler进行转换,在没用任何标识情况下,Mybatis是不会启用你定义的typeHandler进行转换结果的。有以下三种方法
- 在结果集里面直接定义了jdbcType和 javaType和 handler一致
<resultMap id="sysDict" type="com.example.demo.entity.SysDictType"> <result column="dict_name" jdbcType="VARCHAR" javaType="String" typeHandler="com.example.demo.util.StringHandler"/> </resultMap> <select id="findAll" resultMap="sysDict"> select * from sys_dict_type </select>
在mybatis-config配置 扫描 handler,设置handler别名
<typeHandlers> <package name="com.example.demo.util"/> </typeHandlers>
在springboot的yml文件当中配置扫描设置别名
mybatis: type-handlers-package: com.example.demo
枚举类型typeHandler
MyBatis内部提供了两个转化枚举类型的typeHandler给我们使用。
- EnumTypeHandler
- EnumOrdinalTypeHandler
其中EnumTypeHandler是使用枚举字符串名称作为参数传递的,EnumOrdinalTypeHandler是使用整数下标作为参数传递的。
objectFactory
当Mybatis再构建一个结果返回的时候,都会使用ObjectFactory去构建POJO,在Mybatis中可以定制自己的对象工厂。在大部分的场景夏我们都不用修改,如果要特定的工厂则需要配置。
自定义实现 ObjectFactory的要求是实现ObjectFactory,但是DefaultObject已经实现了ObjectFactory,所以我们可以采用继承DefaultObjectFactory的方式去实现自定义的。
缓存cache
- 一级缓存
Mybatis默认情况下,只开启一级缓存,一级缓存只是相对于同一个SqlSession而言
一级缓存
- 二级缓存
二级缓存开启需要配置,实现二级缓存,POJO必须是可序列化的,也就是实现Serializable 接口。然后再映射xml文件配置开启。
二级缓存默认是不开启的在config配置文件当中开启 <settings> <setting name = "cacheEnabled" value = "true" /> </settings>
还需要在Mapper文件当中使用<cache/>来标识对应的mapper文件开启二级缓存。
<cache eviction="LRU" flushInterval="60000" readOnly="true" size="1024" />
eviction 表示回收策略:
- LRU:最近最少使用的:移除最长时间不被使用的对象
- FIFO:先进先出:按对象进入缓存的顺序来移除它们
- SOFT:软引用:移除基于垃圾回收器状态和软引用规则的对象
- WEAK:弱引用 更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval:刷新间隔,单位为毫秒。
size:引用数目。可以被设置为任意正整数,设置过大容易导致内存移除默认1024.
readonly:true 只读得缓存会给所有调用者返回缓存对象得相同示例。因此这些数据不能被修改。可读写得缓存会返回缓存对象得拷贝。这会慢,但是安全。
二级缓存执行流程
开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,会先CachingExecutor进行二级缓存的查询。二级缓存开启后,同一个namespace下面的所有操作预计,都影响着同一个Cache。即二级缓存被多个sqlsession共享,是一个全局变量
- 自定义缓存
使用第三方缓存(redis),需要实现Mybatis为我们提供的Cache.代码如下:
public interface Cache { //获取缓存编号 String getId(); //获取缓存对象大小 int getSize(); //保存key值缓存对象 void putObject(Object key, Object value); //通过key获取缓存对象 Object getObject(Object key); //通过key删除缓存对象 Object removeObject(Object key); //清空缓存 void clear(); }
MyBatis的解析和运行原理
Mapper的接口实现方式?
采用动态代理方式实现。jdk的proxy.
构建SqlSessionFactory过程
- 通过XMLConfigBuilder解析配置的XML文件,读出配置参数,并将读取的数据存入这个Configuration累中。
- 使用Configuration对象去创建SqlSessionFactory。
构建Configuration
- 读入配置文件,包括基础配置的XML文件和映射器的XML文件
- 初始化配置基础,比如MyBatis的别名等,一些重要的类对象。
- 提工单例,为后续创建SessionFactory服务并提供配置的参数
- 执行一些重要的对象方法,初始化配置信息
映射器的内部组成
一般情况下,映射器是由三个部分组成:
- MappedStatemnt,保存映射器的一个节点.SQL\SQLID\等配置内容
- SqlSource 提供BoundSql对象的地方。它是MappedStatement的一个属性
- BoundSql,它是建立SQL和参数的地方。有三个对应的属性:SQL、parameterObject、parameterMappings。(parameterObject为参数本身,如果使用@Param注解,那么Mybatis会把parameterObject也会变成一个Map<String,Object>对象)
SqlSession运行过程
映射器的动态代理
Mybatis通过动态代理技术+命令模式+SqlSession接口的方法使它执行查询。
SqlSession下的四大对象
- Executor代表执行器,用于调度StatementHandler,ParameterHandler,ResultHandler来执行SQL
- StatementHandler的作用是使用数据库的Statement执行操作。它是四大对象核心,起到呈上启下的作用。
- ParameterHandler 用于SQL对参数的处理
ResultHandler是进行最后数据集的封装返回处理的。
SqlSession执行流程
- 执行器
真正执行Java和数据库交互的东西。在Mybatis中存在三种执行器- Simple就是普通的执行器。
- REUSE执行器会重用预处理语句
- BATCH执行器将重用语句并执行批量更新。
- 数据库会话器
- 数据库会话器就是专门处理数据库会话的。
- Executor会先调用StatementHandler的prepare()方法预编译sql语句,同事设置一些基本运行参数。然后用parameterize()方法启用parameterHandler设置参数,完成预编译,而update()也是这样的,最后如果需要查询,就采用ResultSetHandler封装结果返回给调用者
- 参数处理器
- Mybatis是通过ParameterHandler对预编译语句进行参数设置。它的作用是完成对预编译参数的设置
- 结果处理器
ResultSetHandler是结果处理器的接口定义。
image.png
Sqlsession是通过Executor创建StatementHandler来运行的,而StatementHandler要经过下面三步。
- prepared:预编译SQL
- parameterize: 设置参数
- query/update:执行sql
其中parameterize 是调用parameterHandler的方法去设置,而参数是根据类型处理器typeHandler去处理的。query/update方法是通过resultHandler进行处理结果的封装,如果是update语句,它就返回证书,否则它就通过typeHandler处理结果类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。
插件
插件接口
在Mybatis中使用插件,我们就必须实现inteceptor,让我们先看看它的定义和各个方法的含义。
- intecept方法:它将直接覆盖你所拦截对象原有的方法,因此它是插件的和新方法。intecept里面的Invocation对象,通过它可以反射调度原来对象的方法。
- plugin方法:target是被拦截的对象,它的作用是返回一个代理对象。
- setProperties:允许plugin元素中配置所需参数,方法在插件初始化的时候调用一次,然后把对象插入到配置中,再取出。template模式
插件的初始化
插件的初始化是在Mybatis初始化的时候完成的。Mybatis上下文初始化过程中,就可以读入插件节点和我们配置的参数,同时使用反射技术生成对应的插件实例,。所有的插件都是在实例对象的时候就被初始化的。
插件的代理和反射设计
插件用的是责任链模式。责任链就是一个对象处于Mybatis四大对象当中的一个,在做个角色传递。处在传递链上的任何角色都有处理它的机会。
MyBatis的责任链是由inteceptorChain去定义的。
MyBatis中提供了一个常用的工具类,用来生成代理对象,它便是plugin类。Plugin实现了InvocationHandler接口,采用的是JDK动态代理。
MetaObject
MetaObject这个工具类可以用于有效读取或者修改一些重要对象的属性,在MyBatis中,四大对象给我们提供的public级别设置参数的方法很少。常用得三个方法
- MetaObject forObject方法用于包装对象。这个方法已经不再使用,Mybatis为我们提供得SystemMetaObject.for(Object)
- Object getValue(String name)用于获取对象属性值,支持OGNL
- void setValue(String name,Object value)用于修改对象得属性值,支持OGNL.
在Mybatis对象中大量使用了这个类进行包装,包括四大对象,使得我们可以通过它来给四大对象的某些属性赋值满足我们的需要。
插件开发
正如mybatis插件可以拦截四大对象任意一个一样。从Plugin源码中我们可以看到需要注册签名才可以运行。签名需要一些要素
- 确定需要拦截的对象
- Executor是执行sql的全过程,包括组装参数,组装结果返回和执行sql执行过程。
- StatementHandler是执行SQL过程,我们可以重写执行SQL过程。
- ParameterHandler 组要拦截执行SQL的参数组装。
- ResultHandler用于拦截执行结果的组装。
我们清楚需要拦截得是StatementHandler对象,应该在预编译SQL之前,修改SQL使得结果返回数量被限制。
@Intercepts( { @Signature(type = StatementHandler.class,//确定要拦截的对象 method = "prepare",//确定要拦截的方法 args = {Connection.class}//拦截方法的参数 ) } ) public class QueryLimitPlugin implements Interceptor{}
该插件拦截StatementHandler类当中的method方法,在mybatis-config,xml配置对应的插件地址
<plugins> <plugin interceptor="QueryLimitPlugin"> </plugin> </plugins>
总结:
- 能不用插件尽量不用插件,因为会涉及到底层修改
- 插件生成的是层层代理对象的责任链模式,通过反射方法运行,效率不搞。
- 编写插件需要拦截mybatis的运行原理,了解四大对象及其方法的作用,准确判断需要拦截什么对象
- 在插件中否需要读取和修改mybatis映射器的属性,需要熟练掌握mybatis映射器内部组成的知识
- 插件代码要考虑全面特别是多个插件层层代理的时候,要保证逻辑正确
- 尽量少改动底层,避免发生错误
网友评论