mybatis使用和分析

mybatis和hibernate一样是一个ORM 框架,hibernate设计的时候为了简化sql编写的复杂程度开发了一套hql查询语言,但是实际上针对复杂的查询场景hql显得非常不灵活,并且问题定位也特别复杂。相比而言mybatis支持更加灵活的sql操作,同时也能够灵活的支持结果集的映射。

​ 本文从mybatis使用出发简单的介绍了下mybatis的使用原理,介绍了mybatis的一级和二级缓存,后续将进一步更新mybatis池化数据源PooledDataSource的原理说明,mybatis整合spring的原理,多个数据源配置和原理说明

一、mybaits使用

1、resources/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="cacheEnabled" value="true" /></settings><!-- 别名 --><typeAliases><package name="maintest"/></typeAliases><!-- 数据库环境 --><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!-- 映射文件 --><mappers><mapper resource="maintest/Student.xml"/></mappers></configuration>

2、maintest/Student.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="maintest"><!-- 开启本mapper所在namespace的二级缓存 --><cache /><select id="listStudent" resultType="Student">select * from  student</select><insert id="addStudent" parameterType="Student">insert into student (id, studentID, name) values (#{id},#{studentID},#{name})</insert><delete id="deleteStudent" parameterType="Student">delete from student where id = #{id}</delete><select id="getStudent" parameterType="_int" resultType="Student">select * from student where id= #{id}</select><update id="updateStudent" parameterType="Student">update student set name=#{name} where id=#{id}</update>
</mapper>

3、maintest.Student

package maintest;/*** @author : 18073771* @see [相关类/方法](可选)* @since [产品/模块版本] (可选)*/
public class Student {int id;int studentID;String name;/* getter and setter */public int getId() {return id;}public void setId(int id) {this.id = id;}public int getStudentID() {return studentID;}public void setStudentID(int studentID) {this.studentID = studentID;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

4、maintest.TestMyBatis

public class TestMyBatis {public static SqlSessionFactory sqlSessionFactory;public static int id = 0;public static void main(String[] args) throws IOException {// 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactoryString resource = "resources/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);listUsers();crud();}private static void listUsers() {// 然后根据 sqlSessionFactory 得到 sessionSqlSession session = sqlSessionFactory.openSession();// 最后通过 session 的 selectList() 方法调用 sql 语句 listStudentList<Student> listStudent = session.selectList("listStudent");for (Student student : listStudent) {System.out.println("ID:" + student.getId() + ",NAME:" + student.getName());if (student.id > id) {id = student.id;}}}private static void crud() {// 然后根据 sqlSessionFactory 得到 sessionSqlSession session = sqlSessionFactory.openSession();// 增加学生Student student1 = new Student();student1.setId(id);student1.setStudentID(id);student1.setName("新增加的学生");session.insert("addStudent", student1);// 删除学生Student student2 = new Student();student2.setId(1);session.delete("deleteStudent", student2);// 获取学生Student student3 = session.selectOne("getStudent", 2);// 修改学生student3.setName("修改的学生");session.update("updateStudent", student3);// 最后通过 session 的 selectList() 方法调用 sql 语句 listStudentList<Student> listStudent = session.selectList("listStudent");for (Student student : listStudent) {System.out.println("ID:" + student.getId() + ",NAME:" + student.getName());}// 提交修改session.commit();// 关闭 sessionsession.close();}
}

二、一次sql执行的流程步骤

1、读取mybatis-config.xml

将mybatis-config.xml读取成输入流

2、生成SqlSessionFactory

解析mybatis配置的输入流,构建Configuration配置对象

将Configuration对象加载到SqlSessionFactory中

Configuration中的几个重要的属性:Environment里面包含dataSource, mappedStatements

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

MappedStatement对象,用来绑定sql,这也是为什么MappedStatement中需要这些属性

private String resource;
private Configuration configuration; // 配置对象
private String id; // mapper.xml中的namespace 中的sqlid
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType; // 结果集类型
private SqlSource sqlSource; // 绑定了sql的语句
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;

它里面的属性和mapper.xml中的属性相关

<!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST select
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
fetchSize CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
useCache (true|false) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true|false) #IMPLIED
resultSets CDATA #IMPLIED
>

例如:下面这个更新的sql变成MappedStatement后是这样的

<update id="updateStudent" parameterType="Student">update student set name=#{name} where id=#{id}
</update>

3、创建SqlSession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {// environment 里有数据源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();}
}

【1】sqlSession 是否可以做成单例?

不能,sqlSession是会话级别的,如果做成了全局的单例会造成全局只能有一个会话不能支持高并发

【2】autoCommit 配true和false的区别?

默认是false,true 的时候修改操作会自动提交,false的时候不会自动提交。

【3】几种不同的executor区别SimpleExecutor、ReuseExecutor、BatchExecutor?

【1】SimpleExecutor

就是最简单的执行器

【2】ReuseExecutor

会将preparedStatement 缓存下来; 下次再执行的时候就可以复用connection preparedStatement 的缓存map会在事务commit之后自动清除,并关闭statement

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;BoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();// 如果已经存在该sql的preparedStatementif (hasStatementFor(sql)) {// 直接statementMap 中获取preparedStatementstmt = getStatement(sql);} else {// 获取连接Connection connection = getConnection(statementLog);// 准备preparedStatementstmt = handler.prepare(connection);// 缓存 preparedStatementputStatement(sql, stmt);}// 参数化handler.parameterize(stmt);return stmt;
}
【3】BatchExecutor

主要是批量更新的时候使用,其实是通过PrepareStatement的addBatch方法去批量更新的,

public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {final Configuration configuration = ms.getConfiguration();final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT,null, null);final BoundSql boundSql = handler.getBoundSql();final String sql = boundSql.getSql();final Statement stmt;if (sql.equals(currentSql) && ms.equals(currentStatement)) {int last = statementList.size() - 1;stmt = statementList.get(last);BatchResult batchResult = batchResultList.get(last);batchResult.addParameterObject(parameterObject);} else {Connection connection = getConnection(ms.getStatementLog());// 准备prepareed statementstmt = handler.prepare(connection);// 记录当前sqlcurrentSql = sql;// 记录当前mappedstatementcurrentStatement = ms;// 缓存到statement集合中statementList.add(stmt);batchResultList.add(new BatchResult(ms, sql, parameterObject));}// 参数化handler.parameterize(stmt);// 加入批量更新handler.batch(stmt);return BATCH_UPDATE_RETURN_VALUE;
}

测试案例

/***  BatchExecutor 测试,效果应该是最后一次更新; 并且更新时间要比simpleExecutor更新时间短*/private static void batchExecutorTest() {long start = System.currentTimeMillis();// 然后根据 sqlSessionFactory 得到 sessionSqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH,false);// 获取学生Student student3 = session.selectOne("getStudent", 3);System.out.println(student3);// 获取学生Student student4 = session.selectOne("getStudent", 4);System.out.println(student4);// 获取学生Student student5 = session.selectOne("getStudent", 5);System.out.println(student5);// 修改学生student3.setName("修改的学生3");session.update("updateStudent", student3);// 修改学生student4.setName("修改的学生4");session.update("updateStudent", student4);// 修改学生student5.setName("修改的学生5");session.update("updateStudent", student5);// 手动提交session.commit();long end = System.currentTimeMillis();System.out.println("BatchExecutor 更新的执行时间为:" + (end - start));}

结果

结果 只prepare一次
DEBUG [main] - ==>  Preparing: update student set name=? where id=?
DEBUG [main] - ==> Parameters: 修改的学生3(String), 3(Integer)
DEBUG [main] - ==> Parameters: 修改的学生4(String), 4(Integer)
DEBUG [main] - ==> Parameters: 修改的学生5(String), 5(Integer)
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@880ec60]
BatchExecutor 更新的执行时间为:384不批量的操作结果
DEBUG [main] - ==>  Preparing: update student set name=? where id=?
DEBUG [main] - ==> Parameters: 修改的学生3(String), 3(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - ==>  Preparing: update student set name=? where id=?
DEBUG [main] - ==> Parameters: 修改的学生4(String), 4(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - ==>  Preparing: update student set name=? where id=?
DEBUG [main] - ==> Parameters: 修改的学生5(String), 5(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@880ec60]
SimpleExecutor 更新的执行时间为:362

【4】绑定executor的JdbcTransaction事务是干什么的?

用于创建连接,设置隔离级别、自动提交

protected void openConnection() throws SQLException {if (log.isDebugEnabled()) {log.debug("Opening JDBC Connection");}// 创建一个连接connection = dataSource.getConnection();if (level != null) {// 设置事务隔离界别connection.setTransactionIsolation(level.getLevel());}// connection设置是否自动提交setDesiredAutoCommit(autoCommmit);
}

【5】connection、statement何时关闭?

commit的时候关闭statement;

BaseExecutor 中的commit方法
public void commit(boolean required) throws SQLException {if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");// 清除本地缓存clearLocalCache();// 如果是ReuseExecutor清除缓存的preparedStatement 缓存map并关闭StatementflushStatements();if (required) {// 事务提交transaction.commit();}
}

在处理结果集后关闭连接

JdbcTransaction中的close方法
public void close() throws SQLException {if (connection != null) {resetAutoCommit();if (log.isDebugEnabled()) {log.debug("Closing JDBC Connection [" + connection + "]");}connection.close();}
}

【6】SqlSession、JdbcTransaction、Executor、StatementHandler、Connection之间的关系?

SqlSession:一次会话,可能有多次sql操作

JdbcTransaction:事务,可以获取连接,绑定在Executor中

Executor:执行器,绑定在SqlSession中,创建SqlSession的时候可以指定执行器

StatementHandler:statement处理器,每次sql操作都会new一个新的来创建statement

Connection:和数据库的一次连接,如果是相同的sql statement可以复用同一个连接,否则sql操作前要创建连接

关系 SqlSession:JdbcTransaction:Executor:StatementHandler:Connection = 1:1:1:n:(1-n)

4、执行sql和结果集映射

【1】需要事务的update操作

insert为例,最终执行的还是update方法

ReuseExecutor中的doUpdate方法

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {// 获取配置Configuration configuration = ms.getConfiguration();// 创建stateMement处理器--PreparedStatementHandlerStatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);// 创建preparedStatementStatement stmt = prepareStatement(handler, ms.getStatementLog());// 执行更新操作return handler.update(stmt);}

ReuseExecutor中的prepareStatement方法

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;BoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();// 如果已经存在该sql的preparedStatementif (hasStatementFor(sql)) {// 直接statementMap 中获取preparedStatementstmt = getStatement(sql);} else {// 获取连接Connection connection = getConnection(statementLog);// 准备preparedStatementstmt = handler.prepare(connection);// 缓存 preparedStatementputStatement(sql, stmt);}// 参数化--参数化后sql中的?被具体的参数替换handler.parameterize(stmt);return stmt;
}

PreparedStatementHandler中的update方法

public int update(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;// 执行sql--如果ps中connection属性中的autocommit=true则自动提交ps.execute();// 获取更新的条数int rows = ps.getUpdateCount();Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);return rows;
}
【2】不需要事务的select操作

没有缓存的情况下

最终通过ReuseExecutor 的 doQuery进行查询
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql) throws SQLException {// 获取配置Configuration configuration = ms.getConfiguration();// 创建stateMement处理器--PreparedStatementHandlerStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,boundSql);// 创建preparedStatementStatement stmt = prepareStatement(handler, ms.getStatementLog());// 执行查询return handler.<E> query(stmt, resultHandler);
}

最终调用PreparedStatementHandler的query方法进行查询操作

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();// 结果集映射return resultSetHandler.<E> handleResultSets(ps);}

缓存查询的结果,这就是是mybatis的一级缓存,对查询sql 的结果进行缓存,如果是同一个查询sql不会再查库,

那么缓存的删除策略是什么?

同一个session会话中如果有update、commit、rollback、或者flushCache操作会调用clearLocakCache清除本地一级缓存,通过这种方式保证了缓存的一致性。

BaseExecutor 中的update方法public int update(MappedStatement ms, Object parameter) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());if (closed)throw new ExecutorException("Executor was closed.");// 清除本地一级缓存clearLocalCache();return doUpdate(ms, parameter);
}

5、关闭SqlSession

SqlSession的close方法
public void close() {try {// 调用执行器的close方法executor.close(isCommitOrRollbackRequired(false));dirty = false;} finally {ErrorContext.instance().reset();}}BaseExecutor的close方法
public void close(boolean forceRollback) {try {try {rollback(forceRollback);} finally {// 调用事务的close方法if (transaction != null) transaction.close();}} catch (SQLException e) {// Ignore.  There's nothing that can be done at this point.log.warn("Unexpected exception on closing transaction.  Cause: " + e);} finally {transaction = null;deferredLoads = null;localCache = null;localOutputParameterCache = null;closed = true;}}JdbcTransaction 的close方法,关闭连接
public void close() throws SQLException {if (connection != null) {resetAutoCommit();if (log.isDebugEnabled()) {log.debug("Closing JDBC Connection [" + connection + "]");}connection.close();}}

三、mybatis缓存

1、一级缓存

【1】简介

mybatis一级缓存是session会话级别的缓存,即在一次sqlSession会话过程中的缓存,它的作用是针对相同的sql查询操作缓存查询结果,减少数据库的查询操作。

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}// 结果缓存到本地;PerpetualCachelocalCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}
【2】一级缓存的命中场景

运行时参数相关
1.SQL传入参数一致
2.同一个会话(SqlSession对象,一级缓存属于会话级缓存)
3.方法名和类名必须一样(Statement ID必须一样如:com.xxx.XXXMapper.findById())
4.行范围一样 rowbound

操作配置相关
5.不手动清空缓存 (-cleanCache -commit rollback)
6.没有Update操作
7.缓存作用域不能是STATEMENT
8.未配置flushCash为false

【3】总结
  1. MyBatis一级缓存的生命周期和SqlSession一致。
  2. MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
  3. MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

2、二级缓存

二级缓存是statement级别的缓存,可以在不同的session会话之间共享,通过CachingExecutor实现。

【1】CachingExecutor

此类只实现二级缓存的方法,具体的sql执行操作仍然委派给具体的Executor去执行;

二级缓存和一级缓存的区别是二级缓存可以跨会话,sqlSession2可以使用sqlSession1的缓存;二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。

当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

Congifuration 中的newExecutor方法public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}// 如果开启了使用二级缓存,用CachingExecutor包装之前的executorif (cacheEnabled) {executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}
【2】实例,使用步骤

a、在全局mybatis配置中设置开启二级缓存

b、在mapper映射文件中开启cache

c、正常使用Simple、Reuse、Batch执行器正常使用即可,在执行前会将他们包装陈CachingExecutor

private static void cachingExecutorTest() {System.out.println("----------第一个会话----------");// 然后根据 sqlSessionFactory 得到 sessionSqlSession session = sqlSessionFactory.openSession(ExecutorType.SIMPLE,false);// 获取学生---查库Student student3 = session.selectOne("getStudent", 3);System.out.println(student3.toString());// 获取学生---二级缓存能走到吗?Student student31 = session.selectOne("getStudent", 3);System.out.println(student31.toString());// 获取学生---查库Student student4 = session.selectOne("getStudent", 4);System.out.println(student4.toString());// 获取学生---查库Student student5 = session.selectOne("getStudent", 5);System.out.println(student5.toString());// 为什么需要在这提交后面session2才能命中二级缓存session.commit();System.out.println("----------第二个会话----------");// 第二个会话SqlSession session2 = sqlSessionFactory.openSession(ExecutorType.SIMPLE,false);// 获取学生---- 二级缓存Student student23 = session2.selectOne("getStudent", 3);System.out.println(student23.toString());// 获取学生--- 二级缓存Student student231 = session2.selectOne("getStudent", 3);System.out.println(student231.toString());// 获取学生---- 二级缓存Student student24 = session2.selectOne("getStudent", 4);System.out.println(student24.toString());// 获取学生---- 二级缓存Student student25 = session2.selectOne("getStudent", 5);System.out.println(student25.toString());
}

执行结果

----------第一个会话----------
DEBUG [main] - Cache Hit Ratio [maintest]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1531333864.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5b464ce8]
DEBUG [main] - ==>  Preparing: select * from student where id= ?
DEBUG [main] - ==> Parameters: 3(Integer)
DEBUG [main] - <==      Total: 1
[id=3, studentID=3, name=修改的学生3]
DEBUG [main] - Cache Hit Ratio [maintest]: 0.0 // 这里为什么没有命中二级缓存?因为没有commit
[id=3, studentID=3, name=修改的学生3]
DEBUG [main] - Cache Hit Ratio [maintest]: 0.0
DEBUG [main] - ==>  Preparing: select * from student where id= ?
DEBUG [main] - ==> Parameters: 4(Integer)
DEBUG [main] - <==      Total: 1
[id=4, studentID=4, name=修改的学生4]
DEBUG [main] - Cache Hit Ratio [maintest]: 0.0
DEBUG [main] - ==>  Preparing: select * from student where id= ?
DEBUG [main] - ==> Parameters: 5(Integer)
DEBUG [main] - <==      Total: 1
[id=5, studentID=5, name=修改的学生5]
----------第二个会话----------
DEBUG [main] - Cache Hit Ratio [maintest]: 0.2
[id=3, studentID=3, name=修改的学生3]
DEBUG [main] - Cache Hit Ratio [maintest]: 0.3333333333333333
[id=3, studentID=3, name=修改的学生3]
DEBUG [main] - Cache Hit Ratio [maintest]: 0.42857142857142855
[id=4, studentID=4, name=修改的学生4]
DEBUG [main] - Cache Hit Ratio [maintest]: 0.5
[id=5, studentID=5, name=修改的学生5]
【3】底层实现

session.commit之后二级缓存才生效,commit操作做了啥?

查看源码发现session.commit操作最终是通过cache链最终完成commit操作的,其实就是通过装饰模式实现的。

TransactionalCache—>SynchronizedCache---->LoggingCache---->SerializedCache----->LruCache----->PerpetualCache

A、PerpetualCache

最终放到此类的hashMap中;key是sql,value是序列化后的查询结果

B、LruCache

LRU装饰器,它会把PerpetualCache 的map中最近最久未使用的key删除

C、SerializedCache

序列化装饰器,将查询结果集序列化

D、LoggingCache

日志打印装饰器

E、SynchronizedCache

同步处理的装饰器,防并发

F、TransactionalCache

事务处理的装饰器

TransactionalCacheManagerpublic void commit() {// transactionalCaches map 的key是mapedStatement 中的cache对象,value是TransactionalCache缓存for (TransactionalCache txCache : transactionalCaches.values()) {txCache.commit();}
}
【4】总结

二级缓存:MyBatis二级缓存的工作流程和前文提到的一级缓存类似,只是在一级缓存处理前,用CachingExecutor装饰了BaseExecutor的子类,在委托具体职责给delegate之前,实现了二级缓存的查询和写入功能。

MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。

MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。

在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。

四、mybatis更换数据源

1、mybatis如何支持多个数据源?

mybatis数据源介绍:

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

不整合spring如何实现多个数据源配置

A、配置多个sqlSessionFactory

B、可以加个路由的中间件,像mycat

C、对Configuration中的dataSource改造:参考另一篇博文修改mybatis源码

五、mybatis整合spring(后续更新)

1、如何支持spring事务?

结语

在源码测试和使用的过程中发现一个现象就是模拟10万并发查询数据库的时候发现单个sqlSession的处理时间和每次查询打开一个sqlSession的处理时间相比差不多还快一点点。后续将分析下为什么会产生这种现象

/*** @author : 18073771* @see [相关类/方法](可选)* @since [产品/模块版本] (可选)*/
public class TestMyBatis {public static SqlSessionFactory sqlSessionFactory;public static int id = 0;public static void main(String[] args) throws IOException {// 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactoryString resource = "resources/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//        connectionTest();connectionTest1();}/*** 将查询改为2ms后执行时间为244115* 将连接池最大连接数调整成40后 226663*/private static void connectionTest() {long start = System.currentTimeMillis();final SqlSession session = sqlSessionFactory.openSession();for (int i = 0; i < 10000; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// 获取学生---查库Student student3 = session.selectOne("getStudent", RandomUtils.nextInt());if (null != student3) {System.out.println(student3.toString());}}});thread.start();try {// 阻塞main线程thread.join();} catch (InterruptedException e) {e.printStackTrace();}}long end = System.currentTimeMillis();System.out.println("程序执行时间为" + (end - start));}/*** 程序执行时间为68628* 单次查询改为2ms后,执行时间288915* 将连接池最大连接数调整成40后,261498*/private static void connectionTest1() {long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {SqlSession session = sqlSessionFactory.openSession();// 获取学生---查库Student student3 = session.selectOne("getStudent", RandomUtils.nextInt());if (null != student3) {System.out.println(student3.toString());}session.close();}});thread.start();try {// 阻塞main线程thread.join();} catch (InterruptedException e) {e.printStackTrace();}}long end = System.currentTimeMillis();System.out.println("程序执行时间为" + (end - start));}
}

参考资料:

https://tech.meituan.com/2018/01/19/mybatis-cache.html

mybatis使用和分析相关推荐

  1. mybatis源码分析之事务管理器

    2019独角兽企业重金招聘Python工程师标准>>> 上一篇:mybatis源码分析之Configuration 主要分析了构建SqlSessionFactory的过程中配置文件的 ...

  2. mybatis 源码分析, 初始化

    分析版本 我们先分析xml配置文件形式的 mybatis, 等我们分析完毕了, 可以去分析 mybatis 和 spring 的对接 pom.xml <dependency><gro ...

  3. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

  4. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  5. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  6. MyBatis 源码分析 - 内置数据源

    1.简介 本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑.搞懂这些数据源的实现,可使大家对数据源有更深入的认识.同时在配置这些数据源时,也会更清楚每种属性的意义和用途.因此,如果大家想知其 ...

  7. MyBatis 源码分析 - SQL 的执行过程

    本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析.运 ...

  8. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  9. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  10. springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三)

    springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三) 例: package com.example.demo.service;import com.exa ...

最新文章

  1. VTK:vtkTupleInterpolator 插值用法实战
  2. 命令行下 pdb 调试 Python 程序
  3. 右键菜单无响应_被流氓软件玩坏了?这两个清理工具拯救你凌乱的右键菜单。...
  4. 选课 topsort
  5. maven添加sqlserver的jdbc驱动包
  6. vue3 el-form表单验证 自定义校验
  7. 20165323 第一周学习总结
  8. 《Spring微服务实战》读书笔记——通过配置服务器来管理配置
  9. 检测相关问题面试准备
  10. POE 网络变压器 Pulse 普思
  11. python2和python3 print_新手学习Python2和Python3中print不同的用法
  12. word中行与行间距大
  13. dblink(dblink是什么意思)
  14. 2021年北京积分落户名单公布了,爬了两个多小时得到了所有数据,有了惊人的发现(附源码)
  15. Tita 绩效宝:让管理者提高1对1面谈水平的5大技巧
  16. 0XC000007b问题的一种定位方法
  17. 思科C3750密码丢失重置恢复方法
  18. C/C++的指针与数组
  19. 【新】Python获取前N周时间开始日期和截止日期
  20. 顺丰菜鸟之争!物流APP开发亟待提升末端服务问题

热门文章

  1. CentOS 7下安装集群Zookeeper-3.4.9
  2. Underscore.js (1.7.0)-集合(Collections)(25)
  3. 配置管理系统和整体的变化对系统有什么区别和联系
  4. 升级EXCHANGE2010到2013(C)
  5. DNS服务器配置详解
  6. 《Ray Tracing in One Weekend》——Chapter 5: Surface normals and multiple objects
  7. Q102:光线追踪场景(1)——地球仪
  8. 数据挖掘比赛笔记总结
  9. 机器学习笔记-XGBoost
  10. BI软件应用在哪些方面