正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持。
1. 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
一级缓存
MyBatis的一级缓存指的是在一个Session域内,session未关闭的时候执行的查询会根据SQL为key被缓存(跟mysql缓存一样,修改任何参数的值都会导致缓存失效)
1. 默认开启
2. 必须同一个session,如果session对象已经close()过了就不能用了
3. 查询条件必须一致
4. 执行过session.cleanCache();会清理缓存
5. 执行过增删改操作(这些操作都会清理缓存)
案例1.单独使用MyBatis而不继承Spring,使用原生的MyBatis的SqlSessionFactory来构造sqlSession查询,是可以使用以及缓存的,示例代码如下
public class Test { public static void main(String[] args) throws IOException { String config = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(config); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(); System.out.println(session.selectOne("selectUserByID", 1)); // 同一个session的相同sql查询,将会使用一级缓存 System.out.println(session.selectOne("selectUserByID", 1)); // 参数改变,需要重新查询 System.out.println(session.selectOne("selectUserByID", 2)); // 清空缓存后需要重新查询 session.clearCache(); System.out.println(session.selectOne("selectUserByID", 1)); // session close以后,仍然使用同一个db connection session.close(); session = factory.openSession(); System.out.println(session.selectOne("selectUserByID", 1)); } }
输入日志
DEBUG - Openning JDBC Connection DEBUG - Created connection 10044878. DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce] DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? DEBUG - ==> Parameters: 1(Integer) 1|test1|19|beijing 1|test1|19|beijing DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce] DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? DEBUG - ==> Parameters: 2(Integer) 2|test2|18|guangzhou DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce] DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? DEBUG - ==> Parameters: 1(Integer) 1|test1|19|beijing DEBUG - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce] DEBUG - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce] DEBUG - Returned connection 10044878 to pool. DEBUG - Openning JDBC Connection DEBUG - Checked out connection 10044878 from pool. DEBUG - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce] DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce] DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? DEBUG - ==> Parameters: 1(Integer)
看以看出来,当参数不变的时候只进行了一次查询,参数变更以后,则需要重新进行查询,而清空缓存以后,参数相同的查询过的SQL也需要重新查询。
案例2.跟Spring集成的时候(使用mybatis-spring)
@Repository public class UserDao extends SqlSessionDaoSupport { public User selectUserById(int id) { SqlSession session = getSqlSession(); session.selectOne("dao.userdao.selectUserByID", id); // 由于session的实现是SqlSessionTemplate的动态代理实现 // 它已经在代理类内执行了session.close(),所以无需手动关闭session return session.selectOne("dao.userdao.selectUserByID", id); } }
DEBUG - Creating a new SqlSession DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e389b8] was not registered for synchronization because synchronization is not active DEBUG - Fetching JDBC Connection from DataSource DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? DEBUG - ==> Parameters: 1(Integer) DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e389b8] DEBUG - Returning JDBC Connection to DataSource DEBUG - Creating a new SqlSession DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@169da74] was not registered for synchronization because synchronization is not active DEBUG - Fetching JDBC Connection from DataSource DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? DEBUG - ==> Parameters: 1(Integer) DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@169da74] DEBUG - Returning JDBC Connection to DataSource
这里执行了2次sql查询,看似我们使用了同一个sqlSession,但是实际上因为我们的dao继承了SqlSessionDaoSupport,而SqlSessionDaoSupport内部sqlSession的实现是使用用动态代理实现的,这个动态代理sqlSessionProxy使用一个模板方法封装了select()等操作,每一次select()查询都会自动先执行openSession(),执行完close()以后调用close()方法,相当于生成了一个新的session实例,所以我们无需手动的去关闭这个session()(关于这一点见下面mybatis的官方文档),当然也无法使用mybatis的一级缓存,也就是说mybatis的一级缓存在spring中是没有作用的.
二级缓存
二级缓存就是global caching,它超出session范围之外,可以被所有sqlSession共享,它的实现机制和mysql的缓存一样,开启它只需要在mybatis的配置文件开启settings里的。
1. 默认不开启
2. 全局开关配置
<?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> <!-- 默认是true,如果它配成false,其余各个Mapper XML文件配成支持cache也没用 --> <setting name="cacheEnabled" value="true"/> </settings> </configuration>
3. 各个Mapper XML文件,默认是不采用cache。在配置文件加一行就可以支持cache
<mapper namespace="com.test"> <!-- eviction="FIFO" 回收策略为先进先出,(LRU,最近最少使用的算法) flushInterval="300000" 自动刷新时间5分钟 size="16" 最多缓存16个引用对象 readOnly="true" 只读 --> <cache eviction="FIFO" flushInterval="300000" size="16" readOnly="true" /> <select id=""></select> </mapper>
4. Mapper XML文件配置支持cache后,该文件中所有的Mapper statement就支持了。此时要个别sql不需要缓存的话,需要进行配置
<!-- useCache="false" 配置后则没有缓存--> <select id="check" parameterType="string" resultType="int" useCache="false"> </select>
案例
@RequestMapping("/getUser") public String getUser(Model model) { User user = userDao.selectUserById(1); model.addAttribute(user); return "index"; } }
当我们访问两次 /getUser 这个url,查看日志输出
DEBUG - Creating a new SqlSession DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659812] was not registered for synchronization because synchronization is not active DEBUG - Cache Hit Ratio [dao.userdao]: 0.0 DEBUG - Fetching JDBC Connection from DataSource DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? DEBUG - ==> Parameters: 1(Integer) DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659812] DEBUG - Returning JDBC Connection to DataSource DEBUG - Invoking afterPropertiesSet() on bean with name 'index' DEBUG - Rendering view [org.springframework.web.servlet.view.JstlView: name 'index'; URL [/index.jsp]] in DispatcherServlet with name 'dispatcher' DEBUG - Added model object 'org.springframework.validation.BindingResult.user' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'index' DEBUG - Added model object 'user' of type [bean.User] to request in view with name 'index' DEBUG - Forwarding to resource [/index.jsp] in InternalResourceView 'index' DEBUG - Successfully completed request DEBUG - Returning cached instance of singleton bean 'sqlSessionFactory' DEBUG - DispatcherServlet with name 'dispatcher' processing GET request for [/user/getUser] DEBUG - Looking up handler method for path /user/getUser DEBUG - Returning handler method [public java.lang.String controller.UserController.getUser(org.springframework.ui.Model)] DEBUG - Returning cached instance of singleton bean 'userController' DEBUG - Last-Modified value for [/user/getUser] is: -1 DEBUG - Creating a new SqlSession DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@539a92] was not registered for synchronization because synchronization is not active DEBUG - Cache Hit Ratio [dao.userdao]: 0.5 DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@539a92] DEBUG - Rendering view [org.springframework.web.servlet.view.JstlView: name 'index'; URL [/index.jsp]] in DispatcherServlet with name 'dispatcher' DEBUG - Added model object 'org.springframework.validation.BindingResult.user' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'index' DEBUG - Added model object 'user' of type [bean.User] to request in view with name 'index' DEBUG - Forwarding to resource [/index.jsp] in InternalResourceView 'index' DEBUG - Successfully completed request
可以看出第二次访问同一个url的时候相同的查询 hit cache了,这就是global cache的作用
总结
一级,二级缓存意义都不是很大
面对一定规模的数据量,内置的cache方式就派不上用场了
数据缓存在内存中,是增加的应用的压力
一般缓存采用Memcached、redis等