mybatis源碼考究一

mybatis源碼考究一

1.項目結構

在這裏插入圖片描述

2.mybatis使用步驟介紹

  • 獲取SqlSessionFactory

    • 這裏面就會用到全局配置,本文的con1.xml
  • 使用SqlSessionFactory打開sqlSession

  • 使用sqlSession操作數據庫

3.快速入門

1.構建SqlSessionFactory官網介紹

  • 使用xml構建

    static SqlSessionFactory sqlSessionFactory = null;
        static{
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            try {
                sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis/con1.xml"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
  • 不使用xml構建

    static SqlSessionFactory sqlSessionFactory = null;
        static{
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            //構建配置類
            Configuration configuration = new Configuration();
            //數據源
            DataSource dataSource = new PooledDataSource("com.mysql.cj.jdbc.Driver","jdbc:mysql://localhost:3306/no_name","root","root");
            //事務類型
            JdbcTransactionFactory jdbcTransaction = null;
            jdbcTransaction = new JdbcTransactionFactory();
            //新建環境,就是settings裏面的配置
            Environment dev = new Environment("dev", jdbcTransaction, dataSource);
            configuration.setEnvironment(dev);
            //添加接口文件
            configuration.addMapper(UserMapper.class);
    
            //這個類的作用暫時研究不多,看名字就知道對拍錯有幫助
            ErrorContext.instance().resource("mybatis/UserMapper.xml");
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream("mybatis/UserMapper.xml");
            } catch (IOException e) {
                e.printStackTrace();
            }
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, "mybatis/UserMapper.xml", configuration.getSqlFragments());
            //添加xml,有時候需要結合註解和xml
            mapperParser.parse();
            //添加攔截器
            configuration.addInterceptor(new MyInterceptor1());
            configuration.addInterceptor(new MybatisOptimisticInterceptor());
            //構建
            sqlSessionFactory = sqlSessionFactoryBuilder.build(configuration);
        }
    

2.獲取sqlsession

public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }

    public static SqlSession getSessionByType(ExecutorType execType){
        return sqlSessionFactory.openSession(execType);
    }

    public static SqlSession getSessionByTypeAndAutoCommit(ExecutorType execType,boolean autoCommit){
        return sqlSessionFactory.openSession(execType,autoCommit);
    }

3.使用案例

/**
     * 新增測試
     * @param user
     * @return
     */
    public int insert(User user){
        SqlSession session = MybatisSessionUtil.getSession();
        int rint = session.insert("user.insertUser",user); // 第一個參數是mapper xml裏的namespace+MappedStatement對應的id
        session.commit();// 不要忘記提交
        return rint;
    }

	//測試使用xml構建的
    @Test
    public void testXml() {
        UserTest1 userDao = new UserTest1();
        User user = new User();
        user.setName("wwwttt");
        user.setPassword("33333");
        if (userDao.insert(user) == 1) {
            System.out.println("insert success...");
        }else{
            System.out.println("insert fail...");
        }

    }

	//測試不使用xml構建的
	@Test
    public void testAnnoAndXml() {

        SqlSession session = AnnoTest.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User userById = mapper.getUserById(1);
        userById.setId(null);
        userById.setName("渣渣輝");
        mapper.insertUser(userById);
        session.commit();
    }

4.補全上面的幾個xml

​ 1.con1.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>
        &lt;!&ndash; 這個可以用log4j &ndash;&gt;
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>-->
    <!-- 和Spring整合後environment配置都會被幹掉 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事務管理,目前由mybatis來管理 -->
            <transactionManager type="JDBC" />
            <!-- 數據庫連接池,目前由mybatis來管理 -->
            <dataSource type="POOLED"><!--有關於mysql數據庫的各種信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/no_name" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--將操作配置文件User.xml系添加進mapper-->
        <mapper resource="mybatis/user.xml" />
        <!--<mapper class="mybatis/user.xml" />
        <mapper url="mybatis/user.xml" />-->
        <!--<package name=""/>-->
    </mappers>
</configuration>

2.user.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>
        &lt;!&ndash; 這個可以用log4j &ndash;&gt;
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>-->
    <!-- 和Spring整合後environment配置都會被幹掉 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事務管理,目前由mybatis來管理 -->
            <transactionManager type="JDBC" />
            <!-- 數據庫連接池,目前由mybatis來管理 -->
            <dataSource type="POOLED"><!--有關於mysql數據庫的各種信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/no_name" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--將操作配置文件User.xml系添加進mapper-->
        <mapper resource="mybatis/user.xml" />
        <!--<mapper class="mybatis/user.xml" />
        <mapper url="mybatis/user.xml" />-->
        <!--<package name=""/>-->
    </mappers>
</configuration>

3.UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mybatis.UserMapper">

    <!--插入用戶信息-->
    <insert id="insertUser" parameterType="com.qdz.entity.User">
        <!--<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
            select uuid()
            &lt;!&ndash;            這裏是對於主鍵屬性的id進行賦值 &ndash;&gt;
        </selectKey>-->
        insert into user(name,password) values(#{name},#{password})
    </insert>
    <select id="getUserListPageByIds" resultType="com.qdz.entity.User">
       select * from user where id in
       <foreach collection="ids" item="id" separator="," open="(" close=")">
           #{id}
       </foreach>
    </select>

    <update id="updateUsers1">
        update user set name='夏侯惇',password='54321',create_time=now() where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </update>



    <update id="updateUsers2">
        update user set name='姜維',password='54321',create_time=now() where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </update>



</mapper>

4.大致執行流程(本人debug出來,以查詢爲例)

import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionManager;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;

import javax.sql.DataSource;

/**
 * mybatis查詢過程
 * @author authorZhao
 * @date 2020年05月23日
 */
public class MyBatisQueryDetail {

    /**
     * 1.構建{@link SqlSessionFactory}
     *      -取配置文件 Configuration
     *          {@link Configuration}裏面包含了所有
     *                 - {@link Environment} 這就是setting連的 Environment配置
     *                      - {@link TransactionFactory} 事務
     *                      - {@link DataSource} 數據源
     *                 -{@link Configuration#logImpl} 日誌實現
     *                 -{@link Configuration#defaultExecutorType} 執行器類型,默認defalut
     *                 -{@link Configuration#mapperRegistry} mapper註冊器
     *                 -{@link Configuration#interceptorChain} 攔截器鏈
     *                 -{@link Configuration#mappedStatements} 就是每一個具體的mapper,包含.java和.xml的
     *      - 構建{@link SqlSessionFactory}
     *          ->{@link DefaultSqlSessionFactory}
     *          ->{@link SqlSessionManager}
     *
     *
     * 2.獲取sqlSession{@link SqlSessionFactory#openSession()}
     *      ->{@link DefaultSqlSessionFactory#openSessionFromDataSource(org.apache.ibatis.session.ExecutorType, org.apache.ibatis.session.TransactionIsolationLevel, boolean)}
     *              ->{@link Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)}
     *                  -> 這裏面根據執行器類型創建執行器,然後執行執行器的方法 interceptorChain.pluginAll(executor)
     *
     * 3.獲取mapper/selectone
     *  ->{@link DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)}
     *              ->{@link SimpleExecutor#doQuery(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.mapping.BoundSql)}
     *                      ->{@link Configuration#newStatementHandler(org.apache.ibatis.executor.Executor, org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.mapping.BoundSql)}
     *                      -> 上一步也有一個 pluginAll方法
     *                      ->{@link org.apache.ibatis.executor.SimpleExecutor#prepareStatement(org.apache.ibatis.executor.statement.StatementHandler, org.apache.ibatis.logging.Log)}
     *                              -> 默認實現 RoutingStatementHandler,會構建PreparedStatement
     *                              -{@link StatementHandler},構建這個的時候會構建  ParameterHandler、 ResultSetHandler
     *                                      ->{@link Configuration#newParameterHandler(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.mapping.BoundSql)}
     *                                      ->{@link Configuration#newResultSetHandler(org.apache.ibatis.executor.Executor, org.apache.ibatis.mapping.MappedStatement, org.apache.ibatis.session.RowBounds, org.apache.ibatis.executor.parameter.ParameterHandler, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.mapping.BoundSql)}
     *                              ->{@link PreparedStatementHandler#query(java.sql.Statement, org.apache.ibatis.session.ResultHandler)}
     *                  ->{@link ResultSetHandler#handleResultSets(java.sql.Statement)}
     *
     *
     *
     */

    /**
     *
     * 攔截器四大接口
     * 1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 攔截執行器的方法;
     * 4.StatementHandler (prepare, parameterize, batch, update, query) 攔截Sql語法構建的處理
     * 2.ParameterHandler (getParameterObject, setParameters) 攔截參數的處理;
     * 3.ResultSetHandler (handleResultSets, handleOutputParameters) 攔截結果集的處理;
     */

}

5.單機mybatis常見問題(單獨使用情況)

  • sqlsession線程安全問題

    關於sqlsession線程的安全先行問題

    /**
         * 測試多線程插入時的sqlsession線程安全問題
         */
        @Test
        public void testThread(){
            ExecutorService executorService = Executors.newCachedThreadPool();
            SqlSession session = AnnoTest.getSession();
            List<Future> futures = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                int j = i;
                //如果在此獲取sessionn那麼每個線程都是單獨的sqlsession
                Future<?> submit = executorService.submit(() -> save(j,session));
                futures.add(submit);
            }
            while(futures.stream().anyMatch(i -> i.isDone() == false)){
                try {
                    Thread.currentThread().sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
        private void save(int i,SqlSession session) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User userById = mapper.getUserById(1);
            userById.setId(null);
            userById.setName("渣渣輝"+i+":  "+Thread.currentThread().getName());
            mapper.insertUser(userById);
            session.commit();
        }
    
  • mybatis緩存

    • 1.一級緩存是sqlsession級別

      同一個sqlsession執行同樣的sql是有緩存的,如果執行了commit、flush、close 之後 會失效

    • 2.二級緩存是namespace級別的,(開發中從未使用),不做研究

  • sql解析

    這個其實也是mysql的精髓之一,在xml裏面寫出不可維護的sql,外加一堆動態標籤,你無可替代

    這個未研究,xml解析也爲研究

  • 其他使用問題

    • #{}和${}區別,前者在sql出來之後變成了?後者直接替代了
    • 參數接收,mybatis對參數默認採用param1、param2這樣的命名,存在map裏面
    • 什麼對多對一映射略 對多分頁參考
    • mybatis的日誌實現,提供了幾種常見的,項目裏面有就會適配

6.結束

參考文章:文中demo的配置抄襲自網上,現在忘記那個了,主題流程來自mybatis官網

本文代碼github地址本文代碼地址

下一篇會結合spring開發使用,重點分析問題如下

1.spring如何解決sqlsession線程安全問題

2.spring裏面一級緩存爲何失效的問題

3.spring如何接管事務

4.分頁插件和sql打印

5.springboot自動配置和mybatisPlus的單表CRUD實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章