一、mybatics的配置步骤
1.POM依赖包加载
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
2.自定义MYBATICS配置对象。
/**
* mybatics的数据工厂配置。
*/
@Configuration
@MapperScan(basePackages = "com.tpw.summaryday.dao", sqlSessionFactoryRef = "test2_sqlSessionFactory")
public class MybaticsSessionConf {
//配置mybatis的分页插件pageHelper
@Bean
public PageHelper pageHelper() {
PageHelper pageHelper = new PageHelper();
Properties props = new Properties();
props.setProperty("dialect", "mysql");
// 表示支持从接口中读取pageNum和pageSize
props.setProperty("supportMethodsArguments", "true");
pageHelper.setProperties(props);
return pageHelper;
}
@Bean(name = "test2_dataSource")
@ConfigurationProperties(prefix = "spring.datasource.test2")
public DataSource getDateSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "test2_sqlSessionFactory")
public SqlSessionFactory getSqlSessionFactory(@Qualifier("test2_dataSource") DataSource test2DataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(test2DataSource);
bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
// bean.setMapperLocations(
// // 设置mybatis的xml所在位置
// new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/localdb/*.xml"));
return bean.getObject();
}
@Bean("test2_sqlSessionTemplate")
public SqlSessionTemplate getSqlsessiontemplate(@Qualifier("test2_sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
try {
return new SqlSessionTemplate(sqlSessionFactory);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
3.环境配置文件加载pageHelper配置
pagehelper.helperDialect = mysql
pagehelper.reasonable= true
pagehelper.supportMethodsArguments= true
pagehelper.params= count=countSql
4.mybatis-generator.xml配置,用来生成DAO,MAPPER,ENTITY
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库链接地址账号密码-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://rm-wz96s5izji5099uz13o.mysql.rds.aliyuncs.com:3306/test2?"
userId="" password="">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model类存放位置-->
<javaModelGenerator targetPackage="com.tpw.summaryday.entity" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--生成映射文件存放位置-->
<sqlMapGenerator targetPackage="com.tpw.summaryday.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--生成Dao类存放位置-->
<!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
-->
<javaClientGenerator type="ANNOTATEDMAPPER" targetPackage="com.tpw.summaryday.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--生成对应表及类名-->
<table tableName="fanli_directional_goods" domainObjectName="DirectionalGoods"
enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"
enableSelectByExample="false" selectByExampleQueryId="false"></table>
</context>
</generatorConfiguration>
5.mybatis-config.xml配置,开启了二级缓存
<?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>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--这个配置使全局的映射器(二级缓存)启用或禁用缓存-->
<setting name="cacheEnabled" value="true" />
</settings>
</configuration>
6.DAO层添加二级缓存配置
@CacheNamespace(implementation = RedisCache.class,flushInterval = 60L)
public interface DirectionalGoodsMapper {
@Delete({
"delete from fanli_directional_goods",
"where id = #{id,jdbcType=INTEGER}"
})
int deleteByPrimaryKey(Integer id);
@Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_akbgoods a where g.goods_id = a.data_id and g.goods_id = #{goodsId} limit 1")
DirectGoodsDto selectByGoodsId(String goodsId);
@Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_akbgoods a where g.goods_id = a.data_id and g.goods_id = #{goodsId}")
List<DirectGoodsDto> listByGoodsId(String goodsId);
@Select("select * from fanli_directional_goods where stime >= #{stime} and etime <= #{etime}")
List<DirectionalGoods> pageByStimeAndEtime(String stime, String etime/*, @Param("pageNum")int startIndex, @Param("pageSize")int pageNumIn*/);
}
二、mybatics的DAO层对象加载流程
1.在CONFIG类中加入了@MapperScan(basePackages = "com.tpw.summaryday.dao", sqlSessionFactoryRef = "test2_sqlSessionFactory"),mapperScan对象引入了@Import(MapperScannerRegistrar.class)
org.mybatis.spring.annotation.MapperScannerRegistrar
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
2.然后跳到org.mybatis.spring.mapper.ClassPathMapperScanner.doScan 方法,去搜索DAO目录下所有的MAPPER对象。
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
这里生成的BEANDEFINERD中的beanClass就是MapFactoryBean工厂类。
3.初始化数据源,sqlSessionFactory,sqlSessionTemplate对象。
4.开始创建DAO层代理对象。是根据 org.mybatis.spring.mapper.MapperFactoryBean.getObject来通过工厂类BEAN创建对象。由于是工厂BEAN创建对象,首先要创建工厂对象。
这时在进行工厂对象的实例化和初始化操作。
5.上面工厂对象的afterPropertiesSet方法中,接着会调用到org.apache.ibatis.binding.MapperRegistry中的addMapper方法,将mapper映射保存起来。
上面类中有这样一个MAPPER映射哈希表。为一个****Mapper对象一个MAPPER代理工厂对象。
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
MapperRegistry为org.apache.ibatis.session.Configuration下面的一个成员变量。Configuration也为sqlSession下一个成员。
Configuration configuration = getSqlSession().getConfiguration();
public Configuration getConfiguration() {
return this.sqlSessionFactory.getConfiguration();
}
从上面可以看出,configuation,MapperRegistry为一个sqlSessionFactory全局共享。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
6.这个为 org.mybatis.spring.mapper.MapperFactoryBean.getObject方法,就是通过sqlSession去创建一个mapper对象,所以我们在外面也可以通过sqlSession获取任意的mybatics的MAPPER对象。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
7.这里面的sqlSession其实为sqlSessionTemplate对象,然后会调用MapperRegistry的
getMapper方法来创建对象。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
最终也就是调用org.apache.ibatis.binding.MapperProxyFactory.newInstance来创建代理对象。
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
8.我们可以看到,最终就是生成一个JDK的动态代理对象,真正的代理实现类为MapperProxy
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
三、mybatics的DAO层接口调用流程
1.上面说过DAO层接口对象实际上是JDK生成的动态代理对象,dao层的任何接口都会走向代理对象的invoke方法。
org.apache.ibatis.binding.MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
2.首先创建mapperMethod对象,传入sqlSession.getConfiguration()对象。
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
3.然后会调用到MapperMethod.execute方法,从这里可以看到,都是通过sqlSession的接口转发。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
return result;
}
4.由于我们执行的是返回一个list,所以执行的是executeForMany方法
@Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_goods a where g.goods_id = a.data_id and g.goods_id = #{goodsId}") List<DirectGoodsDto> listByGoodsId(String goodsId);
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
5.这里面的sqlSession为sqlSessionTemplate对象,继而调用此类的selectList方法
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.<E> selectList(statement, parameter);
}
这里的sqlSessionProxy又是一个JDK动态代理对象,实际的实现类为SqlSessionInterceptor this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor());
6.接着就会调用SqlSessionInterceptor.invoke方法,进行反射调用。这里面会先获取一个sqlSession(同一个事务就会共用,否则每次都会创建一个新的)
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
7.创建session为org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource,注意这里会先根据mybatics的config类以及数据源开启一个JDBC的事务,并且生成一个带缓存的执行器,然后会再生成一个SQL会话,传入执行器,配置类,默认自动提交为FALSE。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
8.接着反射调用到org.apache.ibatis.session.defaults.DefaultSqlSession.selectList
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
9.这里面的executor为带缓存的执行器,然后由于我们加入了分页插件,所以加入了
com.github.pagehelper.pageInterceptor,
public Object intercept(Invocation invocation) throws Throwable {
Object var16;
try {
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();
CacheKey cacheKey;
BoundSql boundSql;
this.checkDialectExists();
List resultList;
if (!this.dialect.skip(ms, parameter, rowBounds)) {
resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
} finally {
this.dialect.afterAll();
}
return var16;
}
10.由于我们是普通查询,没有分页,所以执行executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
执行到org.apache.ibatis.executor.BaseExecutor.query方法
这里的cache只是在同一个sqlSession中有效,由于spring的特性,每次都会开启一个新的会话,所以一级缓存无效。这里查询的是数据库。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
return list;
}
11.然后会执行到org.apache.ibatis.executor.SimpleExecutor.doQuery,这里最终会去创建数据源连接,然后生成一个预处理的语句,然后进行JDBC的数据源查询 。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
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);
} finally {
closeStatement(stmt);
}
}
12.执行完查询结果后,就会返回到SqlSessionInterceptor.invoke方法,最终发现本方法没有开启事务,进行自动提交,返回结果,然后关闭会话。
13.至此查询流程完成。
四、mybatics的一级查询缓存在SPRING中无效,上面已经讲过,二级缓存可以开启,在同 一个命名空间中生效。
二级缓存,实现Cache接口,然后底层实现可以使用REDIS等各种分布式缓存。