概念
它是一个半ORM框架,帮你处理了加载驱动、创建连接、statement等操作,最终调用JDBC方法。
优点
集成spring方便;
编写原生sql,优化起来灵活度高;
避免对dto的手动set、get以及设置参数,提供了映射标签,支持自定义映射;
兼容性和JDBC有关,非常好;
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
缺点
原生sql工作量大,同时依赖数据库,依赖程度高,可移植性差;
和Hibernate的区别
mybatis是半ORM,hibernate是完全的ORM,数据库无惯性好,移植数据库方便。
但在互联网项目中,数据库一旦选型,分库分表一做好就不会轻易更换,所以这个可移植性是没必要的,更需要严格控制sql执行性能的能力,所以mybatis才会在互利网公司中流行起来。
一二级缓存
版权声明:本文为CSDN博主「林海静」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jinhaijing/article/details/84230810
一级:sqlSession级,会话查到的数据放入一级缓存
二级:mapper.xml(namespace)级,会话关闭时(openSession.close();后,才能从二级缓存中查数据;),一级缓存放入二级缓存;
开启:
1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
2)、去mapper.xml中配置使用二级缓存: <cache></cache>
3)、我们的POJO需要实现序列化接口
<cache>中配置一些参数:
eviction:缓存的回收策略:
• LRU – 最近最少使用的:移除最长时间不被使用的对象。
• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
• 默认的是 LRU。
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。
mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type="":指定自定义缓存的全类名;
实现Cache接口即可;
一对一配置
版权声明:本文为CSDN博主「Selenium39」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/niangou0915/article/details/90698773
model
@Data
public class User {
private Integer userId;
private String userName;
private Integer cId;
private Car car;
}
@Data
public class Car {
private Integer carId;
private String carName;
}
嵌套查询:(会先后执行两条sql语句)
<mapper namespace="com.wantao.dao.UserDao">
<resultMap type="com.wantao.bean.User" id="map1">
<id column="user_id" property="userId"></id>
<result column="user_name" property="userName" />
<result column="c_id" property="cId" />
<association select="com.wantao.dao.CarDao.findCarById"
property="car" column="c_id">
</association>
</resultMap>
<select id="findUserById" resultMap="map1">
select
*
from tb_user u
where u.user_id=#{id}
</select>
</mapper>
<mapper namespace="com.wantao.dao.CarDao">
<select id="findCarById" resultType="com.wantao.bean.Car">
select car_id,car_name
from
tb_car
where car_id=#{id}
</select>
</mapper>
联合查询:
<mapper namespace="com.wantao.dao.UserDao">
<resultMap type="com.wantao.bean.User" id="map1">
<id column="user_id" property="userId"></id>
<result column="user_name" property="userName" />
<result column="c_id" property="cId" />
<association property="car" javaType="com.wantao.bean.Car">
<id column="car_id" property="carId"/>
<result column="car_name" property="carName"/>
</association>
</resultMap>
<select id="findUserById" resultMap="map1">
select
u.user_id,u.user_name,u.c_id,c.car_id ,c.car_name
from tb_user u
left join tb_car c on u.c_id = c.car_id
where u.user_id=#{id}
</select>
</mapper>
一对多配置
model
@Data
public class User {
private Integer userId;
private String userName;
private List<Car> cars;
}
@Data
public class Car {
private Integer carId;
private String carName;
private Integer uId;
}
联合查询:
<mapper namespace="com.wantao.dao.UserDao">
<resultMap type="com.wantao.bean.User" id="map1">
<id column="user_id" property="userId"></id>
<result column="user_name" property="userName" />
<collection property="cars" ofType="com.wantao.bean.Car">
<id property="carId" column="car_id"/>
<result property="carName" column="car_name"/>
<result property="uId" column="u_id"/>
</collection>
</resultMap>
<select id="findUserById" resultMap="map1">
select
user_id,user_name,car_id,car_name,u_id
from tb_user u
left join tb_car c on u.user_id = c.u_id
where u.user_id=#{id}
</select>
</mapper>
源码分析
分页插件分析
<!--pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
接口定义
@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class PageInterceptor implements Interceptor {
private Dialect dialect;//自定义方言类
@Override
public Object plugin(Object target) {
//TODO Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化,因此考虑在这个方法中做一次判断和初始化
//TODO https://github.com/pagehelper/Mybatis-PageHelper/issues/26
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
...就是把xml配的属性保存到this bean中。
}
//核心方法
@Override
public Object intercept(Invocation invocation) throws Throwable {
//从invocation中提取参数
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
//初始化一次的参数
BoundSql boundSql=ms.getBoundSql(parameter);
CacheKey cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
//中间的逻辑就是看要不要进行分页,要的话就new一个BoundSql,从原来的boundSql中复用一部分属性,最终调用executor.query时用的新BoundSql
//最终调用 走JDBC
List resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
插件从哪开始调用的?
在InterceptorChain中,有如下一段代码:
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
再观察它在哪被调用的
原来mybatis四大组件都初始化构造时启用插件!四大组件为
– executor 执行Ms,BoundSql的,有Base,Caching,Simple、Reuse、Batch实现
– ParameterHandler 主要方法setParameters(PreparedStatement ps)
– StatementHandler 实现有Base,Simple,Callable,Prepared,Routing
– ResultHandler 用于处理结果,主要实现DefaultResultSetHandler
只有Executor是在newExecutor时(也就是获得SqlSession时)启用插件,其它三组件是在BaseStatementHandler的构造方法中启用的。
既然四种组件都进行了插件化,怎么控制在只哪个组件中生效?
仔细看分页插件的配置
而控制生效实际是在
Plugin.wrap(target, this)
这段生成代理类的方法中实现的——
target其实就是哪个组件进行插件化时传的那个组件的实现类,
this指的就是当前实现了Interceptor的类。
wrap中使用JDK实现了一个动态代理,它实现了当前组件接口的所有方法,并传入一个InvocationHandler,它在invoke时会判断当前执行的方法签名是否跟@Intercepts中定义拦截的方法签名一致,一致才会执行interceptor方法(Plugin.wrap中的第二个参数)
也就是四大组件其实都生成了代理类,由@Intercepts中的配置控制在哪个类的哪个方法中才用这个Interceptor拦截。
spring中mybatis的配置
@MapperScan(basePackages = "com.jiangli.api.mapper", sqlSessionFactoryRef = "sqlSessionFactoryCommon")
@Bean
public SqlSessionFactory sqlSessionFactoryCommon(@Qualifier("dataSourceModified") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);//设置数据源
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);//设置配置属性
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));//获得Resource[] 设置mapper.xml资源文件
factoryBean.setTypeAliasesPackage("com.jiangli.api.model");//设置model类路径
return factoryBean.getObject();
}
mybatis从配置到源码分析
SqlSessionFactory配置比较直观,主要引用了DataSource,然后对属性、xml资源、model包进行了配置。
重点看@MapperScan。
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
....
//对BeanDefinition这一层的操作,而@Bean是对Bean instance这一层的操作
public interface ImportBeanDefinitionRegistrar {
//
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
实现了 registerBeanDefinitions 方法
深入MapperScannerRegistrar实现的registerBeanDefinitions方法
1. cps = new ClassPathMapperScanner(registry)
1. 把@MapperScan上设置的属性设置到cps上
1. 调用scanner.doScan(StringUtils.toStringArray(basePackages))【由value、basePackages、basePackageClasses合并而来】;
ClassPathMapperScanner的doScan方法
1. 调用父类ClassPathBeanDefinitionScanner(spring-context中的类)的doScan
对于每一个路径basePackage,找到候选的BD(其实就是路径下所有的class),最终调用registry.registerBD方法
1. processBeanDefinitions
对上一步产生的BD设置class为MapperFactoryBean,对构造函数使用了参数为bd.getBeanClassName,为然后添加属性引用sqlSessionFactory、sqlSessionTemplate、autowireMode
其实到这一步基本流程已经确定,
@MapperScan -> @Import -> registerBD(basePkgs) & process(主要是设置class为MapperFactoryBean),也就是对于pkg下每一个interface,注册了一个MapperFacotryBean。
再来看看MapperFacotryBean的主要方法
1. protected void checkDaoConfig() {
最顶层由InitializingBean的afterPropertiesSet调用
它自己的实现逻辑是如果Configuration中无法通过InterfaceClass找到mapper,就调用addMapper方法
1. public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
getSqlSession()会获得一个SqlSessionTemplate(sqlSessionFactory)(属性在MapperFactoryBean的父类SqlSessionDaoSupport中)
MapperFacotryBean主要是一个代理,实际获得的bean的逻辑都交由Configuration管理;
Configuration是什么
它内部有很多其它类和一些缓存,上面管理mapper的是MapperRegistry类。
MapperRegistry逻辑很简单,内部维护了一个Map<Class,MapperProxyFactory>,如果map找不到,就put一个new MapperProxyFactory
然后使用MapperAnnotationBuilder对type进行解析。
要获取Mapper的时候,获得这个MapperProxyFactory,调用newInstance(sqlSession)方法获得具体实现类;
MapperProxyFactory的newInstance用的JDK代理实现,实现接口显然是MapperInterface,InvocationHandler是new MapperProxy
MapperProxy
它的public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
方法在执行时会判断,如果不是Object定义的方法或是接口默认实现,会调用MapperMethod的execute执行
根据SqlCommand类型调用sqlSession的insert、update、delete等方法;
在DefaultSqlSession实现中,
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
在BaseExecutor中,query方法逻辑如下
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
...
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
...
queryFromDatabase又调用了org.apache.ibatis.executor.SimpleExecutor#doQuery,逻辑如下
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
...
org.apache.ibatis.executor.statement.PreparedStatementHandler#query逻辑如下,
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
总结:使用JDK生成了一个动态代理类(在被@Reference引用时初始化),这个类实现了Mapper接口,调用其定义的方法时会调用MapperMethod根据sql的类型路由到SqlSession的insert、update等方法中。
SqlSession的执行逻辑则是根据参数获得BoundSql,通过Configuration生成StatementHandler,准备参数,执行query、update等方法并返回。