spring + mybatis實現數據庫CRUD操作原理

事務

數據庫事務

  • 事務:邏輯上的多條sql的分組。該分組內的事務要麼全部成功(commit),要麼全部失敗(rollback)。
  • 數據庫默認自動提交事務,即發一條sql就執行一條。
  • 單條sql語句不存在事務概念。
  • mysql事務語法:
start transaction
...
...
commit/rollback
- start transaction:開啓事務
- rollback:回滾事務
- commit:提交事務

ACID

  • 原子性:事務內的一組sql,要麼全部成功,要麼全部失敗
  • 一致性:事務執行之前和執行之後狀態一致,e.g.拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
  • 隔離性:多個併發事務之間相互隔離,感知不到其他事務的存在
  • 持久性:事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即便是在數據庫系統遇到故障的情況下也不會丟失提交事務的操作

隔離級別

沒有隔離可能會出現的錯誤

  • 髒讀:A事務讀到B事務未提交的更改數據
  • 不可重複讀:A事務讀數據Data爲狀態S1, B事務將Data狀態更新爲S2並提交。A事務再次讀Data,Data的狀態
  • 幻讀

JDBC事務

  • jdbc從數據庫獲取connection,默認情況下connection會自動向數據庫提交它發送的sql。
  • jdbc事務流程:
// 關閉自動提交,相當於mysql的start transaction
connection.setAutoCommit(false); 
...
...
connection.commit()/rollback();

Spring事務

spring結合mybatis配置

  • spring數據庫配置
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
      p:basePackage="com.cmbchina.ccd.pluto.babylon.*.dao"/>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
      p:dataSource-ref="dataSource"
      p:configLocation="classpath:mybatis-config.xml"/>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
      p:dataSource-ref="dataSource"/>

<tx:annotation-driven transaction-manager="transactionManager"/>
  • 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>
    <!--<settings>-->
    <!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
    <!--</settings>-->

    <!-- 注入別名,便於mapper書寫 -->
    <typeAliases>
        <package name="com.cmbchina.ccd.pluto.babylon.deploy.model"/>
        <package name="com.cmbchina.ccd.pluto.babylon.deploy.vo"/>
        <package name="com.cmbchina.ccd.pluto.babylon.resource.model"/>
        <package name="com.cmbchina.ccd.pluto.babylon.resource.vo"/>
    </typeAliases>

    <!--
        plugins在配置文件中的位置必須符合要求,否則會報錯,順序如下:
        properties?, settings?,
        typeAliases?, typeHandlers?,
        objectFactory?,objectWrapperFactory?,
        plugins?,
        environments?, databaseIdProvider?, mappers?
    -->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- 4.0.0以後版本可以不設置該參數 -->
            <property name="dialect" value="sqlserver"/>
            <!-- 該參數默認爲false -->
            <!-- 設置爲true時,會將RowBounds第一個參數offset當成pageNum頁碼使用 -->
            <!-- 和startPage中的pageNum效果一樣-->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 該參數默認爲false -->
            <!-- 設置爲true時,使用RowBounds分頁會進行count查詢 -->
            <property name="rowBoundsWithCount" value="true"/>
            <!-- 設置爲true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結果 -->
            <!-- (相當於沒有執行分頁查詢,但是返回結果仍然是Page類型)-->
            <property name="pageSizeZero" value="true"/>
            <!-- 3.3.0版本可用 - 分頁參數合理化,默認false禁用 -->
            <!-- 啓用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最後一頁 -->
            <!-- 禁用合理化時,如果pageNum<1或pageNum>pages會返回空數據 -->
            <property name="reasonable" value="false"/>
            <!-- 支持通過Mapper接口參數來傳遞分頁參數 -->
            <property name="supportMethodsArguments" value="false"/>
            <!-- always總是返回PageInfo類型,check檢查返回類型是否爲PageInfo,none返回Page -->
            <property name="returnPageInfo" value="none"/>
        </plugin>
    </plugins>
</configuration>

mybatis技術內幕

SqlSession與SqlSessionFactory(https://my.oschina.net/zudajun/blog/665956)

  • SqlSessionFactory: SqlSession工廠bean,singleton,在spring啓動時載入mybatis全局配置文件,創建SqlSessionFactory。
  • SqlSession: 程序與數據庫交互的一次會話,SqlSession封裝數據庫增刪改查及事務方法。
  • SqlSession生命週期:
    • 開啓spring事務:一個事務共用一個SqlSession
    • 未開啓spring事務:調用一次mybatis方法一個SqlSession

動態代理,自動映射Mapper的底層實現原理(https://my.oschina.net/zudajun/blog/666223)

  • Mybatis中聲明一個interface接口,沒有編寫任何實現類,Mybatis就能返回接口實例,並調用接口方法返回數據庫數據

實現原理:動態代理

  • 動態代理實現sql映射例子
/**
 * <i>invocation handler</i> of a proxy instance
 * Created by z673414 on 2018/6/29.
 *
 * @author z673414
 */
public class MapperProxy implements InvocationHandler {
    public static void main(String[] args) {
        MapperProxy mapperProxy = new MapperProxy();
        UserMapper userMapper = mapperProxy.newInstance(UserMapper.class);
        User user = userMapper.getUserById(1);

        System.out.println(user);
    }

    /**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                // 諸如hashCode()、toString()、equals()等方法,將target指向當前對象this
                return method.invoke(this, args);
            } catch (Exception t) {
                t.printStackTrace();
            }
        }

        return new User(1, "xiaoming");
    }

    /**
     * 創建proxy對象
     *
     * @param clz
     * @param <T>
     * @return
     */
    public <T> T newInstance(Class<T> clz) {
        return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{clz}, this);
    }
}
  • mybatis動態代理的實現
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    ......
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 調用sqlSession執行CRUD操作
    return mapperMethod.execute(sqlSession, args); 
  }

  private MapperMethod cachedMapperMethod(Method method) {
    ......
  }

}
- spring事務執行:


- Spring創建MapperScannerConfigurer bean,searches recursively starting from a base package for interfaces and registers them as {@code MapperFactoryBean},將sqlSessionFactory注入每個mapperFactoryBean
- MapperFactoryBean: BeanFactory that enables injection of MyBatis mapper interfaces
    - getSqlSession().getConfiguration().addMapper(this.mapperInterface)
- Spring創建SqlSessionFactory bean,解析配置文件生成Configuration對象
    - Configuration生成MapperRegistry對象
        - MapperRegistry getMapper()生成mapperProxyFactory
            - MapperProxyFactory newInstance()生成mapperInterface的代理對象,用於調用mybatis方法,執行數據庫CRUD操作
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章