Mybatis原理及源碼分析


什么是Mybatis?

  Mybatis是一個半自動化的持久層框架。

  Mybatis可以將向PreparedStatement中的輸入參數自動進行映射(輸入映射),將結果集映射成Java對象(輸出映射)

為什么使用Mybatis?

  JDBC:

    SQL夾雜在Java代碼塊中,耦合度高導致硬編碼

    維護不易且實際開發需求中SQL有變化,頻繁修改的情況多見

  Hibernate和JPA:

    長難復雜SQL,對於Hibernate而言處理也不容易

    內部自動生成的SQL,不容易做特殊優化

    基於全映射的全自動框架,大量字段的POJO進行部分映射時比較苦難,導致數據庫性能下降

而實際開發中,對開發人員而言,核心SQL還是需要自己優化,而Mybatis中SQL和Java代碼分開,功能邊界清晰,一個專注業務,一個專注數據

 

配置文件,mybatis-config.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>
    <properties resource=""></properties><!--加載配置文件-->
    <settings>
        <!--開啟二級緩存,默認開啟-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <typeAliases>

        <!--設置單個pojo別名-->
        <!--<typeAlias alias="Employee" type="com.yang.domain.Employee"/>-->
        <!--對整個包下的pojo設置別名,別名為類名,如果類上使用了@Alias("")注解指定了別名則用注解設置的-->
        <package name="com.yang.domain"/>
    </typeAliases>
    <!--與Spring整合后,environment配置將廢除-->
    <environments default="development">
        <environment id="development">
            <!--使用jdbc事務管理,由mybatis自己管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--數據庫連接池,由mybatis自己管理-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="yang"/>
                <property name="password" value="yang"/>
            </dataSource>
        </environment>
    </environments>
    <!--我們寫的sql映射文件-->
    <mappers>
        <mapper resource="mybatis/xxxMapper.xml"/>
    </mappers>
</configuration>

 logback.xml,打印sql

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定義日志文件的存儲地址 勿在 LogBack 的配置中使用相對路徑-->
    <property name="LOG_HOME" value="/logback/LogFile"/>
    <!--控制台輸出-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS}|%thread|%-5level|%r|%X{threadId}|%C|%msg%n</pattern>
        </encoder>
    </appender>
    <!--sql相關-->
    <logger name="java.sql">
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

 簡單的Mybatis操作數據庫步驟:

  1:創建Mybatis全局配置文件,包含了影響Mybatis行為的設置(setting)和屬性(properties)信息、如數據庫連接池信息等

  2:創建SQL映射文件,映射文件的作用就相當於是定義Dao接口的實現類如何工作

  3:將sql映射文件注冊到全局配置中

  4:持久化代碼

    1):根據全局配置文件得到SqlSessionFactory

    2):使用SqlSessionFactory,獲取到SqlSession對象使用它來執行增刪改查,一個SqlSession就是代表和數據庫的一次會話,用完則關閉

    3):使用sql的唯一標志,namespace+id,執行sql

String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 1、獲取sqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2、獲取sqlSession對象
        SqlSession openSession = sqlSessionFactory.openSession();
        try {
            // 3、獲取接口的實現類對象
            //會為接口自動的創建一個代理對象,代理對象去執行增刪改查方法
            Employee employee = (Employee) openSession.selectOne(
                    "com.atguigu.mybatis.EmployeeMapper.selectEmp", 1);
        } finally {
            openSession.close();
        }

第二種方式,接口式編程

  xxxMapper.xml中的namespace設置為xxxMapper接口的全路徑名,Mybatis會為Mapper接口創建一個代理對象

  使用接口式編程會有更強的類型檢查,參數控制等

 String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 1、獲取sqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2、獲取sqlSession對象
        SqlSession openSession = sqlSessionFactory.openSession();
        try {
            // 3、獲取接口的實現類對象
            //會為接口自動的創建一個代理對象,代理對象去執行增刪改查方法
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            Employee employee = mapper.getEmpById(1);
        } finally {
            openSession.close();
        }

 SqlSession,需要注意:

  1、SqlSession的實例不是線程安全的,因此是不能被共享的

  2、SqlSession每次使用完成后需要正確關閉,這個關閉操作是必須的

  3、SqlSession可以直接調用方法的id進行數據庫操作,不過一般推薦使用SqlSession獲取到Dao接口的代理類,執行代理對象的方法,可以更安全的進行類型檢查操作

 

代理Mapper執行方法的源碼:

1、JDK動態代理創建Mapper的代理類 

    public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
        ClassLoader classLoader = mapperInterface.getClassLoader();
        Class<?>[] interfaces = new Class[]{mapperInterface};
        MapperProxy proxy = new MapperProxy(sqlSession);
        return Proxy.newProxyInstance(classLoader, interfaces, proxy);
    }

2、代理類執行方法

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        } else {
            Class<?> declaringInterface = this.findDeclaringInterface(proxy, method);
            MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, this.sqlSession);
            Object result = mapperMethod.execute(args);
            if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
                throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
            } else {
                return result;
            }
        }
    }

execute:

    public Object execute(Object[] args) {
        Object result = null;
        Object param;
    // 判斷執行sql類型,insert,update、delete或select,然后封裝參數,調用的還是sqlSession的增刪改查方法
if (SqlCommandType.INSERT == this.type) { param = this.getParam(args); result = this.sqlSession.insert(this.commandName, param); } else if (SqlCommandType.UPDATE == this.type) { param = this.getParam(args); result = this.sqlSession.update(this.commandName, param); } else if (SqlCommandType.DELETE == this.type) { param = this.getParam(args); result = this.sqlSession.delete(this.commandName, param); } else { if (SqlCommandType.SELECT != this.type) { throw new BindingException("Unknown execution method for: " + this.commandName); } if (this.returnsVoid && this.resultHandlerIndex != null) { this.executeWithResultHandler(args); } else if (this.returnsList) { result = this.executeForList(args); } else if (this.returnsMap) { result = this.executeForMap(args); } else { param = this.getParam(args); result = this.sqlSession.selectOne(this.commandName, param); } } return result; }

getParam方法:

 

 

Mybatis的緩存:

  Mybatis提供查詢緩存,用於減輕數據庫壓力,提高數據庫性能,Mybatis提供一級緩存,二級緩存

  一級緩存(粒度小):SqlSession級別的緩存,在操作數據庫時需要構造SqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據,不同的SqlSession之間的緩

    存數據區域是互相不影響的。

    當第一次發起查詢請求時,先去緩存中查找有沒有符合的信息,如果沒有,就從數據庫中去查,然后將結果信息存儲到一級緩存中

    如果SqlSession執行了Commit操作(插入、刪除、更新)等,將清空SqlSession中的一級緩存,為了讓緩存中的信息是最新信息,避免臟讀,Mybatis默認是支持一級緩存的,

    關掉一級緩存的話需要在配置文件中配置。

    SqlSession關閉,一級緩存就清空

    應用:將Mybatis和Spring整合開發,事務控制是在service中,開始執行時,開啟事務,創建SqlSession對象。

      在service方法內第一次調用,第二次調用將從一級緩存中取數據,方法結束,SqlSession關閉

      如果執行了兩次service方法調用查詢相同的信息,不走一級緩存,因為service方法結束,SqlSession就關閉了,一級緩存也隨之清空

    

  二級緩存(粒度大):Mapper級別的緩存,多個SqlSession去操作同一個mapper sql語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。

    多個SqlSession共享一個Mapper的二級緩存區域,按照namespace區分,每個mapper有都按自己的namespace區分的緩存區域。二級緩存默認是也是開啟的

    開啟二級緩存:

      1):在mybatis-config.xml配置文件的setting標簽中設置二級緩存的開關

      2):在每個具體的mapper.xml文件中開啟二級緩存

      3):調用pojo類實現序列化接口,因為為了將緩存數據取出執行反序列操作,因為二級緩存存儲介質多種多樣,不一定在內存

    禁用緩存:

      在每個select標簽中設置useCache="false",如果想要針對每次查詢都需要最新的數據,則需要設置禁用二級緩存

    

Mybatis和Spring整合:

  1)、需要Spring通過單例方式管理SqlSessionFactory,注入org.mybatis.spring.SqlSessionFactoryBean指定mybatis配置文件地址、dataSource、mapper.xml、別名等

  2)、Spring和Mybatis整合生成代理對象,使用SqlSessionFactory創建Session(整合自動完成),提供SqlSessionTemplate

  3)、持久層的mapper都需要由Spring進行管理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM