一、Mybatis环境快速入门
1、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>
2、创建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>
3、Mapper配置文件
<?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>
4、实体类
public class UserEntity { private Integer id; private Date birdate; private String name; }
5、mapper接口
public interface UserMapper { public UserEntity getUser(int id); }
6、运行Mybatis代码
public class TestMyBatis { public static void main(String[] args) { try { //配置文件
String configXml = "mybatis_config.xml"; //加载配置获取流
Reader reader = Resources.getResourceAsReader(configXml); //获取sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); //获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(); //获取对应的mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //执行方法
UserEntity user = userMapper.getUser(1); System.out.println("name:" + user.getName()); } catch (IOException e) { e.printStackTrace(); } } }
7、数据表结构
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核心配置文件
1、Properties(属性)
Java属性文件可以配置直观的。
如:
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql:///mybatis"/>
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="root"/>
</properties>
或者通过直接引入属性文件,例如:
<properties resource="db.properties"></properties>
然后db.properties文件中的配置就是:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///mybatis
jdbc.username=root jdbc.password=root
2、typeAliases(类型别名)
类型别名是Java类型的简称。
逐个设置,例如:
<typeAliases>
<typeAlias type="com.mayikt.entity.User" alias="user"/>
</typeAliases>
3、Plugins
分页插件配置
四、Mybatis大体架构流程分析
1、流程图
1、读取resources获取对应的Reader对象。
reader = Resources.getResourceAsReader(resources);
2、使用SqlSessionFactoryBuilder获取SqlSessionFactory源码分析
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
源码分析:
- Reader reader = Resources.getResourceAsReader(resources);
调用javaioAPI 读取resources配置文件,获取InputStreamReader
2、SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
3、使用XMLConfigBuilder 解析mybatis配置文件
SqlSessionFactoryBuilder使用XMLConfigBuilder解析配置文件,封装成Configuration对象。
4、因为在构造函数设置了parsed 为fasle,XMLConfigBuilder 只能被使用一次。
调用该方法解析mybatis_config文件
通过反射机制匹配接口
注意:XMLConfigBuilder运行之后,只能被解析一次 否则会抛出异常。
xml转换程bean对象 configuration
XMLConfigBuilder的作用是:解析mybatis配置文件文件 得到configuration
XMLMapperBuilder的作用是什么: 解析mybatisMapper文件
建议很多源码中设置值都是采用构造函数形式
loadedResource 存放都是mybatis映射的文件路由地址 使用set集合存放
1.protected final Set<String> loadedResources = new HashSet<String>();
注意:mapper文件配置的namespace一定要和接口对应 否则情况查找失败!
2mapperRegistry作用存放dao层mapper接口 底层使用过map集合存放。
解析配置文件完成了之后,都会装配到configuration
Configuration作用:mybatis核心的配置文件内容 ,使用xml转换bean
Mybatis扫包方式有两种一种是 写package、和resource
明白了 mapperRegistry 注册我们的Mapper文件。
使用configuration获取默认的DefaultSqlSessionFactory
五、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); } }
接口绑定源码分析:
解析节点信息封装到Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(),然后添加到configuration里面
转换成Java类就是一个MappedStatement(存sql信息)
使用Configuration的getMappedStatement方法来获取MappedStatement对象
获取的方式key的组成为命名空
getMapper接口
从mapperRegistry获取查询接口对应的绑定接口
1、检查是否已经注册过Mapper接口
2、使用MapperProxyFactory 创建代理类MapperProxy
UserMapper.getUser方法的时候 调用MapperProxy的invoke方法
因为mapperRegister中 key:mapper接口 value MapperProxyFactory
使用MapperProxyFacotory创建MapperProxy代理
Mybatis基于多个不同的接口生成代理类 不同接口肯定不同的invoke方法
相同的接口,不同的方法肯定是走同一个invoke方法。
UserMapper.getUser()执行原理分析
1、调用MapperPrxoxy的invoke方法()
2、实现接口方法与配置文件sql语句关联
底层使用的是:result = sqlSession.selectOne(command.getName(), param);
大致原理分析:
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。
射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,
从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询
(也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。
五、总结:
- 获取本地InputStreamReader对象(mybatis配置文件)
- 调用SqlSessionFactoryBuilder
- ###在使用XMLConfigBuilder解析mybatis配置文件,装配到Configuration中。
- 将配置文件中的Mapper添加到Configuration mapperRegistry实现注册。
备注:mapperRegistry存放当前所有的mapper文件。
5.使用Configuration获取默认的DefaultSqlSessionFactory
六、MybatisMapper SQLSession源码分析
1、SQLSession的作用
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的。
2、Executor执行器原理分析
1.openSessionFromDataSource,首先是从 Configuration 中取出相关的配置,生成 Transaction,接着又创建了一个 Executor,最后返回了 DefaultSqlSession 对象。
2.SimpleExecutor: 默认的 Executor,每个 SQL 执行时都会创建新的 Statement
ResuseExecutor: 相同的 SQL 会复用 Statement
BatchExecutor: 用于批处理的 Executor
CachingExecutor: 可缓存数据的 Executor,用代理模式包装了其它类型的 Executor
默认情况下使用缓存的CachingExecutor
创建的openSession源码分析:
1、创建事务管理器
2、创建执行器、
默认是创建简单的执行器会变成缓存执行器呢
最好交给DefaultSqlSession
默认创建SimpleExecutor执行器 ,判断是否开启二级缓存,如果开启了二级缓存
CachingExecutor执行器构造函数传递SimpleExecutor
如果二级缓存没有,走简单执行器
3、SelectOne底层原理查询分析
- 当查询单条数据的时候,最终还是调用selectList查询多个结果集包装程单个对象。
- 从configuration中获取到MappedStatement(对应的sql语句配置),调用executor的query方法实现执行。
先查询二级缓存,是否有缓存,没有的话调用delegate.<E> query
如果一级缓存中没有该结果,会调用queryFromDatabase查询数据库得到数据让后在缓存到一级缓存中,下次查询的时候相同的sql语句直接走一级缓存不会查询数据库。
一级(sqlSession缓存)和二级缓存(sessionFactory)
为什么CachingExecutor需要找到SimpleExecutor创建缓存key呢? 方便实现缓存key代码重构
mybatis缓存控制 先查找二级缓存(硬盘、redis)、二级缓存没有的情况在查找一级缓存。
一级缓存绝对是有的 ,但是二缓存可以没有。
PerpetualCache 指的就是我们的一级 一级缓存属于本地缓存 存放在内存中 使用map集合存放
4、Mybatis一级与二级缓存
一级缓存实现原理
相同查询sql语句和参数
第一次查询的时候 会调用数据库的查询 ,缓存到本地内存中
第二次查询的时候 直接走本地内存 不会查询数据库。
sqlSession缓存为了防止脏数据,增加、修改、删除的时候 都会清楚所有本地一级缓存。
mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,
在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。
具体流程:
1.第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来
2.第二次执行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;
八、流程图总结