1. 程式人生 > >MyBatis原理淺析

MyBatis原理淺析

簡介

MyBatis是一個輕量級的ORM框架,它簡化了對關係資料庫的使用,開發人員可以在XML或註解中編寫SQL來完成對資料庫的操作。
如果完全使用XML方式,SQL語句可以集中維護,做到與Java程式碼完全隔離,便於對SQL調優。

原理及流程

  1. 載入配置:配置來源於兩個地方,一是配置檔案,一是Java程式碼的註解,將SQL的配置資訊載入成為一個個MappedStatement物件(包括了傳入引數對映配置、執行的SQL語句、結果對映配置),儲存在記憶體中。
  2. SQL解析:當API介面層接收到呼叫請求時,會接收到傳入SQL的ID和傳入物件(可以是Map、JavaBean或者基本資料型別),Mybatis會根據SQL的ID找到對應的MappedStatement,然後根據傳入引數物件對MappedStatement進行解析,解析後可以得到最終要執行的SQL語句和參。
  3. SQL執行:將最終得到的SQL和引數拿到資料庫進行執行,得到操作資料庫的結果。
  4. 結果對映:將操作資料庫的結果按照對映的配置進行轉換,可以轉換成HashMap、JavaBean或者基本資料型別,並將最終結果返回。

技術盞

反射、jdk動態代理、Ognl表示式引擎、快取

反射和動態代理

解析是將SQL或儲存過程定義表述為Mybatis中對應物件的過程,例如將執行的Sql標籤( , …)解析為 MappedStatement ;輸入引數定義標籤解析為 ParameterMap ;結果列定義標籤解析為 ResultMap

Mybatis支援將一個類的方法對映到一個 mapper 檔案裡的對應 statement sql,將方法名與DML SQL標籤的id對應起來,這樣我們就可以透明地使用 interface 的方式結合了面向物件的方式來與資料庫操作,這樣做更趨近於面向物件的程式設計風格。其中用到jdbc動態代理原理,用 MapperProxy 動態代理了需要執行的介面方法,主要代理邏輯在 MapperMethod 中實現,負責用介面的名稱以及方法名稱找到解析好的 MappedStatement 然後呼叫 SqlSession 中對應的執行邏輯執行。

快取

Mybatis的一級快取是SqlSession級別。第一次執行select時候會發現sqlsession快取沒有記錄,會去資料庫查詢,然後把結果儲存到快取,第二次同等條件查詢下,就會從快取中查詢到結果。另外為了避免髒讀,每次執行更新新增刪除時候會清空當前sqlsession快取。

二級快取是namespace級別的。同一個namespace下的搜尋語句共享一個二級快取。如果開啟了二級快取,則先從二級快取中查詢,查詢不到則委託為SimpleExecutor查詢,而它則會先從一級快取中查詢,查詢不到則從資料庫查詢。

mybaits的二級快取一般不怎麼使用,預設一級快取是開啟的。

基於註解方式的demo

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_name` varchar(100) DEFAULT NULL,
  `create_time` int(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.4.3.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
 </dependencyManagement>

 <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.1.1</version>
        </dependency>
 </dependencies>

application.properties

project.name=mybatis
server.port=7005
management.port=7006

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=123

logging.level.root=info

mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 我們不會將這個Bean進行例項化,class屬性中定義了PropertyPlaceholderConfigurer類,可以告訴Spring我們實際上要註冊一個properties檔案-->
    <bean id="propertyFileConfigForDB"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location">
            <value>classpath:application.properties</value>
        </property>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!-- 宣告式事物控制 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- spring和MyBatis完美整合,不需要mybatis的配置對映檔案 -->
    <bean id="sqlSessionFactor" class="org.mybatis.spring.SqlSessionFactoryBean" primary="true">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- DAO介面所在包名,Spring會自動查詢其下的類 ,包下的類需要使用@MapperScan註解,否則容器注入會失敗 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.test.dao.*"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactor"/>
    </bean>
</beans>

主類上添加註解 @ImportResource({“classpath:mybatis.xml”}),以引入配置mybatis.xml

public class UserDo {
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    private Long id;
    private String user_name;
    private Long create_time;
    private Integer age;

    public UserDo() {}

    public String getUser_name() {
        return user_name;
    }

    public void setUser_name(String user_name) {
        this.user_name = user_name;
    }

    public Long getCreate_time() {
        return create_time;
    }

    public void setCreate_time(Long create_time) {
        this.create_time = create_time;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}




@MapperScan
public interface UserMapper {
    @Insert("insert into user (user_name,create_time,age) values ("
            + "#{user_name},#{create_time},#{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    Long insertUser(UserDo userDo);

    @Delete("delete from user where id = #{id}")
    Long deleteUser( Long id);

    @Update("update user set user_name=#{user_name}, create_time=#{create_time},"
      + " age=#{age} where id=#{id}")
    Long updateUser(UserDo userDo);

    @Select("select * from user where id=#{id}")
    UserDo selectUserById(Long id);
}



public interface UserService {
    Long createUser(User user);
    Long deleteUser(Long id);
    Long updateUser(User user);
    User selectUser(Long id);
}



@Service
public class UserServiceImpl implements UserService {
    private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Resource
    private UserMapper userMapper;

    @Override
    public Long createUser(User user) {
        try {
            return userMapper.insertUser(new UserDo(user));
        } catch (Exception ex) {
            logger.error("exception occurred: {}", ex);
        }

        return null;
    }

    @Override
    public Long deleteUser(Long id) {
        try {
            return userMapper.deleteUser(id);
        } catch (Exception ex) {
            logger.error("exception occurred: {}", ex);
        }
        return null;
    }

    @Override
    public Long updateUser(User user) {
        try {
            return userMapper.updateUser(new UserDo(user));
        } catch (Exception ex) {
            logger.error("exception occurred: {}", ex);
        }
        return null;
    }

    @Override
    public User selectUser(Long id){
        try {
            UserDo userDo =  userMapper.selectUserById(id);
            return new User(userDo);
        } catch (Exception ex) {
            logger.error("exception occurred: {}", ex);
        }
        return null;
    }
}

參考資料