demo案例
public class MyBatisTest {
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* 1、获取sqlSessionFactory对象:
* 解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
* 注意:【MappedStatement】:代表一个增删改查的详细信息
*
* 2、获取sqlSession对象
* 返回一个DefaultSQlSession对象,包含Executor和Configuration;
* 这一步会创建Executor对象;
*
* 3、获取接口的代理对象(MapperProxy)
* getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
* 代理对象里面包含了,DefaultSqlSession(Executor)
* 4、执行增删改查方法
*
* 总结:
* 1、根据配置文件(全局,sql映射)初始化出Configuration对象
* 2、创建一个DefaultSqlSession对象,
* 他里面包含Configuration以及
* Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
* 3、DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
* 4、MapperProxy里面有(DefaultSqlSession);
* 5、执行增删改查方法:
* 1)、调用DefaultSqlSession的增删改查(Executor);
* 2)、会创建一个StatementHandler对象。
* (同时也会创建出ParameterHandler和ResultSetHandler)
* 3)、调用StatementHandler预编译参数以及设置参数值;
* 使用ParameterHandler来给sql设置参数
* 4)、调用StatementHandler的增删改查方法;
* 5)、ResultSetHandler封装结果
* 注意:
* 四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);
*
* @throws IOException
*/
@Test
public void test01() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);
System.out.println(mapper);
System.out.println(employee);
} finally {
openSession.close();
}
}
}
源码分析
一.
1.根据全局配置文件创建sqlsessionfactory (Configuration(数据源,全局配置信息),SqlSessio对象)
2.创建sqlsession对象
3.获取接口的实现类对象//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
二.mybatis属性<configuration>
<!--1、mybatis可以使用properties来引入外部properties配置文件的内容;resource:引入类路径下的资源 url:引入网络路径或者磁盘路径下的资源-->
<properties resource="dbconfig.properties"></properties>
<settings>
<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> -->
<setting name="jdbcTypeForNull" value="NULL"/>
<!--显示的指定每个我们需要更改的配置的值,即使他是默认的。防止版本更新带来的问题 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 3、typeAliases:别名处理器:可以为我们的java类型起别名 别名不区分大小写-->
<typeAliases>
<!-- 1、typeAlias:为某个java类型起别名 type:指定要起别名的类型全类名;默认别名就是类名小写;employee alias:指定新的别名-->
<!-- <typeAlias type="com.atguigu.mybatis.bean.Employee" alias="emp"/> -->
<!-- 2、package:为某个包下的所有类批量起别名
name:指定包名(为当前包以及下面所有的后代包的每一个类都起一个默认别名(类名小写),)
-->
<package name="com.atguigu.mybatis.bean"/>
<!-- 3、批量起别名的情况下,使用@Alias注解为某个类型指定新的别名 -->
</typeAliases>
<environments default="dev_mysql">
<environment id="dev_mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
<environment id="dev_oracle">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${orcl.driver}" />
<property name="url" value="${orcl.url}" />
<property name="username" value="${orcl.username}" />
<property name="password" value="${orcl.password}" />
</dataSource>
</environment>
</environments>
<!-- 5、databaseIdProvider:支持多数据库厂商的;
type="DB_VENDOR":VendorDatabaseIdProvider
作用就是得到数据库厂商的标识(驱动getDatabaseProductName()),mybatis就能根据数据库厂商标识来执行不同的sql;
MySQL,Oracle,SQL Server,xxxx
-->
<databaseIdProvider type="DB_VENDOR">
<!-- 为不同的数据库厂商起别名 -->
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
<!-- 6、mappers:将sql映射注册到全局配置中 -->
<mappers>
<!--
mapper:注册一个sql映射
注册配置文件
resource:引用类路径下的sql映射文件
mybatis/mapper/EmployeeMapper.xml
url:引用网路路径或者磁盘路径下的sql映射文件
file:///var/mappers/AuthorMapper.xml
注册接口
class:引用(注册)接口,
1、有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
2、没有sql映射文件,所有的sql都是利用注解写在接口上;
推荐:
比较重要的,复杂的Dao接口我们来写sql映射文件
不重要,简单的Dao接口为了开发快速可以使用注解;
-->
<!-- <mapper resource="mybatis/mapper/EmployeeMapper.xml"/> -->
<!-- <mapper class="com.atguigu.mybatis.dao.EmployeeMapperAnnotation"/> -->
<!-- 批量注册: -->
<package name="com.atguigu.mybatis.dao"/>
</mappers>
</configuration>
三.mapper文件常用标签
Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签?
答:还有很多其他的标签,<resultMap>、<parameterMap>、<sql>、<include>、 <selectKey>,<collection> <association> <resulttype>
加上动态 sql 的 9 个标签, trim|where|set|foreach|if|choose|when|otherwise|bind 等,其中<sql>为 sql 片段标签,
通 过<include>标签引入 sql 片段,
<selectKey>为不支持自增的主键生成策略标签,select 字标签。
resultType resultMap区别
resultType 可以接收基本类型,包装类型的结果集映射。但是当是包装类型的时候就有要求了,必须包装类型中的属性值跟查询结果的字段对应的上,否则的话对应不上的属性是接收不到查询结果的
resultMap 需要首先在xml中定义一个 <resultMap></resultMap>节点
association 和collection 区别
association是用于一对一和多对一,而collection是用于一对多的关系
<mapper namespace="com.peny.mapper.ActivityMapper">
<resultMap id="BaseResultMap" type="com.peny.po.Activity">
<!--
WARNING - @mbggenerated
This element is automatically generated by MyBatis Generator, do not modify.
-->
<result column="activityId" jdbcType="INTEGER" property="activityid" />
<result column="activityName" jdbcType="VARCHAR" property="activityname" />
<result column="activityDes" jdbcType="VARCHAR" property="activitydes" />
<result column="discount" jdbcType="REAL" property="discount" />
<result column="fullPrice" jdbcType="INTEGER" property="fullprice" />
<result column="reducePrice" jdbcType="INTEGER" property="reduceprice" />
<result column="fullNum" jdbcType="INTEGER" property="fullnum" />
<result column="reduceNum" jdbcType="INTEGER" property="reducenum" />
</resultMap>
<select id="countByExample" parameterType="com.peny.po.ActivityExample" resultType="java.lang.Integer">
<!--
WARNING - @mbggenerated
This element is automatically generated by MyBatis Generator, do not modify.
-->
select count(*) from activity
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
</mapper>
四。插件类型
ParameterHandler、参数映射,参数类型解析
ResultSetHandler、结果封装
StatementHandler、 sql预编译
Executor :SimpleExecutor、ReuseExecutor、 BatchExecutor 执行器
这 4 种接口的插
五.
namespace+id 是作为 Map<String, MappedStatement>的 key 使用的
六.参数传递
七.
一级缓存演示&失效情况
同一次会话期间只要查询过的数据都会保存在当 前SqlSession的一个Map中 ? key:hashCode+查询的SqlId+编写的sql查询语句+参数
? 一级缓存失效的四种情况
– 1、不同的SqlSession对应不同的一级缓存
– 2、同一个SqlSession但是查询条件不同
– 3、同一个SqlSession两次查询期间执行了任何一次增 删改操作
– 4、同一个SqlSession两次查询期间手动清空了缓
二级缓存
? 二级缓存(second level cache),全局作用域缓存
? 二级缓存默认不开启,需要手动配置
? MyBatis提供二级缓存的接口以及实现,缓存实现要求 POJO实现Serializable接口
? 二级缓存在 SqlSession 关闭或提交之后才会生效
? 使用步骤
– 1、全局配置文件中开启二级缓存 ? <setting name= "cacheEnabled" value="true"/>
– 2、需要使用二级缓存的映射文件处使用cache配置缓存 ? <cache /> <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
– 3、注意:POJO需要实现Serializable接口
缓存有关设置
? 1、全局setting的cacheEnable: – 配置二级缓存的开关。一级缓存一直是打开的。
? 2、select标签的useCache属性: – 配置这个select是否使用二级缓存。一级缓存一直是使用的
? 3、sql标签的flushCache属性: – 增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。 查询默认flushCache=false。
? 4、sqlSession.clearCache(): – 只是用来清除一级缓存。
? 5、当在某一个作用域 (一级缓存Session/二级缓存 Namespaces) 进行了 C/U/D 操作后,默认该作用域下所 有 select 中的缓存将被clear。
当执行一条查询SQL时,流程为 从二级缓存中进行查询 进入一级缓存中查询 执行 JDBC 查询
八.mybatis执行过程
1.根据全局配置文件创建sqlsessionfactory
1)XMLConfigBuilder(SAX解析)解析xml,设置Configuration 属性(mybatis的全局变量),每个mapper.xml将每一个标签封装成一个MappedStatment
每一个MappedStatmen就代表一个增删改查,全局configuration设置了一个mapperRegistry ,里面有个hashmap类型的knownmapper,kownmaper(key=接口名,value=MapperProxyFactory)
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
2)最后返回DefaultSqlSessionfactory
2.创建sqlSession
1)根据configuration的enviroment信息创建事务
2)final Executor executor = configuration.newExecutor(tx, execType); 创建执行器,默认是simpleExecutor
3)开启二级缓存CachingExecutor,委派模式(Delegate) ,可以先去查缓存
4)executor = (Executor) interceptorChain.pluginAll(executor);
5)返回DefaultSqlSession(保含configuration和excutor)
3)创建mapperProxy(jdk动态代理)里面有sqlsession
1)MapperProxyFactory 之前设置的,通过他创建MapperProxy对象(InvocationHandler)
2)public class MapperProxy<T> implements InvocationHandler, Serializable
3)(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
4)查询实现
1)调用方法,执行的是这个mapperproxy的invoke方法
2)method包装成mapperMethod,执行execute, 会根据SqlCommand类型(增删改查)进行相应处理
3)查询条件参数有多个会封装成一个mappeer,一个参数直接返回
4)调用defaultsqlsession result = sqlSession.selectOne(command.getName(), param);
5)List<T> list = this.<T>selectList(statement, parameter);
6)从全局变量configuraton获取mapperstatment,执行execute方法
7)从mapperstatment获取BoundSql(有sql语句,参数,参数映射关系)
8)创建CacheKey,executor开始执行查询
9)从localCache查询 查不到到数据库查
10)list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
localCache.putObject(key, list);
11) StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
创建statementHandler(preparedstatementHandler)
12)statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
13)预编译sql ,
创建parameterHandler 设置参数(通过typehandler来设置)
同时创建preparedstatementHandler sql预处理
同时会创建resultSetHandler和,创建parameterHandler
14) resultSetHandler.<E> handleResultSets(ps);
九。插件自定义 拦截器
具体要拦截哪个对象
public @interface Signature {
Class<?> type(); //拦截对象
String method(); //拦截对象的方法
Class<?>[] args();//方法的参数
}
自定义枚举类型
1、实现TypeHandler接口。或者继承BaseTypeHandler ,实现
com.atguigu.mybatis.typehandler.MyEnumEmpStatusTypeHandler.setParameter(PreparedStatement, int, EmpStatus, JdbcType)
com.atguigu.mybatis.typehandler.MyEnumEmpStatusTypeHandler.getResult(ResultSet, String)
2.自定义statementHandler
/**
* 完成插件签名:
* 告诉MyBatis当前插件用来拦截哪个对象的哪个方法
*/
@Intercepts(
{
@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
插件原理
* 在四大对象创建的时候
* 1、每个创建出来的对象不是直接返回的,而是
* interceptorChain.pluginAll(parameterHandler);
* 2、获取到所有的Interceptor(拦截器)(插件需要实现的接口);
* 调用interceptor.plugin(target);返回target包装后的对象
* 3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)
* 我们的插件可以为四大对象创建出代理对象;
* 代理对象就可以拦截到四大对象的每一个执行;
*