mybatis的部署和加載詳解

      MyBatis是支持普通SQL查詢,存儲過程和高級映射的優秀持久層框架。MyBatis消除了幾乎所有的JDBC代碼和參數的手工設置以及結果集的檢索。MyBatis使用簡單的XML或註解用於配置和原始映射,將接口和Java的POJOs(Plan Old Java Objects,普通的Java對象)映射成數據庫中的記錄。Mybatis的功能架構分爲三層(圖片借用了百度百科):

1)API接口層:提供給外部使用的接口API,開發人員通過這些本地API來操縱數據庫。接口層一接收到調用請求就會調用數據處理層來完成具體的數據處理。

2)數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次數據庫操作。

3)基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來作爲最基礎的組件。爲上層的數據處理層提供最基礎的支撐。

如圖所示:

 

以在maven管理的項目架構下創建工程爲例,在pom.xml中引入相關的jar包:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.j1.mybatis</groupId>
  <artifactId>mybatis-demo</artifactId>
  <version>1.0.0-SNAPSHOT</version> 
      <parent>
        <groupId>cn.j1.parent</groupId>
        <artifactId>j1-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <dependencies>
        <!-- MySql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- 單元測試 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>
        <!-- Mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>
    </dependencies>
</project>
上述依賴是調用了java後臺和MySQL數據庫連接的封裝依賴、通信和日誌記錄依賴以及mybatis封裝依賴等,接下來創建一個簡單的JDBC數據庫連接代碼
package com.j1.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class JdbcTest {
    public static void main(String[] args) {
        // 數據庫連接
        Connection con = null;
        // 執行sql
        ResultSet res = null;
        // 封裝sql
        PreparedStatement pre = null;
        // 加載驅動
        try {
            Class.forName("com.mysql.jdbc.Driver");
            // 創建連接
            // 創建連接
            String url = "jdbc:mysql://127.0.0.1:3306/mybatis";
            String username = "root";
            String password = "root";
            con = DriverManager.getConnection(url, username, password);

            // 獲取PreparedStatement對象
            String sql = "select * from tb_user u  where  u.user_name=?";
            pre = con.prepareStatement(sql);

            // 封裝查詢的參數
            pre.setString(1, "wangwu");
            // 執行
            res = pre.executeQuery();
            // 打印結果集,
            while (res.next()) {
                System.out.println("username : " + res.getString("user_name"));
                System.out.println("name : " + res.getString("name"));
                System.out.println("age : " + res.getInt("age"));
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                // 釋放資源
                if (pre != null) {
                    pre.close();
                }
                if (res != null) {
                    res.close();
                }
                if (con != null) {
                    con.close();
                }

            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }

}
class.forName()函數入參是一個類,目的是要求JVM查找並加載入參指定的類,也就是說JVM會執行該類的靜態代碼段。關於forName()方法,實際上這個方法和newInstance()方法一起對類進行實例化,二者結合等同於new關鍵字。如:A a = (A)Class.forName(“pacage.A”).newInstance(); 這和你 A a = new A(); 是一樣的效果

動態加載和創建Class 對象,比如想根據用戶輸入的字符串來創建對象時需要用到:
String str = “用戶輸入的字符串” ;
Class t = Class.forName(str);
t.newInstance();

在初始化一個類,生成一個實例的時候,newInstance()方法和new關鍵字的區別在於創建對象的方式不一樣,前者是使用類加載機制,後者是創建一個新類而同時存在不同的對象創建方法這要考慮到軟件的可伸縮、可擴展和可重用等軟件設計思想

java工廠模式經常使用newInstance來創建實例,如:

class c = Class.forName(“Example”);
factory = (ExampleInterface)c.newInstance();

其中ExampleInterface是類Example的接口,上述代碼又可以寫成如下形式:

String className = “Example”;
class c = Class.forName(className);
factory = (ExampleInterface)c.newInstance();

進一步可以寫成如下形式:

String className = readfromXMLConfig;//從xml 配置文件中獲得字符串
class c = Class.forName(className);
factory = (ExampleInterface)c.newInstance();

這裏需要說明的是,readfromXMLConfig並不是一個類,而是指通過讀取XML文件中的類配置方法從而獲取類名的抽象描述。由此,我們可以只要在目標文件中配置好繼承了ExampleInterface接口的類,上述代碼將會自動創建對應的實例。

JVM的角度看,我們使用關鍵字new創建一個類的時候,這個類可以沒有被加載但是使用newInstance()方法的時候,就必須保證:
1、這個類已經加載;
2、這個類已經連接了。
而完成上面兩個步驟的正是Class的靜態方法forName()所完成的,這個靜態方法調用了啓動類加載器,即加載 java API的那個加載器。
現在可以看出,newInstance()實際上是把new這個方式分解爲兩步,即首先調用Class加載方法加載某個類,然後實例化。這樣分步的好處是顯而易見的。我們可以在調用class的靜態加載方法forName時獲得更好的靈活性,提供給了一種降耦的手段。

在Java開發特別是數據庫開發中,經常會用到Class.forName( )這個方法。通過查詢Java Documentation我們會發現使用Class.forName( )靜態方法的目的是爲了動態加載類。在加載完成後,一般還要調用Class下的newInstance( )靜態方法來實例化對象以便操作。因此,單單使用Class.forName( )是動態加載類是沒有用的,其最終目的是爲了實例化對象。 即:Class.forName("")返回的是類 ,Class.forName("").newInstance()返回的是object 

上述代碼連接數據庫的時候僅僅使用了forName()函數而沒有使用newInstance(),甚至有的jdbc連接數據庫的寫法裏是Class.forName(xxx.xx.xx);而有一 些:Class.forName(xxx.xx.xx).newInstance(),爲什麼會有這兩種寫法呢?
剛纔提到,Class.forName("");的作用是要求JVM查找並加載指定的類,如果在類中有靜態初始化器的話,JVM必然會執行該類的靜態代碼段。而在JDBC規範中明確要求這個Driver類必須向DriverManager註冊自己,即任何一個JDBC Driver的 Driver類的代碼都必須類似如下
  public class MyJDBCDriver implements Driver {
   static {
     DriverManager.registerDriver(new MyJDBCDriver());
  }
  }
 換言之,上述forName函數入參的Driver類在之前已經在JVM中實例化並註冊了,既然在靜態初始化器的中已經進行了註冊,所以我們在使用JDBC時只需要Class.forName(XXX.XXX);就可以了。
貼出Proxool 連接池的靜態初始化方法:
public class ProxoolDriver implements Driver {

    private static final Log LOG = LogFactory.getLog(ProxoolDriver.class);

    static {
        try {
            DriverManager.registerDriver(new ProxoolDriver());
        } catch (SQLException e) {
            System.out.println(e.toString());
        }
    }
}

getConnection()函數由繼承javax.sql.DataSource 接口指定的連接數據庫的函數,其功能封裝在Driver類中,入參有url,username,password三個,其中URL包括有相應的JDBC驅動和數據庫的存儲地址,username和password分別對應連接數據庫的用戶名和密碼。
prepareStatement()函數,其入參爲代碼中的sql語句,作用是對入參語句進行預編譯,與其功能相似的函數還有createStatement()函數。二者同屬connection類下的方法,只是二者實現方式不同,createStatement需要事先寫好完整的sql語句作爲入參,在創建時調用connection對象的方法createStatement()所返回的對象中的execute函數執行從而完成對sql語句的編譯和執行,即createStatement對sql語句的預編譯的入參口實際上是在createStatement創建對象中的execute方法處,如:
  1. String sql = "select * from users where  username= '"+username+"' and userpwd='"+userpwd+"'";  
  2. stmt = conn.createStatement();  
  3. rs = stmt.executeQuery(sql); 
而prepareStatement對sql的編譯較爲寬容,調用時將sql語句作爲入參,只是入參時的sql語句較爲寬容,可以用?代替sql語句字串組中的部分關鍵字串,當需要設定這些關鍵字串的值時,只需用setString等函數用自己的值對對應的?進行替換,預編譯後再用execute方法執行,如:
  1. String sql = "select * from users where  username=? and userpwd=?";  
  2. pstmt = conn.prepareStatement(sql);  
  3. pstmt.setString(1, username);  
  4. pstmt.setString(2, userpwd);  
  5. rs = pstmt.executeQuery();  
其中,setString(v1,v2)有兩個入參,入參v1是一個整型變量,代表輸入的sql語句中的第v1個出現的?,v2是一個string變量,代表?應當填入的關鍵字。
因此prepareStatement方法在一定程度上避免了sql注入攻擊。(這個真的不懂。。。。)
execute系列方法是執行sql語句並返回結果的方法,包括execute(),executeQuery()和executeUpdate(),execute函數接受返回多個結果集(union ResultSet)的sql語句;而executeQuery和executeUpdate函數只接受返回單個結果集(single ResultSet)的sql語句。
execute()方法返回一個boolean值,標明執行該語句是否返回一個ResultSet值。
executeUpdate()方法用於執行 INSERT、UPDATE 或 DELETE 語句以及 SQL DDL(數據定義語言)語句,例如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或 DELETE 語句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一個整數(int),指示受影響的行數(即更新計數)。對於 CREATE TABLE 或 DROP TABLE 等不操作行的語句,executeUpdate 的返回值總爲零。
executeQuery()方法用於產生單個結果集(ResultSet)的語句,例如:被執行最多的SELECT 語句。 這個方法被用來執行 SELECT 語句,但也只能執行查詢語句,執行後返回代表查詢結果的ResultSet對象。
詳細講解可參看:http://blog.csdn.net/qq_20302155/article/details/73696246

實際上,上述代碼便是絕大部分框架的數據庫連接底層操作方式的一個比較典型的案例。然而開發中存在諸多問題,如:sql相關的連接、配置、執行等語句和用戶名等參數都寫在了代碼之中,且對程序返回的結果的類型依賴手動判斷,不便於代碼的維護和複用,同時多次開關連接造成了極大的資源浪費。
因此,在解決上述問題時,有如下思路:sql配置、執行語句等放入外部連接,對參數的連接創立自動映射,並建立和維護連接池。

在部署Mybatis之前,其運行原理如:http://blog.csdn.net/u013631223/article/details/79225956參考,在此不多做贅述。
如下配置java bean文件User.java
package com.j1.jdbc.Model;

import java.util.Date;

public class User {
       private Long id;
        private String userName;
        private String password;
        private String name;
        private Integer age;
        private Boolean sex;
        private Date birthday;
        private Date created;
        private Date updated;
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
        public Boolean getSex() {
            return sex;
        }
        public void setSex(Boolean sex) {
            this.sex = sex;
        }
        public Date getBirthday() {
            return birthday;
        }
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
        public Date getCreated() {
            return created;
        }
        public void setCreated(Date created) {
            this.created = created;
        }
        public Date getUpdated() {
            return updated;
        }
        public void setUpdated(Date updated) {
            this.updated = updated;
        }
        @Override
        public String toString() {
            return "User [id=" + id + ", userName=" + userName + ", password="
                    + password + ", name=" + name + ", age=" + age + ", sex="
                    + sex + ", birthday=" + birthday + ", created=" + created
                    + ", updated=" + updated + "]";
        }

}
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.j1.user">
    <select id="queryUserByUserName" resultType="com.j1.jdbc.Model.User">
        SELECT *, user_name userName  FROM tb_user WHERE user_name = #{userName}
    </select>
    
</mapper>
其中,mapper下的namespace標籤是用於綁定DAO接口的,即用於面向接口編程的配置,其值對應的是接口user所在的工程文件夾位置;select標籤下的內容是和被存儲在外部xml文件的sql語句,標籤後的內容:id—>代表該sql語句在邏輯業務層的被調用代號,resultType—>代表該sql語句返回的結果集的類型,即對應結果集在邏輯層中存儲java bean變量類的位置。因此,爲正常使用該封裝sql語句,在工程內還需創建一個接口文件user,並在接口中創建一個方法名爲select id值,返回值類型爲id值對應的bean類或者類集合的方法
package com.j1;

import org.apache.ibatis.annotations.Mapper;
import com.j1.jdbc.Model.User;

public interface user{
List<User> user(String userName)
}
其中,user的入參雖然沒有必須的要求,但按習慣來講還是最好和匹配select id下的sql語句中的關鍵字(#{}中的字段)名相符合。

jdbc.properties文件內容如下:
#jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.username=root
jdbc.password=root
全局l配置文件種關於mybatis的內容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="jdbc.properties"/>
    <settings>
        <!-- 開啓駝峯自動映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.j1.jdbc.Model"/>
    </typeAliases>
    <!-- 指定環境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>  
    </environments>
    <mappers>
<!-- 指定UserMapper.xml  -->
         <mapper resource="UserMapper.xml" /> 
    </mappers>
</configuration>
其中,typeAliases標籤是用來設置執行文件的別名的,子標籤package用於將路徑文件夾(或包)下所有的類和接口批量設置別名,name爲文件夾或包的路徑,MyBatis默認的設置別名的方式就是去除類所在的包後的簡單的類名,比如me.gacl.domain.User這個實體類的別名就會被設置成User。當然也可以用typeAlias標籤單獨爲某個類設置別名, <typeAlias type="me.gacl.domain.User" alias="_User"/> 其中type值爲類的路徑,alias爲路徑類的別名。設置好別名後,這樣,在Mapper中我們就不用每次配置都寫類的全名了,但是namespace例外,namespace配置的時候必須寫類的全名。

之後執行mybatis框架下的sql語句
package com.j1.mybatis;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.j1.jdbc.Model.User;

public class Mybatis {
public static void main(String[] args) {
    
    //讀取配置文件
      try {
            //配置文件
        String resource="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
         // 通過SqlSessionFactoryBuilder構建一個SqlSessionFactory
        SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream);
        //System.out.println(sqlSessionFactory);
        SqlSession session=sqlSessionFactory.openSession();
        //System.out.println(session);
        User user=session.selectOne("com.j1.user.queryUserByUserName", "zhangsan");
        System.out.println(user);
        
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}    
}
       其中,Resources類提供了從路徑讀取中加載資源的多種易於使用的方法,主要用於:1、從類路徑加載各種SQL Map配置文件;2、從類路徑加載DAO manager配置文件;3、從類路徑黏在各種properties文件。Resources中提供的加載方式有多種:1、對於簡單的只讀文本數據,加載爲Reader,匹配方法爲Reader getResourceAsReader(String resource);2、對於簡單的只讀二進制或文本數據,加載爲Stream,方法爲Stream getResourceAsStream(String resource);3、對於可讀寫的二進制或文本文件,加載爲File,方法 File getResourceAsFile(String  resource);4、對於只讀的配置屬性文件,加載爲Properties,方法爲Properties getResourceAsProperties(String resource);5、對於只讀的通用資源,加載爲URL,方法爲Url getResourceAsUrl(String resource)。上述方法在使用時,入參的resource應當爲文件的全路徑(以文件夾/文件名的形式,上面只寫文件名的方式應該是寫錯了)其中,對於Resources類的方法也可以指定Classloader,也就是說各種方法有傳參Classloader的重載形式,如Properties getResourceAsProperties(Classloader classloader,String resource)

       上段代碼中,用stream方法將xml文件轉化成二進制流存儲在inputstream實例中,再由SqlSessionFactory實例讀取配置(build函數)並創建數據庫會話(openSession函數),SqlSessionFactory是個單個數據庫映射關係經過編譯後的內存鏡像. SqlSessionFactory對象的實例可以通過SqlSessionFactoryBuilder對象類獲得,而SqlSessionFactoryBuilder則可以從XML配置文件或一個預先定製的Configuration的實例構建出SqlSessionFactory的實例。SqlSessionFactoryBuilder這個類可以被實例化、使用和丟棄,一旦創建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 實例的最佳範圍是方法範圍也就是局部方法變量)。每一個MyBatis的應用程序都以一個SqlSessionFactory對象的實例爲核心.同時SqlSessionFactory也是線程安全的, SqlSessionFactory一旦被創建,應該在應用執行期間都存在.在應用運行期間不要重複創建多次,建議使用單例模式.SqlSessionFactory是創建SqlSession的工廠. 
 
       SqlSession是MyBatis的關鍵對象,是執行持久化操作的獨享,類似於JDBC中的Connection.它是應用程序與持久層之間執行交互操作的一個單線程對象,也是MyBatis執行持久化操作的關鍵對象.SqlSession對象完全包含以數據庫爲背景的所有執行SQL操作的方法,它的底層封裝了JDBC連接,可以用SqlSession實例來直接執行被映射的SQL語句.每個線程都應該有它自己的SqlSession實例.SqlSession的實例不能被共享,同時SqlSession也是線程不安全的,絕對不能講SqlSeesion實例的引用放在一個類的靜態字段甚至是實例字段中.也絕不能將SqlSession實例的引用放在任何類型的管理範圍中,比如Servlet當中的HttpSession對象中.使用完SqlSeesion之後關閉Session很重要,應該確保使用finally塊來關閉它.

mybatis創建sqlsession經過了以下幾個主要步驟:

1)       從核心配置文件mybatis-config.xml中獲取Environment(這裏面是數據源)

2)       Environment中取得DataSource

3)       Environment中取得TransactionFactory

4)       DataSource裏獲取數據庫連接對象Connection

5)       在取得的數據庫連接上創建事務對象Transaction

6)       創建Executor對象(該對象非常重要,事實上sqlsession的所有操作都是通過它完成的);

7)       創建sqlsession對象。



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