MyBatis 缓存
MyBatis 缓存分为一级缓存和二级缓存
一级缓存
MyBatis 的一级缓存指的是在一个 Session 域内,session 为关闭的时候执行的查询会根据 SQL 为 key 被缓存 (跟 mysql 缓存一样, 修改任何参数的值都会导致缓存失效)
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)
1|test1|19|beijing
看以看出来, 当参数不变的时候只进行了一次查询, 参数变更以后, 则需要重新进行查询, 而清空缓存以后, 参数相同的查询过的 SQL 也需要重新查询, 而且使用的数据库连接是同一个数据库连接, 这里要得益于我们在 mybatis-config.xml 里面的 datasource 设置
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"><span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">transactionManager</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">dataSource </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="POOLED"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="driver"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="com.mysql.jdbc.Driver"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="url"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="username"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="root"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="password"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="root"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">dataSource</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">environment</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">environments</span><span style="color: rgba(0, 0, 255, 1)">></span></pre>
注意 datasource 使用的是 POOLED, 也就是使用了连接池, 所以数据库连接可回收利用, 当然这个 environment 属性在集成 spring 的时候是不需要的, 因为我们需要另外配置 datasource 的 bean.
2)跟 Spring 集成的时候 ( 使用 mybatis-spring)
直接在 dao 里查询两次同样参数的 sql
@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 中是没有作用的.
官方文档摘要
MyBatis SqlSession provides you with specific methods to handle transactions programmatically. But when using MyBatis-Spring your beans will be injected with a Spring managed SqlSession or a Spring managed mapper. That means that Spring will always handle your transactions.
You cannot call SqlSession.commit(), SqlSession.rollback() or SqlSession.close() over a Spring managed SqlSession. If you try to do so, a UnsupportedOperationException exception will be thrown. Note these methods are not exposed in injected mapper classes.
二级缓存
二级缓存就是 global caching, 它超出 session 范围之外, 可以被所有 sqlSession 共享, 它的实现机制和 mysql 的缓存一样, 开启它只需要在 mybatis 的配置文件开启 settings 里的
<setting name="cacheEnabled" value="true"/>
以及在相应的 Mapper 文件 (例如 userMapper.xml) 里开启
<mapper namespace="dao.userdao"> ... select statement ... <!-- Cache 配置 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" /> </mapper>
需要注意的是 global caching 的作用域是针对 Mapper 的 Namespace 而言的, 也就是说只在有在这个 Namespace 内的查询才能共享这个 cache. 例如上面的 dao.userdao namespace, 下面是官方文档的介绍
It's important to remember that a cache configuration and the cache instance are bound to the namespace of the SQL Map file. Thus, all statements in the same namespace as the cache are bound by it.
例如下面的示例, 我们执行两次对同一个 sql 语句的查询, 观察输出日志
@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 的作用
The End