为什么要使用Mybatis
Mybatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(PlanOld Java Objects,普通的Java对象)映射成数据库中的记录。
Mybatis环境快速入门
Maven依赖信息
<dependencies>
<!-- mybatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
<!-- junit测试包 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
创建Mybatis配置文件configuration
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 数据库连接相关配置 ,这里动态获取config.properties文件中的内容-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- mapping文件路径配置 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 为这个mapper指定一个唯一的namespace,namespace的值习惯上设置成包名+sql映射文件名,这样就能够保证namespace的值是唯一的
例如namespace="com.mayikt.mapper.UserMapper"就是com.mayikt.mapper(包名)+userMapper(userMapper.xml文件去除后缀)
-->
<mapper namespace="com.mayikt.mapper.UserMapper">
<!-- 在select标签中编写查询的SQL语句, 设置select标签的id属性为getUser,id属性值必须是唯一的,不能够重复
使用parameterType属性指明查询时使用的参数类型,resultType属性指明查询返回的结果集类型
resultType="com.mayikt.entity.User"就表示将查询结果封装成一个User类的对象返回
User类就是users表所对应的实体类
-->
<!--
根据id查询得到一个user对象
-->
<select id="getUser" parameterType="int"
resultType="com.mayikt.entity.UserEntity">
select * from user where id=#{id}
</select>
</mapper>
运行Mybatis代码
try {
// 1.mybatis配置文件
String resources = "mybatis.xml";
// 2.获取Reader对象
Reader resourceAsReader = Resources.getResourceAsReader(resources);
// 3.获取SqlSessionFactoryBuilder
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsReader);
// 4.创建对应的session
SqlSession sqlSession = build.openSession();
// 5.获取对应的mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 6.执行方法
UserEntity user = userMapper.getUser(1);
System.out.println("name:" + user.getName());
} catch (Exception e) {
e.printStackTrace();
}
数据库表结构
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Mybatis核心配置文件
Properties(属性)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis
jdbc.username=root
jdbc.password=root
Mybatis大体架构流程分析
1. 读取resource获取对应的Reader对象
reader = Resources.getResourceAsReader(resources);
2. 使用SqlSessionFactoryBuilder获取SqlSessionFactory源码分析
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
源码分析:
1.进入到build传递reader有参构造函数
最终执行:
2.SqlSessionFactoryBuilder使用XMLConfigBuilder解析配置文件,封装成Configuration对象。
注意:XMLConfigBuilder运行之后,只能被解析一次 否则会抛出异常。
3.将配置文件中的mapper注册到configuration的mapperRegistry中
所以在configuration中的mapperRegistry注册mapper接口
4.使用configuration获取默认的DefaultSqlSessionFactory
Mybatis代理模式原理分析
MybatisMapper接口绑定原理
Mapper既然是接口,没有被初始化如何被调用的?
答案:使用动态代理技术
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
UserMapper userMapper = getMapper(UserMapper.class);
UserEntity user = userMapper.getUser(1);
System.out.println("user:" + user.toString());
}
//1.获取对应的Mapper接口
public static <T> T getMapper(Class<T> clas)
throws IllegalArgumentException, InstantiationException, IllegalAccessException {
return (T) Proxy.newProxyInstance(clas.getClassLoader(), new Class[]{clas},
new MyBatisJdkInvocationHandler(clas));
}
public class MyBatisJdkInvocationHandler implements InvocationHandler {
/**
* 目标对象
*/
private Object target;
public MyBatisJdkInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return new UserEntity(1000l, "蚂蚁课堂", 20);
}
/**
* 获取代理对象接口
*
* @param <T>
* @return
*/
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
MybatisMapper接口绑定原理分析
大致原理分析:
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。
射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询(也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。
MybatisMapper SQLSession源码分析
SQLSession的作用
-
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法
-
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的。
-
SqlSession 因为线程不安全 所以不会共享
Executor执行器原理分析
1. openSessionFromDataSource:首先是从Configuration中取出相关的配置,生成Transaction,接着又创建一个Executor,最后返回了DefaultSqlSession对象。
2. SimpleExecutor: 默认的 Executor,每个 SQL 执行时都会创建新的 Statement
3. ResuseExecutor: 相同的 SQL 会复用 Statement
4. BatchExecutor: 用于批处理的 Executor
5. CachingExecutor: 可缓存数据的 Executor,用代理模式包装了其它类型的 Executor
默认情况下使用缓存的CachingExecutor
SelectOne底层原理查询分析
1.当查询单条数据的时候,最终还是调用selectList查询多个结果集包装程单个对象。
2.从configuration中获取到MappedStatement(对应的sql语句配置),调用executor的query方法实现执行。
3.先查询二级缓存,是否有缓存,没有的话调用delegate. query
如果一级缓存中没有该结果,会调用queryFromDatabase查询数据库得到数据让后在缓存到一级缓存中,下次查询的时候相同的sql语句直接走一级缓存不会查询数据库。
Mybatis一级与二级缓存
一级缓存
mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。
具体流程:
- 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来
- 第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率
注意事项:
- 如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
- 当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
- mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数]作为key
注意:服务器集群的时候,每个sqlSession有自己独立的缓存相互之间不存在共享,所以在服务器集群的时候容易产生数据冲突问题.
配置以下配置可以实现开启日志打印
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="cacheEnabled" value="false"/>
</settings>
如何禁止一级缓存
方案1 在sql语句上 随机生成 不同的参数 存在缺点:map集合可能爆 内存溢出的问题
方案2 开启二级缓存
方案3 使用sqlSession强制清除缓存
方案4 创建新的sqlSession连接。
二级缓存SessionFactory
二级缓存是mapper级别的缓存,也就是同一个namespace的mappe.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域,二级缓存默认是没有开启的。
需要在setting全局参数中配置开启二级缓存
Config.配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在UserMapper配置
<!-- 以下两个<cache>标签二选一,第一个可以输出日志,第二个不输出日志 -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
<!-- <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> -->
二级缓存回收策略
LRU:最近最少使用的策略,移除最长时间不被使用的对象
FIFO:先进先出策略,按对象进入缓存的顺序来移除它们。
SOFT:软引用策略,移除基于垃圾回收器状态和软引用规则的对象。
WEAK:弱引用策略,更积极地移除基于垃圾收集器状态和弱引用规则的对象。
软引用与弱引用的区别:
软引用: 软引用是用来描述一些有用但并不是必需的对象, 对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象
弱引用: 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
TransactionalCache
TransactionalCache:继承自Cache接口,主要作用是保存SqlSession在事务中需要向某个二级缓存提交的缓存数据(因为事务过程中的数据可能会回滚,所以不能直接把数据就提交二级缓存,而是暂存在TransactionalCache中,在事务提交后再将过程中存放在其中的数据提交到二级缓存,如果事务回滚,则将数据清除掉)
TransactionalCacheManager
TransactionalCacheManager:用于管理CachingExecutor使用的二级缓存对象,只定义了一个transactionalCaches字段
private final Cache delegate; //对应的二级缓存对象
private boolean clearOnCommit; //是否在commit时清除二级缓存的标记
// 需要在commit时提交到二级缓存的数据
private final Map<Object, Object> entriesToAddOnCommit;
// 缓存未命中的数据,事务commit时,也会放入二级缓存(key,null)
private final Set<Object> entriesMissedInCache;
StatementHandler
StatementHandler接口的实现大致有四个,其中三个实现类都是和JDBC中的Statement响对应的:
- SimpleStatementHandler,这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理; 存在sql注入攻击问题
- PreparedStatementHandler,这个对应JDBC中的PreparedStatement,预编译SQL的接口;
防止sql注入 - CallableStatementHandler,这个对应JDBC中CallableStatement,用于执行存储过程相关的接口;
- RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用。
ResultSetHandler
就是将Statement实例执行之后返回的ResultSet结果集转换成我们需要的List结果集
一级缓存与二级缓存区别
①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(sqlHashMap)是互相不影响的。
②、二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
注意:sqlSession缓存底层存在线程安全问题。
Mybatis使用常用设计模式
Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
工厂模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
单例模式,例如ErrorContext和LogFactory;
代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
组合模式,例如SqlNode和各个子类ChooseSqlNode等;
模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;
迭代器模式,例如迭代器模式PropertyTokenizer;