MyBatis缓存分为一级缓存和二级缓存java
MyBatis的一级缓存指的是在一个Session域内,session为关闭的时候执行的查询会根据SQL为key被缓存(跟mysql缓存同样,修改任何参数的值都会致使缓存失效)mysql
1)单独使用MyBatis而不继承Spring,使用原生的MyBatis的SqlSessionFactory来构造sqlSession查询,是可使用以及缓存的,示例代码以下git
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)); } }
输出以下github
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|beijingweb
看以看出来,当参数不变的时候只进行了一次查询,参数变动之后,则须要从新进行查询,而清空缓存之后,参数相同的查询过的SQL也须要从新查询,并且使用的数据库链接是同一个数据库链接,这里要得益于咱们在mybatis-config.xml里面的datasource设置算法
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments>
注意datasource使用的是POOLED,也就是使用了链接池,因此数据库链接可回收利用,固然这个environment属性在集成spring的时候是不须要的,由于咱们须要另外配置datasource的bean.spring
2) 跟Spring集成的时候(使用mybatis-spring)sql
直接在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); } }
观察日志apache
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)里开启
<cache />
以上全部的属性均可以经过<cache />
元素来修改:
<mapper namespace="dao.userdao"> ... select statement ... <!-- Cache 配置 --> <cache eviction="FIFO"// 回收策略 flushInterval="60000"// 刷新间隔 size="512" // 引用数目 readOnly="true" // 只读
/> </mapper>
以上代表首先建立了一个先进先出策略的缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,并且返回的对象被认为是只读的,所以在不一样线程中的调用者之间修改它们会致使冲突。
默认的是LRU。
属性flushInterval(刷新间隔)能够被设置为任意的正整数,并且它们表明一个合理的毫秒形式的时间段。默认状况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
属性size(引用数目)能够被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
属性readOnly(只读)属性能够被设置为true 或 false。只读的缓存会给全部调用者返回缓存对象的相同实例。所以这些对象不能被修改。这提供了很重要的性能优点。可读写的缓存会返回缓存对象的拷贝(经过序列化) 。这会慢一些,可是安全,所以默认是false。
须要注意的是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的做用。
<select />
标签中关于缓存的使用属性
flushCache:将其设置为 true,任什么时候候只要语句被调用,都会致使本地缓存和二级缓存都会被清空,默认值:false。
useCache:将其设置为 true,将会致使本条语句的结果被二级缓存,默认值:对 select 元素为 true。若是设置为false,那么即便启用了<cache />
该条select的结构也不会被二级缓存。
<select ... flushCache="false" useCache="true"/> <insert ... flushCache="true"/> <update ... flushCache="true"/> <delete ... flushCache="true"/>
若是数据缓存在本地,另外一个系统修改数据库或者手动修改数据库时,会出现脏数据问题。
一级缓存
Myatis的一级缓存默认为SESSION,底层用PerpetualCache,里面使用map作为存储,并无作太多条件限制。
二级缓存
MyBatis虽然全局配置开启缓存,可是仍是取决因而否使用了<cache/>
标签,若是使用了二级缓存,须要注意:
每一个<cache />
表明一个单独的二级缓存,若是多个Mapper须要共享同一个二级缓存,就须要使用<cache-ref/>。
若是一个Mapper中查询数据时,使用了多表联查,当另外一个Mapper更新相关数据时,若是没有共享一个Cache,那么下一次该Mapper查询时,就会出现读到脏数据。
使用二级缓存通常基于如下原则:
不常常变更的数据,但常常会使用
数据量比较大,系统多处会用到。或者跨系统用。
对性能有特别要求的地方。
滥用二级缓存,有可能反而会下降性能,特别是根据条件查询缓存。
<cache />
属性的组装。经过MapperBuilderAssistant类调用CacheBuilder进行具体的装配顺序操做。