1. 程式人生 > >MyBatis 緩存

MyBatis 緩存

spa apach setting println clas throws remember dao exce

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 [[email protected]]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
1|test1|19|beijing
1|test1|19|beijing
DEBUG - ooo Using Connection [[email protected]

/* */]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 2(Integer)
2|test2|18|guangzhou
DEBUG - ooo Using Connection [[email protected]]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
1|test1|19|beijing
DEBUG - Resetting autocommit to true on JDBC Connection [[email protected]
/* */]
DEBUG - Closing JDBC Connection [[email protected]]
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 [[email protected]]
DEBUG - ooo Using Connection [[email protected]]
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">

            </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.

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 [[email protected]] 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, [email protected], MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, [email protected], MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [[email protected]]
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Creating a new SqlSession
DEBUG - SqlSession [[email protected]] 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, [email protected], MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, [email protected], MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [[email protected]]
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 [[email protected]] 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, [email protected], MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, [email protected], MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [[email protected]]
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 [[email protected]] was not registered for synchronization because synchronization is not active
DEBUG - Cache Hit Ratio [dao.userdao]: 0.5
DEBUG - Closing non transactional SqlSession [[email protected]]
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

MyBatis 緩存