mybatis-中文文檔

1       Mybatis的介紹

Mybatis就是一個封裝jdbc的持久層框架,它和hibernate都屬於ORM框架,但是具體的說,hibernate是一個完全的orm框架,而mbatis是一個不完全的orm框架.

Mybatis讓程序員只關注sql本身,而不需要去關注如連接的創建、statement 的創建等操作。

Mybatis會將輸入參數據、輸出結果進行映射

 

MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,並且改名爲MyBatis,實質上Mybatis對ibatis進行一些改進。

MyBatis是一個優秀的持久層框架,它對jdbc的操作數據庫的過程進行封裝,使開發者只需要關注 SQL 本身,而不需要花費精力去處理例如註冊驅動、創建connection、創建statement、手動設置參數、結果集檢索等jdbc繁雜的過程代碼。

Mybatis通過xml或註解的方式將要執行的各種statement(statement、preparedStatemnt、CallableStatement)配置起來,並通過java對象和statement中的sql進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射成java對象並返回。

 

2       分析jdbc的問題

2.1      原生態的jdbc代碼

publicstaticvoid main(String[] args) {

        Connection connection = null;

        PreparedStatement preparedStatement = null;

        ResultSet resultSet = null;

       

        try {

            //1、加載數據庫驅動

            Class.forName("oracle.jdbc.OracleDriver");

            //2、通過驅動管理類獲取數據庫鏈接

            connection =  DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE", "xiaoming", "root");

            //3、定義sql語句 ?表示佔位符

            String sql = "select * from t_user where t_name = ?";

            //4、獲取預處理statement

            preparedStatement = connection.prepareStatement(sql);

            //5、設置參數,第一個參數爲sql語句中參數的序號(從1開始),第二個參數爲設置的參數值

            preparedStatement.setString(1, "程冠西");

            //6、向數據庫發出sql執行查詢,查詢出結果集

            resultSet =  preparedStatement.executeQuery();

            //7、遍歷查詢結果集

            while(resultSet.next()){

                System.out.println(resultSet.getString("t_id")+""+resultSet.getString("t_name"));

            }

        } catch (Exception e) {

            e.printStackTrace();

        }finally{

            //8、釋放資源

            if(resultSet!=null){

                try {

                    resultSet.close();

                } catch (SQLException e) {

                    e.printStackTrace();

                }

            }

            if(preparedStatement!=null){

                try {

                    preparedStatement.close();

                } catch (SQLException e) {

                    e.printStackTrace();

                }

            }

            if(connection!=null){

                try {

                    connection.close();

                } catch (SQLException e) {

                    e.printStackTrace();

                }

            }

 

        }

 

    }

 

2.2      問題總結

1、在創建連接時,存在硬編碼

配置文件

2、在執行statement時存在硬編碼

配置文件(映射文件)

3、頻繁的開啓的關閉數據庫連接,會造成數據庫性能下降。

數據庫連接池(全局配置文件)

 

3       Mybatis的框架原理

 

 

 

4       入門程序

4.1      需求

對訂單商品案例中的用戶表進行增刪改查操作

1、  根據用戶id查詢用戶信息

2、  根據用戶名稱模糊查詢用戶列表

3、  添加用戶

4、  刪除用戶(練習)

5、  修改用戶(練習)

4.2      環境準備

l  Jdk:1.7

l  Ide: MyEclipse 10

l  Mybatis:3.2.7

l  數據庫:mysql

4.3      下載mybaits

Mybatis的代碼同github.com管理,下載地址: https://github.com/mybatis/mybatis-3/releases

 

4.4      數據庫腳本初始化

 

4.5      工程搭建

Mybatis的核心包和依賴包(必須)

mysql的驅動包(必須)

Junit(非必須)

 

 

4.6      代碼實現

4.6.1   創建po類

 

4.6.2   創建全局配置文件

在config目錄下,創建SqlMapConfig.xml,該名稱不是固定不變的

 

db.driver=oracle.jdbc.OracleDriver

db.url=jdbc:oracle:thin:@localhost:1521:XE

 

4.6.3   需求開發

4.6.3.1        根據用戶id查詢用戶信息

4.6.3.1.1       映射文件

 

4.6.3.1.2       在全局配置文件夾中加載映射文件

 

 

4.6.3.1.3       測試代碼

 

4.6.4   根據用戶名稱模糊查詢用戶列表

4.6.4.1.1       映射文件

 

concat("%",#{value},"%")

concat(concat('%',#{value}),'%')

4.6.4.1.2       在全局配置文件夾中加載映射文件

 

4.6.4.1.3       測試代碼

 

4.6.5   添加用戶

4.6.5.1.1       映射文件

 

4.6.5.1.2       在全局配置文件夾中加載映射文件

 

4.6.5.1.3       測試代碼

 

4.6.5.1.4       主鍵返加自增長序列

Mysql

 

 

如果使用UUID 字段就必需設置爲字符串

select sys_guid()from dual;

oracle

 

 

 

4.6.5.1.5       selectKey語句屬性配置細節:

 

屬性

描述

取值

keyProperty

selectKey 語句生成結果需要設置的屬性。

 

resultType

生成結果類型,MyBatis 允許使用基本的數據類型,包括String 、int類型。

 

order

1:BEFORE,會先選擇主鍵,然後設置keyProperty,再執行insert語句;

2:AFTER,就先運行insert 語句再運行selectKey 語句。

BEFORE

AFTER

 

 

4.6.6   小結

4.6.6.1        parameterType和resultType

parameterType指定輸入參數的java類型,可以填寫別名或java類的全限定名.

4.6.6.2        #{}和${}

#{}:相當於預處理中的佔位符?。

#{}裏面的參數表示接收java輸入參數的名稱。

#{}可以接受HashMap、簡單類型、POJO類型的參數。

當接受簡單類型的參數時,#{}裏面可以是value,也可以是其他。

#{}可以防止SQL注入。

${}:相當於拼接SQL串,對傳入的值不做任何解釋的原樣輸出。

${}會引起SQL注入,所以要謹慎使用。

${}可以接受HashMap、簡單類型、POJO類型的參數。

當接受簡單類型的參數時,${}裏面只能是value。

4.6.6.3        selecxtOne和selectList

selecxtOne:只能查詢0或1條記錄,大於1條記錄的話,則報錯

 

selectList:可以查詢0或N條記錄

 

5       mybatis開發dao

mybatis在項目中主要使用的地方就是開發dao(數據訪問層   ),所以下面講解一下一步mybatis開發dao的方法。有兩種方式

1,原始dao開發方式(ibatis遺留下來的)

2.Mapper代理髮發方式(推薦)

5.1      需求

1、    根據用戶id來查詢用戶信息

2、    根據用戶名稱來模糊查詢用戶信息列表

3、    添加用戶

5.2      原始dao開發方式

5.2.1   思路

程序員需要寫dao和dao實現類

5.2.2   編程步驟

1、    根據需求創建po類

2、    編寫全局配置文件

3、    根據需求編寫映射文件

4、    加載映射文件

5、    編寫dao

6、    編寫dao實現類

7、    編寫測試代碼

5.2.3   程序編寫

步驟中的1、2、3、4都在入門程序中進行了編寫,此處不需要重新編寫。

5.2.3.1        開發dao接口

publicinterface UserDao {

 

    //根據用戶ID來查詢用戶信息

    public User findUserById(int id);

    //根據用戶名稱來模糊查詢用戶信息列表

    public List<User> findUsersByName(String username);

    //添加用戶

    publicvoid insertUser(User user);

}

5.2.3.2        開發dao實現類

5.2.3.2.1       SqlSession使用範圍

通過入門程序,大家可以看出,在測試代碼中,有大量的重複代碼。所以我們第一反應就是想給它抽取出共性的部分,但是SqlSession、SqlSessionFactory、SqlSessionFactoryBuilder有着各自的生命週期,因爲這些生命週期的不同,抽取時要有針對性的處理。

所以在抽取之前,我們先來了解並總結下它們三個的生命週期。

1、               SqlSessionFactoryBuilder

它的作用只是通過配置文件創建SqlSessionFactory,所以只要創建出SqlSessionFactory,它就可以銷燬了。所以說,它的生命週期是在方法之內。

 

2、               SqlSessionFactory

它的作用是創建SqlSession的工廠,工廠一旦創建,除非應用停掉,不要銷燬。

所以說它的生命週期是在應用範圍內。這裏可以通過單例模式來管理它。

在mybatis整合spring之後,最好的處理方式是把SqlSessionFactory交由spring來做單例管理。

 

3、               SqlSession

SqlSession是一個面向用戶(程序員)的接口,它的默認實現是DefaultSqlSession。

Mybatis是通過SqlSession來操作數據庫的。SqlSession中不僅包含要處理的SQL信息,還包括一些數據信息,所以說它是線程不安全的,因此它最佳的生命週期範圍是在方法體之內。

 

5.2.3.2.2       Dao實現類代碼

需要向dao實現類中注入SqlSessionFactory,在方法體內通過SqlSessionFactory創建SqlSession

要注意SqlSession和SqlSessionFactory的生命週期。

 

publicclass UserDaoImpl implements UserDao {

 

    //注入SqlSessionFactory

    private SqlSessionFactory sqlSessionFactory;

    //使用構造方法來初始化SqlSessionFactory

    public UserDaoImpl(SqlSessionFactory sqlSessionFactory){

       this.sqlSessionFactory = sqlSessionFactory;

    }

   

    @Override

    public User findUserById(int id) {

       //通過工廠,在方法內部獲取SqlSession,這樣就可以避免線程不安全

       SqlSession sqlSession = sqlSessionFactory.openSession();

       //返回結果集

       return sqlSession.selectOne("test.findUserById", id);

    }

 

    @Override

    public List<User> findUsersByName(String username) {

       //通過工廠,在方法內部獲取SqlSession,這樣就可以避免線程不安全

       SqlSession sqlSession = sqlSessionFactory.openSession();

       return sqlSession.selectList("test.findUsersByName", username);

    }

 

    @Override

    publicvoid insertUser(User user) {

       //通過工廠,在方法內部獲取SqlSession,這樣就可以避免線程不安全

       SqlSession sqlSession = sqlSessionFactory.openSession();

       sqlSession.insert("test.insertUser", user);

    }

 

}

 

5.2.3.3        編寫測試代碼

 

5.2.4   問題總結

原始dao開發存在一些問題:

  • 存在一定量的模板代碼。比如:通過SqlSessionFactory創建SqlSession;調用SqlSession的方法操作數據庫;關閉Sqlsession。
  • 存在一些硬編碼。調用SqlSession的方法操作數據庫時,需要指定statement的id,這裏存在了硬編碼。

 

5.3      Mapper代理開發方式(推薦)

Mapper代理的開發方式,程序員只需要編寫mapper接口(相當於dao接口)即可。Mybatis會自動的爲mapper接口生成動態代理實現類。

不過要實現mapper代理的開發方式,需要遵循一些開發規範。

5.3.1   開發規範

1、  mapper接口的全限定名要和mapper映射文件的namespace的值相同。

2、  mapper接口的方法名稱要和mapper映射文件中的statement的id相同;

3、  mapper接口的方法參數只能有一個,且類型要和mapper映射文件中statement的parameterType的值保持一致。

4、  mapper接口的方法參數如輸入多個時接口中必需加入註解@Param,且mapper映射文件中不需要parameterType

5、  mapper接口的返回值類型要和mapper映射文件中statement的resultType值或resultMap中的type值保持一致;

 

通過規範式的開發mapper接口,可以解決原始dao開發當中存在的問題:

1、  模板代碼已經去掉;

2、  剩下去不掉的操作數據庫的代碼,其實就是一行代碼。這行代碼中硬編碼的部分,通過第一和第二個規範就可以解決。

 

 

5.3.2   編程步驟

1、  根據需求創建po類

2、  編寫全局配置文件

3、  根據需求編寫映射文件

4、  加載映射文件

5、  編寫mapper接口

6、  編寫測試代碼

5.3.3   程序編寫

步驟中的1、2都在入門程序中進行了編寫,此處不需要重新編寫。

 

5.3.3.1        編寫mapper映射文件

重新定義mapper映射文件UserMapper.xml(內容同Users.xml,除了namespace的值),放到新創建的目錄mapper下。

 

5.3.3.2        加載mapper映射文件

<!-- 加載mapper -->

<mappers>

    <mapperresource="mapper/UserMapper.xml"/>

</mappers>

5.3.3.3        編寫mapper接口

內容同UserDao接口一樣:

 

5.3.3.4        編寫測試代碼

// 聲明全局的SqlSessionFactory

privatestatic SqlSessionFactory sqlSessionFactory ;

@BeforeClass

publicstaticvoid setUpBeforeClass() throws Exception {

    // 1、讀取配置文件

    String sqlMapConfig = "SqlMapConfig.xml";

    InputStream is = Resources.getResourceAsStream(sqlMapConfig);

    // 2、根據配置文件創建SqlSessionFactory

    sqlSessionFactory= new SqlSessionFactoryBuilder().build(is);

}

 

@Test

publicvoid testFindUserById() {

    // 創建SqlSession

    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 通過SqlSession,獲取mapper接口的動態代理對象

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 調用mapper對象的方法

    User user = userMapper.findUserById(1);

    System.out.println(user);

    // 關閉SqlSession

    sqlSession.close();

           

}

6       Mybatis全局配置文件

SqlMapConfig.xml是mybatis的全局配置文件,它的名稱可以是任意命名的。

6.1      全部配置內容

SqlMapConfig.xml的配置內容和順序如下(順序不能亂):

Properties(屬性)

typeAliases(類型別名)

typeHandlers(類型處理器)

objectFactory(對象工廠)

plugins(插件)

environments(環境信息集合)

         environment(單個環境信息)

                   transactionManager(事物)

                   dataSource(數據源)

mappers(映射器)

 

6.2      常用配置詳解

6.2.1   Properties

SqlMapConfig.xml文件中可以引用java屬性文件中的配置信息

 

db.properties配置信息如下:

db.driver=oracle.jdbc.OracleDriver

db.url=jdbc:oracle:thin:@localhost:1521:XE

db.username=xiaoming

db.password=root

SqlMapConfig.xml使用properties標籤後,如下所示:

<!-- 通過properties標籤,讀取java配置文件的內容 -->

<propertiesresource="db.properties"/>

 

<!-- 配置mybatis的環境信息 -->

<environmentsdefault="development">

    <environmentid="development">

       <!-- 配置JDBC事務控制,由mybatis進行管理 -->

       <transactionManagertype="JDBC"></transactionManager>

       <!-- 配置數據源,採用dbcp連接池 -->

       <dataSourcetype="POOLED">

           <propertyname="driver"value="${db.driver}"/>

           <propertyname="url"value="${db.url}"/>

           <propertyname="username"value="${db.username}"/>

           <propertyname="password"value="${db.password}"/>

       </dataSource>

    </environment>

</environments>

   

 

使用${},可以引用已經加載的java配置文件中的信息。

 

注意:mybatis將按照下面的順序加載屬性:

u  Properties標籤體內定義的屬性首先被讀取

u  Properties引用的屬性會被讀取,如果發現上面已經有同名的屬性了,那後面會覆蓋前面的值

u  parameterType接收的值會最後被讀取,如果發現上面已經有同名的屬性了,那後面會覆蓋前面的值

 

所以說,mybatis讀取屬性的順序由高到低分別是:parameterType接收的屬性值、properties引用的屬性、properties標籤內定義的屬性。

6.2.2   Settings

mybatis全局配置參數,全局參數將會影響mybatis的運行行爲。

 

詳細參見“mybatis學習資料/mybatis-settings.xlsx”文件

 

 

 

6.2.3   typeAliases

別名是使用是爲了在映射文件中,更方便的去指定入參和結果集的類型,不再用寫很長的一段全限定名。

6.2.3.1        mybatis支持的別名

別名

映射的類型

_byte

byte

_long

long

_short

short

_int

int

_integer

int

_double

double

_float

float

_boolean

boolean

string

String

byte

Byte

long

Long

short

Short

int

Integer

integer

Integer

double

Double

float

Float

boolean

Boolean

date

Date

decimal

BigDecimal

bigdecimal

BigDecimal

 

6.2.3.2        自定義別名

SqlMapConfig.xml配置信息如下:

<!-- 定義別名 -->

    <typeAliases>

       <!-- 單個定義別名 -->

       <typeAliastype="com.mybatis.po.User"alias="user"/>

      

       <!-- 批量定義別名(推薦) -->

       <!-- [name]:指定批量定義別名的類包,別名爲類名(首字母大小寫都可) -->

       <packagename="com.mybatis.po"/>

    </typeAliases>

 

6.2.4   mappers

6.2.4.1        <mapper resource=’’/>

使用相對於類路徑的資源

如:<mapper resource="sqlmap/User.xml" />

 

6.2.4.2        <mapper url=’’/>

使用完全限定路徑

如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />

6.2.4.3        <mapper class=’’/>

使用mapper接口的全限定名

如:<mapper class="com.mybatis.mapper.UserMapper"/>

 

注意:此種方法要求mapper接口和mapper映射文件要名稱相同,且放到同一個目錄下

6.2.4.4        <package name=’’/>(推薦)

註冊指定包下的所有映射文件

如:<package name="com.mybatis.mapper"/>

 

注意:此種方法要求mapper接口和mapper映射文件要名稱相同,且放到同一個目錄下

 

7       Mybatis映射文件(核心)

7.1      輸入映射

7.1.1   ParameterType

指定輸入參數的java類型,可以使用別名或者類的全限定名。它可以接收簡單類型、POJO、HashMap。

7.1.1.1        傳遞簡單類型

參考入門需求:根據用戶ID查詢用戶信息。

 

 

7.1.1.2        傳遞POJO對象

參考入門需求:添加用戶。

 

7.1.1.3        傳遞POJO包裝對象

開發中通過pojo傳遞查詢條件 ,查詢條件是綜合的查詢條件,不僅包括用戶查詢條件還包括其它的查詢條件(比如將用戶購買商品信息也作爲查詢條件),這時可以使用包裝對象傳遞輸入參數。

7.1.1.3.1       需求

綜合查詢用戶信息,需要傳入查詢條件複雜,比如(用戶信息、訂單信息、商品信息)。

7.1.1.3.2       定義包裝對象

一般User.java類要和數據表表字段一致,最好不要在這裏面添加其他字段,如果使用mybatis的逆向工程時,會根據表結構,生成po類,如果在po類中擴展字段,此時會被覆蓋掉。

所以針對要擴展的po類,我們需要創建一個擴展類,來繼承它。

 

定義POJO包裝類:

 

 

7.1.1.3.3       編寫Mapper接口

//通過包裝類來進行復雜的用戶信息綜合查詢

public List<UserExt> findUserList(UserQueryVO userQueryVO);

7.1.1.3.4       編寫mapper映射文件

 

<!-- 通過包裝類來進行復雜的用戶信息綜合查詢 -->

<selectid="findUserList"parameterType="userQueryVO"resultType="userExt">

       SELECT * FROM USER WHERE sex=#{userExt.sex} AND username LIKE '%${userExt.username}%'

</select>

 

注意:入參的類型變爲UserQueryVO、結果集的類型變爲UserExt,#{}裏面的參數變爲UserQueryVO對象中的userExt屬性的sex和username子屬性。

 

7.1.1.3.5       編寫測試代碼

 

7.1.1.4        傳遞HashMap(練習)

同傳遞POJO對象一樣,map的key相當於pojo的屬性

 

7.1.1.4.1       映射文件

<!-- 傳遞hashmap綜合查詢用戶信息 -->

    <selectid="findUserByHashmap"parameterType="hashmap"resultType="user">

       select * from user where id=#{id} and username like '%${username}%'

    </select>

 

上邊紅色標註的是hashmap的key。

 

7.1.1.4.2       測試代碼

Publicvoid testFindUserByHashmap()throws Exception{

        //獲取session

       SqlSession session = sqlSessionFactory.openSession();

       //獲限mapper接口實例

       UserMapper userMapper = session.getMapper(UserMapper.class);

       //構造查詢條件Hashmap對象

       HashMap<String, Object> map = new HashMap<String, Object>();

       map.put("id", 1);

       map.put("username", "管理員");

      

       //傳遞Hashmap對象查詢用戶列表

       List<User>list = userMapper.findUserByHashmap(map);

       //關閉session

       session.close();

    }

 

 

異常測試:

傳遞的map中的key和sql中解析的key不一致。

測試結果沒有報錯,只是通過key獲取值爲空。

7.2      輸出映射

7.2.1   resultType

先帶着同學們看下原先resultType作爲輸出結果映射時,它的特點,如何再把列名改爲別名,看看是否還能不能映射成功。

7.2.1.1        使用方法

使用resultType進行結果映射時,查詢的列名和映射的pojo屬性名完全一致,該列才能映射成功。

如果查詢的列名和映射的pojo屬性名全部不一致,則不會創建pojo對象;

如果查詢的列名和映射的pojo屬性名有一個一致,就會創建pojo對象。

 

7.2.1.2        輸出簡單類型

當輸出結果只有一列時,可以使用ResultType指定簡單類型作爲輸出結果類型。

7.2.1.2.1       需求

 查詢用戶總數。

7.2.1.2.2       Mapper映射文件

<!-- 查詢用戶信息總數-->

<selectid="findUsersCount"parameterType="UserVo"

      resultType="int">

      SELECT count(1) FROM USER WHERE sex = #{userExt.sex} AND username LIKE '%${userExt.username}%'

</select>

7.2.1.2.3       Mapper接口

//綜合查詢用戶信息總數。學習:resultType輸出簡單類型

publicintfindUsersCount(UserVo vo);

7.2.1.2.4       測試代碼

 

7.2.1.3        輸出POJO單個對象和列表

注意:輸出單個pojo對象和pojo列表(盛放pojo對象)時,mapper映射文件中的resultType的類型是一樣的,mapper接口的方法返回值不同。

 

7.2.1.3.1       Mapper映射文件

Mapper映射文件是同一個

<selectid="findUsersByName"parameterType="java.lang.String"resultType="com.mybatis.po.User">

       SELECT * FROM USER WHERE username LIKE '%${value}%'

</select>

7.2.1.3.2       Mapper接口

下面看下mapper接口的不同之處

1、  輸出單個pojo對象

//根據用戶名稱來模糊查詢用戶信息

public User findUsersByName(String username);

2、  輸出pojo列表

//根據用戶名稱來模糊查詢用戶信息列表

    public List<User> findUsersByName(String username);

 

總結:同樣的mapper映射文件,返回單個對象和對象列表時,mapper接口在生成動態代理的時候,會根據返回值的類型,決定調用selectOne方法還是selectList方法。

 

7.2.2   resultMap

resultMap可以進行高級結果映射(一對一、一對多映射,後面課程講解)。

7.2.2.1        使用方法

如果查詢出來的列名和屬性名不一致,通過定義一個resultMap將列名和pojo屬性名之間作一個映射關係。

1、  定義resultMap

2、  使用resultMap作爲statement的輸出映射類型。

7.2.2.2        需求

把下面SQL的輸出結果集進行映射

SELECT id id_,username username_,sex sex_ FROM USER WHERE id = 1

7.2.2.3        Mapper映射文件

定義resultMap:

<!-- 定義resultMap -->

<!--

    [id]:定義resultMap的唯一標識

    [type]:定義該resultMap最終映射的pojo對象

    [id標籤]:映射結果集的唯一標識列,如果是多個字段聯合唯一,則定義多個id標籤

    [result標籤]:映射結果集的普通列

    [column]:SQL查詢的列名,如果列有別名,則該處填寫別名

    [property]:pojo對象的屬性名

-->

<resultMaptype="user"id="userResultMap">

    <idcolumn="id_"property="id"/>

    <resultcolumn="username_"property="username"/>

    <resultcolumn="sex_"property="sex"/>

</resultMap>

 

定義statement:

<!-- 根據ID查詢用戶信息(學習resultMap) -->

<selectid="findUserByIdResultMap"parameterType="int"resultMap="userResultMap">

SELECT id id_,username username_,sex sex_ FROM USER WHERE id = #{id}

</select>

 

7.2.2.4        Mapper接口定義

    //根據ID查詢用戶信息(學習resultMap)

    public User findUserByIdResultMap(int id);

 

定義Statement使用resultMap映射結果集時,Mapper接口定義方法的返回值類型爲mapper映射文件中resultMap的type類型。

7.2.2.5        測試代碼

@Test

publicvoid findUserByIdResultMapTest() {

    // 創建SqlSession

    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 通過SqlSession,獲取mapper接口的動態代理對象

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

 

    // 調用mapper對象的方法

    User user = userMapper.findUserByIdResultMap(1);

 

    System.out.println(user);

    // 關閉SqlSession

    sqlSession.close();

}

7.3      動態SQL

通過Mybatis提供的各種動態標籤實現動態拼接sql,使得mapper映射文件在編寫SQL時更加靈活,方便。常用動態SQL標籤有:if、where、foreach;

7.3.1   If和where

  • If標籤:作爲判斷入參來使用的,如果符合條件,則把if標籤體內的SQL拼接上。

注意:用if進行判斷是否爲空時,不僅要判斷null,也要判斷空字符串‘’;

  • Where標籤:會去掉條件中的第一個and符號。

7.3.1.1        需求

用戶信息綜合查詢列表和用戶信息綜合查詢總數這兩個statement的定義使用動態SQL。

7.3.1.2        映射文件

<!-- 綜合查詢用戶信息,需要傳入查詢條件複雜,比如(用戶信息、訂單信息、商品信息) -->

<selectid="findUsersByQueryVO"parameterType="com.mybatis.po.QueryUserVO"

      resultType="User">

      SELECT * FROM USER

   <where>

      <iftest="userExt != null">

         <iftest="userExt.sex != null and userExt.sex != ''">

            AND sex = #{userExt.sex}

         </if>

         <iftest="userExt.username != null and userExt.username != ''">

            AND username LIKE '%${userExt.username}%'

         </if>

      </if>

   </where>

</select>

  

<!-- 綜合查詢用戶信息總數,需要傳入查詢條件複雜,比如(用戶信息、訂單信息、商品信息) -->

<selectid="findUsersCount"parameterType="QueryUserVO"

      resultType="int">

   SELECT count(1) FROM USER

   <where>

      <iftest="userExt != null">

         <iftest="userExt.sex != null and userExt.sex != ''">

            AND sex = #{userExt.sex}

         </if>

         <iftest="userExt.username != null and userExt.username != ''">

            AND username LIKE '%${userExt.username}%'

         </if>

      </if>

   </where>

</select>

7.3.1.3        Mapper接口

//通過包裝類來進行復雜的用戶信息綜合查詢

public List<UserExt> findUserList(UserQueryVO userQueryVO);

//綜合查詢用戶總數

publicint findUsersCount(UserQueryVO userQueryVO);

 

7.3.1.4        測試代碼

不傳用戶名:

 

輸出的SQL如下(也不包含用戶名):

 

 

 

通過測試可以得知,打印出的SQL語句確實會隨着條件的滿足情況而不一樣。

7.3.2   If Set改造

7.3.2.1.1       映射文件

<!-- 修改 -->

<updateid="updateUserSet"parameterType="user">

        update t_user

    <set>

        <iftest="uname!=null">

            uname =#{uname},

        </if>

        <iftest="birthday!=null">

            birthday =#{birthday},

        </if>

        <iftest="sex!=null and sex!=0">

            sex =#{sex},

        </if>

        <iftest="address!=null">

            address =#{address},

        </if>

    </set>

    where tid=#{tid}

</update>

 

 

7.3.2.1.2       Mapper接口

//修改

    publicint updateUserSet(User user);

 

7.3.2.1.3       測試代碼

@Test

publicvoid updateUserSet() {

    SqlSession sqlSession = sqlSessionFactory.openSession();

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    User user = new User();

    user.setSex('1');

    user.setUname("小3");

    user.setAddress(" 剛修改");

    user.setTid(5);

    userMapper.updateUserSet(user);

    sqlSession.close();

}

 

7.3.3   SQL片段

Mybatis提供了SQL片段的功能,可以提高SQL的可重用性。

7.3.3.1        定義SQL片段

使用sql標籤來定義一個SQL片段:

<!-- 定義SQL片段 -->

<!--

    [sql標籤]:定義一個SQL片段

    [id]:SQL片段的唯一標識

    建議:

       1、SQL片段中的內容最好是以單表來定義

       2、如果是查詢字段,則不要寫上SELECT

       3、如果是條件語句,則不要寫上WHERE

 -->

<sqlid="select_user_where">

    <iftest="userExt != null">

      <iftest="userExt.sex != null and userExt.sex != ''">

         AND sex = #{userExt.sex}

      </if>

      <iftest="userExt.username != null and userExt.username != ''">

         AND username LIKE '%${userExt.username}%'

      </if>

   </if>

</sql>

 

7.3.3.2        引用SQL片段

使用<include refid=’’ />來引用SQL片段:

<!-- 根據用戶id來查詢用戶信息(使用SQL片段) -->

<!--

    [include標籤]:引用已經定義好的SQL片段

    [refid]:引用的SQL片段id

-->

<selectid="findUserList"parameterType="userQueryVO"resultType="userExt">

 

    SELECT * FROM USER

<where>

      <includerefid="select_user_where"/>

   </where>

</select>

<!-- 綜合查詢用戶信息總數,需要傳入查詢條件複雜,比如(用戶信息、訂單信息、商品信息) -->

<selectid="findUsersCount"parameterType="QueryUserVO"

      resultType="int">

   SELECT count(1) FROM USER

   <where>

      <includerefid="select_user_where"/>

   </where>

</select>

 

7.3.4   Foreach

向sql傳遞數組或List時,mybatis使用foreach解析數組裏的參數並拼接到SQL中。

7.3.4.1        傳遞pojo對象中的List集合

7.3.4.1.1       需求

在用戶查詢列表和查詢總數的statement中增加多個id輸入查詢。

7.3.4.1.2       SQL

SELECT * FROM user WHERE id IN (1,10,16)

7.3.4.1.3       定義pojo中的List屬性

 

7.3.4.1.4       映射文件

<!-- [foreach標籤]:表示一個foreach循環 -->

<!-- [collection]:集合參數的名稱,如果是直接傳入集合參數,則該處的參數名稱只能填寫[list]。 -->

<!-- [item]:每次遍歷出來的對象 -->

<!-- [open]:開始遍歷時拼接的串 -->

<!-- [close]:結束遍歷時拼接的串 -->

<!-- [separator]:遍歷出的每個對象之間需要拼接的字符 -->

<iftest="idList != null and idList.size > 0">

<foreachcollection="idList"item="id"open="AND id IN ("close=")"separator=",">

       #{id}

</foreach>

</if>

7.3.4.1.5       Mapper接口

//根據用戶ID的集合查詢用戶列表(學習foreach標籤之通過POJO對象傳ID集合)

public List<UserExt> findUserList(UserQueryVO vo);

7.3.4.1.6       測試代碼

@Test

publicvoid testFindUserList() {

    // 創建SqlSession

    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 通過SqlSession,獲取mapper接口的動態代理對象

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

 

    // 構造QueryUserVO對象

    QueryUserVO vo = new QueryUserVO();

    // UserExt ext = new UserExt();

    // ext.setUsername("小明");

    // ext.setSex("1");

    // vo.setUserExt(ext);

 

    // 創建用戶ID集合,然後設置到QueryUserVO對象中

    List<Integer> idList = new ArrayList<Integer>();

    idList.add(1);

    idList.add(10);

    idList.add(16);

    vo.setIdList(idList);

 

    // 調用mapper代理對象的方法

    List<UserExt> list = mapper.findUserList(vo);

    System.out.println(list);

    // 關閉SqlSession

    sqlSession.close();

}

 

 

8       關聯查詢映射

8.1      分析數據模型

8.1.1   思路

1、  每張表記錄的數據內容

分模塊對每張表記錄的內容進行熟悉,相當於你學習系統需求(功能)的過程。

2、  每張表重要的字段

主鍵、外鍵、非空字段

3、  數據庫級別表與表的關係

外鍵關係

4、  表與表之間的業務關係

在分析表與表之間的業務關係時一定要建立 在某個業務意義基礎上去分析。

8.1.2   圖形分析

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

8.1.3   數據庫表之間有外鍵關係的業務關係

 

user和orders

user---->orders:一個用戶可以創建多個訂單,一對多

orders--->user:一個訂單隻由一個用戶創建,一對一

 

orders和orderdetail

orders-àorderdetail:一個訂單可以包括 多個訂單明細,因爲一個訂單可以購買多個商品,每個商品的購買信息在orderdetail記錄,一對多關係

orderdetail--> orders:一個訂單明細只能包括在一個訂單中,一對一

 

 

orderdetail和itesm

orderdetail---》itesms:一個訂單明細只對應一個商品信息,一對一

items--> orderdetail:一個商品可以包括在多個訂單明細 ,一對多

 

8.1.4   數據庫表之間沒有外鍵關係的業務關係

Orders和items

這兩張表沒有直接的外鍵關係,通過業務及數據庫的間接關係分析出它們是多對多的關係。

Ordersà orderdetail –>items:一個訂單可以有多個訂單明細,一個訂單明細對應一個商品,所以一個訂單對應多個商品

Items-àorderdetailàorders:一個商品可以對應多個訂單明細,一個訂單明細對應一個訂單,所以一個商品對應多個訂單

 

User和items

這兩張表沒有直接的外鍵關係,通過業務及數據庫的間接關係分析出它們是多對多的關係。

Useràordersàorderdetailàitems:一個用戶有多個訂單,一個訂單有多個訂單明細、一個訂單明細對應一個商品,所以一個用戶對應多個商品

Itemsàorderdetailàordersàuser:一個商品對應多個訂單明細,一個訂單明細對應一個訂單,一個訂單對應一個用戶,所以一個商品對應多個用戶

 

8.2      一對一查詢

8.2.1   需求

查詢訂單信息,關聯查詢創建訂單的用戶信息

8.2.2   SQL語句

確定查詢的主表:訂單表

確定查詢的關聯表:用戶表

 

 

SELECT

         o.id,

         o.order_Number,

         o.createtime,

         o.describer,

         o.user_id

         userId,

         u.uname,

         u.address

FROM orders o,t_user u

WHERE o.user_id=u.tid;

8.2.3   resultType

複雜查詢時,單表對應的po類已不能滿足輸出結果集的映射。

所以要根據需求建立一個擴展類來作爲resultType的類型。

8.2.3.1        創建po類

//通過此類映射訂單和用戶查詢的結果,讓此類繼承包括 字段較多的pojo

publicclass OrdersExt extends Orders{

    //添加用戶屬性

    /*USER.username,USER.address */

    private String username;

    private String address;

}

8.2.3.2        編寫mapper接口

創建OrdersMapper接口類,在類中添加以下內容:

//  進行訂單信息查詢,包括用戶的名稱和地址信息

public List<OrdersExt> findOrdersUser();

8.2.3.3        編寫映射文件

<mappernamespace="com.mybatis.mapper.OrdersMapper">

 

    <!-- 定義查詢訂單表列名的SQL片段 -->

    <sqlid="select_orders">

       Orders.id,

       Orders.user_id,

       orders.number,

       orders.createtime,

       orders.note

    </sql>

    <!-- 定義查詢用戶表列名的SQL片段 -->

    <sqlid="select_user">

       user.username,

       user.address

    </sql>

    <!-- 進行訂單信息查詢,包括用戶的名稱和地址信息 -->

    <selectid="findOrdersUser"resultType="OrdersExt">

       Select

       <includerefid="select_orders"/>

       <includerefid="select_user"></include>

       from orders,user

       where orders.user_id = user.id

    </select>

</mapper>

8.2.3.4        加載映射文件

<!-- 批量加載mapper文件,需要mapper接口文件和mapper映射文件名稱相同且在同一個包下 -->

<packagename="com.mybatis.mapper"/>

8.2.3.5        編寫測試代碼

@Test

publicvoidtestFindOrdersUser() {

    // 創建sqlSession

    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 通過SqlSession構造usermapper的代理對象

    OrdersMapper orders qlSession.getMapper(OrdersMapper.class);

    // 調用usermapper的方法

    // 釋放SqlSession

    sqlSession.close();

}

 

8.2.4   resultMap

8.2.4.1        修改po類

在Orders類中,添加User對象

publicclass Orders {

private Integer id;

 

private Integer userId;

 

private String number;

 

private Date createtime;

 

private String note;

 

//用戶信息

privateUseruser;

8.2.4.2        編寫mapper接口

    //  進行訂單信息查詢,包括用戶的名稱和地址信息(resultMap)

    public List<OrdersExt> findOrdersUserRstMap();

8.2.4.3        編寫映射文件

<!-- 進行訂單信息查詢,包括用戶的名稱和地址信息 (ResultMap) -->

    <selectid="findOrdersUserRstMap"resultMap="OrdersUserRstMap">

       Select

       <includerefid="select_orders"/>

       ,

       <includerefid="select_user"></include>

       from orders,user

       where orders.user_id = user.id

    </select>

 

    <!-- 定義orderUserResultMap -->

    <resultMaptype="com.mybatis.po.Orders"id="OrdersUserRstMap">

       <idcolumn="id"property="id"/>

       <resultcolumn="user_id"property="userId"/>

       <resultcolumn="number"property="number"/>

       <resultcolumn="createtime"property="createtime"/>

       <resultcolumn="note"property="note"/>

       <!-- 映射一對一關聯關係的用戶對象-->

       <!--

           property:指定關聯對象要映射到Orders的哪個屬性上

           javaType:指定關聯對象所要映射的java類型

         -->

       <!-- id標籤:指定關聯對象結果集的唯一標識,很重要,不寫不會報錯,但是會影響性能 -->

       <associationproperty="user"javaType="com.mybatis.po.User">

           <idcolumn="user_id"property="id"/>

           <resultcolumn="username"property="username"/>

           <resultcolumn="address"property="address"/>

       </association>

    </resultMap>

 

8.2.4.4        編寫測試代碼

@Test

publicvoid testFindOrdersUserRstMap() {

    // 創建sqlSession

    SqlSession sqlSession = sqlSessionFactory.openSession();

 

    // 通過SqlSession構造usermapper的代理對象

    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);

    // 調用usermapper的方法

    List<Orders> list = ordersMapper.findOrdersUserRstMap();

      

    //此處我們採用debug模式來跟蹤代碼,然後驗證結果集是否正確

    System.out.println(list);

    // 釋放SqlSession

    sqlSession.close();

}

8.2.5   一對一小結

實現一對一查詢:

resultType:使用resultType實現較爲簡單,如果pojo中沒有包括查詢出來的列名,需要增加列名對應的屬性,即可完成映射。

如果沒有查詢結果的特殊要求建議使用resultType。

 

resultMap:需要單獨定義resultMap,實現有點麻煩,如果對查詢結果有特殊的要求,使用resultMap可以完成將關聯查詢映射pojo的對象屬性中。

 

resultMap可以實現延遲加載,resultType無法實現延遲加載。

 

8.3      一對多查詢

一對多查詢和一對一查詢的配置基本類似。只是如果使用resultMap的話,映射一對多關聯關係要使用collection標籤。

8.3.1   需求

查詢訂單信息及訂單明細信息

8.3.2   SQL語句

確定主查詢表:訂單表

確定關聯查詢表:訂單明細表

在一對一查詢基礎上添加訂單明細表關聯即可。

 

SELECT

         o.id,

         o.order_Number,

         o.createtime,

         o.describer,

         o.user_id,

         u.uname,

         u.address,

         d.id detailId,

         d.product_num

FROM orders o,t_user u ,orderdetail d

WHERE o.user_id=u.tid

AND o.id=d.order_id

分析

使用resultType將上邊的 查詢結果映射到pojo中,訂單信息將會重複。

 

 

要求:

對orders映射不能出現重複記錄。

 

 

在orders.java類中添加List<Orderdetail>detailList屬性。

最終會將訂單信息映射到orders中,訂單所對應的訂單明細映射到orders中的detailList屬性中。

 

 

映射成的orders記錄數爲兩條(orders信息不重複)

每個orders中的detailList屬性存儲了該訂單所對應的訂單明細集合

8.3.3   修改PO類

在Orders類中添加以下屬性,並提供get/set方法:

 

//訂單明細

private List<Orderdetail>detailList;

8.3.4   編寫mapper接口

// 查詢訂單信息及訂單明細信息(一對多映射之使用resultMap)

public List<Orders> findOrdersAndOrderdetailRstMap();

8.3.5   編寫映射文件

<!-- 定義OrdersAndOrderdetailRstMap -->

<!-- extends:繼承已有的ResultMap,值爲繼承的ResultMap的唯一標示 -->

<resultMaptype="Orders"id="OrdersAndOrderdetailRstMap"

    extends="OrdersUserRstMap">

       <!-- 映射關聯關係(一對多) -->

       <!-- collection標籤:定義一個一對多關係

           ofType:指定該集合參數所映射的類型

        -->

       <collectionproperty="detailList"ofType="Orderdetail">

           <idcolumn="detail_id"property="id"/>

           <resultcolumn="items_id"property="itemsId"/>

           <resultcolumn="items_num"property="itemsNum"/>

       </collection>

    </resultMap>

 

<!-- 查詢訂單信息,包括用戶名稱、用戶地址,訂單商品信息(嵌套結果) -->

<selectid="findOrdersAndOrderdetailRstMap"resultMap="OrdersAndOrderdetailRstMap">

 

       Select

       <includerefid="select_orders"/>

       ,

       <includerefid="select_user"/>

       ,

       orderdetail.id detail_id,

       orderdetail.items_id,

       orderdetail.items_num

       from orders,user,orderdetail

       where orders.user_id = user.id

       and

       orders.id = orderdetail.orders_id

 

    </select>

 

resultMap的extends屬性:可以用此屬性來繼承一個已有的resultmap。但是它繼承的resultMap的type和它本身的type要保持一致。

8.3.6   編寫測試代碼

@Test

publicvoid testFindOrdersAndOrderdetailRstMap() {

    // 創建sqlSession

    SqlSession sqlSession = sqlSessionFactory.openSession();

 

    // 通過SqlSession構造usermapper的代理對象

    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);

    // 調用usermapper的方法

    List<Orders> list = ordersMapper.findOrdersAndOrderdetailRstMap();

      

    //此處我們採用debug模式來跟蹤代碼,然後驗證結果集是否正確

    System.out.println(list);

    // 釋放SqlSession

    sqlSession.close();

}

8.3.7   一對多小結

mybatis使用resultMap的collection對關聯查詢的多條記錄映射到一個list集合屬性中。

 

使用resultType實現:

需要對結果集進行二次處理。

將訂單明細映射到orders中的orderdetails中,需要自己處理,使用雙重循環遍歷,去掉重複記錄,將訂單明細放在orderdetails中。

8.4      多對多查詢

8.4.1   需求

查詢用戶信息及用戶購買的商品信息,要求將關聯信息映射到主pojo的pojo屬性中

8.4.2   SQL語句

查詢主表:user

查詢關聯表:orders、orderdetail、t_product

 

select orders.id,

       orders.order_number,

       orders.createtime,

       orders.describer,

       orders.user_id,

       u.uname,

       u.address,

       o.id detail_id,

       o.product_num,

p.id product_id,

       p.p_name,

       p.description,

       p.price

from orders, tt_user u,orderdetail o,t_product p

where orders.user_id = u.tid and orders.id=o.order_id and o.product_id=p.id

8.4.3   映射思路

將用戶信息映射到user中。

在user類中添加訂單列表屬性List<Orders> orderslist,將用戶創建的訂單映射到orderslist

在Orders中添加訂單明細列表屬性List<Orderdetail> detailList,將訂單的明細映射到detailList

在Orderdetail中添加Items屬性,將訂單明細所對應的商品映射到product

 

8.4.4   修改PO類

在user類中添加List<Orders> ordersList 屬性

// 訂單信息

private List<Orders>ordersList;

在Orders類中添加List<Orderdetail>屬性

//訂單明細

private List<Orderdetail>detailList;

在Orderdetail類中添加Items屬性

//商品信息

private  Product product;

8.4.5   編寫mapper接口

//查詢用戶及用戶購買商品信息(多對多映射之使用resultMap)

public List<User> findUserAndItemsRstMap();

8.4.6   編寫映射文件

<!-- 定義UserAndItemsRstMap -->

    <resultMap type="com.mybatis.po.User" id="userProductResMap">

       <!-- user映射 -->

       <id column="user_id" property="tid"/>

       <result column="uname" property="uname"/>

       <result column="address" property="address"/>

       <!-- 訂單 一個用戶對應多個訂單 -->

       <collection property="orders" ofType="com.mybatis.po.Orders">

           <id column="id" property="id"/>

           <result column="ORDER_NUMBER" property="orderNumber"/>

           <result column="createtime" property="createtime"/>

           <result column="describer" property="describer"/>

           <result column="user_Id" property="userId"/>

           <!-- 一個訂單有多個明細 -->

           <collection property="orderDetails" ofType="com.mybatis.po.OrderDetail">

              <id column="detail_id" property="id"/>

              <result column="product_num" property="productNum"/>

             

              <!-- 一個訂單對應一個產品 -->

              <association property="product"  javaType="Product">

                  <id column="product_id" property="id"/>

                  <result column="p_name" property="pName"/>

                  <result column="description" property="description"/>

                  <result column="price" property="price"/>

              </association>

           </collection>

       </collection>

      

</resultMap>

    <!-- 查詢用戶及用戶購買商品信息(多對多映射之使用resultMap) -->

    <selectid="findUserAndItemsRstMap"resultMap="UserAndItemsRstMap">

       Select

       <includerefid="select_orders"/>

       ,

       <includerefid="select_user"/>

       ,

       <includerefid="select_orderdetail"></include>

       ,

       items.name items_name,

       items.detail items_detail

       from

       orders,user,orderdetail,items

       where orders.user_id = user.id

       and

       orders.id = orderdetail.orders_id

       and orderdetail.items_id = items.id

    </select>

 

8.4.7   編寫測試代碼

    @Test

    publicvoid testFindUserAndItemsRstMap() {

       // 創建sqlSession

       SqlSession sqlSession = sqlSessionFactory.openSession();

 

       // 通過SqlSession構造usermapper的代理對象

       OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);

        // 調用usermapper的方法

       List<User> list = ordersMapper.findUserAndItemsRstMap();

 

       // 此處我們採用debug模式來跟蹤代碼,然後驗證結果集是否正確

       System.out.println(list);

       // 釋放SqlSession

       sqlSession.close();

    }

8.4.8   多對多查詢小結

將查詢用戶購買的商品信息明細清單,(用戶名、用戶地址、購買商品名稱、購買商品時間、購買商品數量)

 

針對上邊的需求就使用resultType將查詢到的記錄映射到一個擴展的pojo中,很簡單實現明細清單的功能。

 

一對多是多對多的特例,如下需求:

查詢用戶購買的商品信息,用戶和商品的關係是多對多關係。

需求1:

查詢字段:用戶賬號、用戶名稱、用戶性別、商品名稱、商品價格(最常見)

企業開發中常見明細列表,用戶購買商品明細列表,

使用resultType將上邊查詢列映射到pojo輸出。

 

需求2:

查詢字段:用戶賬號、用戶名稱、購買商品數量、商品明細(鼠標移上顯示明細)

使用resultMap將用戶購買的商品明細列表映射到user對象中。

 

總結:

 

使用resultMap是針對那些對查詢結果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多個list。

8.5      高級映射總結

resultType:

作用:

         將查詢結果按照sql列名pojo屬性名一致性映射到pojo中。

場合:

         常見一些明細記錄的展示,比如用戶購買商品明細,將關聯查詢信息全部展示在頁面時,此時可直接使用resultType將每一條記錄映射到pojo中,在前端頁面遍歷list(list中是pojo)即可。

 

resultMap:

         使用association和collection完成一對一和一對多高級映射(對結果有特殊的映射要求)。

 

association:

作用:

         將關聯查詢信息映射到一個pojo對象中。

場合:

         爲了方便查詢關聯信息可以使用association將關聯訂單信息映射爲用戶對象的pojo屬性中,比如:查詢訂單及關聯用戶信息。

         使用resultType無法將查詢結果映射到pojo對象的pojo屬性中,根據對結果集查詢遍歷的需要選擇使用resultType還是resultMap。

        

collection:

作用:

         將關聯查詢信息映射到一個list集合中。

場合:

         爲了方便查詢遍歷關聯信息可以使用collection將關聯信息映射到list集合中,比如:查詢用戶權限範圍模塊及模塊下的菜單,可使用collection將模塊映射到模塊list中,將菜單列表映射到模塊對象的菜單list屬性中,這樣的作的目的也是方便對查詢結果集進行遍歷查詢。

         如果使用resultType無法將查詢結果映射到list集合中。

9       延遲加載

9.1      什麼是延遲加載

resultMap中的association和collection標籤具有延遲加載的功能。

 

延遲加載的意思是說,在關聯查詢時,利用延遲加載,先加載主信息。需要關聯信息時再去按需加載關聯信息。這樣會大大提高數據庫性能,因爲查詢單表要比關聯查詢多張錶速度要快。

 

9.2      設置延遲加載

Mybatis默認是不開啓延遲加載功能的,我們需要手動開啓。

需要在SqlMapConfig.xml文件中,在<settings>標籤中開啓延遲加載功能。

lazyLoadingEnabled、aggressiveLazyLoading

 

設置項

描述

允許值

默認值

lazyLoadingEnabled

全局性設置懶加載。如果設爲‘false’,則所有相關聯的都會被初始化加載。

true | false

false

aggressiveLazyLoading

當設置爲‘true’的時候,懶加載的對象可能被任何懶屬性全部加載。否則,每個屬性都按需加載。

true | false

true

 

 

 

 

9.3      使用association進行延遲加載

9.3.1   需求

查詢訂單並且關聯查詢用戶信息(對用戶信息的加載要求是按需加載)

9.3.2   編寫映射文件

需要定義兩個mapper的方法對應的statement。

1、只查詢訂單信息

SELECT * FROM orders

在查詢訂單的statement中使用association去延遲加載(執行)下邊的satatement(關聯查詢用戶信息)

<!-- 定義OrdersUserLazyLoadingRstMap -->

<resultMaptype="com.mybatis.po.Orders"id="OrdersUserLazyLoadingRstMap">

    <idcolumn="id"property="id"/>

    <resultcolumn="user_id"property="userId"/>

    <resultcolumn="number"property="number"/>

    <resultcolumn="createtime"property="createtime"/>

    <resultcolumn="note"property="note"/>

      

    <!-- 延遲加載用戶信息 -->

    <!-- select:指定延遲加載需要執行的statement的id(是根據user_id查詢用戶信息的statement)

       我們使用UserMapper.xml中的findUserById完成根據用戶ID(user_id)查詢用戶信息

       如果findUserById不在本mapper中,前邊需要加namespace

    -->

    <!-- column:主信息表中需要關聯查詢的列,此處是user_id -->

<associationproperty="user"select="com.mybatis.mapper.UserMapper.findUserById"column="user_id"></association>

</resultMap>

 

<!-- 查詢訂單信息,延遲加載關聯查詢的用戶信息 -->

<selectid="findOrdersUserLazyLoading"resultMap="OrdersUserLazyLoadingRstMap">

    SELECT * FROM orders

</select>

 

2、關聯查詢用戶信息

         通過上邊查詢到的訂單信息中user_id去關聯查詢用戶信息

         使用UserMapper.xml中的findUserById

<selectid="findUserById"parameterType="int"

       resultType="com.mybatis.po.User">

    SELECT * FROM user WHERE id = #{id}

</select>

 

上邊先去執行findOrdersUserLazyLoading,當需要去查詢用戶的時候再去執行findUserById,通過resultMap的定義將延遲加載執行配置起來。

 

9.3.3   加載映射文件

<!-- 批量加載mapper文件,需要mapper接口文件和mapper映射文件名稱相同且在同一個包下 -->

<packagename="com.mybatis.mapper"/>

9.3.4   編寫mapper接口

// 查詢訂單信息,延遲加載關聯查詢的用戶信息

public List<Orders>findOrdersUserLazyLoading();

9.3.5   編寫測試代碼

思路:

1、執行上邊mapper方法(findOrdersUserLazyLoading),內部去調用com.mybatis.mapper.OrdersMapper中的findOrdersUserLazyLoading只查詢orders信息(單表)。

 

2、在程序中去遍歷上一步驟查詢出的List<Orders>,當我們調用Orders中的getUser方法時,開始進行延遲加載。

 

3、執行延遲加載,去調用UserMapper.xml中findUserbyId這個方法獲取用戶信息。

 

@Test

publicvoidtestFindOrdersUserLazyLoading() {

    // 創建sqlSession

    SqlSession sqlSession = sqlSessionFactory.openSession();

 

    // 通過SqlSession構造usermapper的代理對象

    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);

    // 調用usermapper的方法

    List<Orders> list = ordersMapper.findOrdersUserLazyLoading();

 

    for(Orders orders : list){

       System.out.println(orders.getUser());

    }

    // 釋放SqlSession

    sqlSession.close();

}

9.4      延遲加載思考

不使用mybatis提供的association及collection中的延遲加載功能,如何實現延遲加載??

 

實現方法如下:

定義兩個mapper方法:

1、查詢訂單列表

2、根據用戶id查詢用戶信息

實現思路:

先去查詢第一個mapper方法,獲取訂單信息列表

在程序中(service),按需去調用第二個mapper方法去查詢用戶信息。

 

總之:

使用延遲加載方法,先去查詢簡單的sql最好單表,也可以關聯查詢),再去按需要加載關聯查詢的其它信息。

 

10             查詢緩存

10.1mybatis緩存分析

mybatis提供查詢緩存,如果緩存中有數據就不用從數據庫中獲取,用於減輕數據壓力,提高系統性能。

 

 

        

一級緩存是SqlSession級別的緩存。在操作數據庫時需要構造 sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。不同的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。

 

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

10.2一級緩存

10.2.1 原理

 

第一次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,如果沒有,從數據庫查詢用戶信息。

得到用戶信息,將用戶信息存儲到一級緩存中。

 

如果sqlSession去執行commit操作(執行插入、更新、刪除),清空SqlSession中的一級緩存,這樣做的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。

 

第二次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,緩存中有,直接從緩存中獲取用戶信息。

 

Mybatis默認支持一級緩存。

 

10.2.2 測試1

    @Test

    publicvoid testOneLevelCache() {

       SqlSession sqlSession = sqlSessionFactory.openSession();

       UserMapper mapper = sqlSession.getMapper(UserMapper.class);

       // 第一次查詢ID爲1的用戶,去緩存找,找不到就去查找數據庫

       User user1 = mapper.findUserById(1);

       System.out.println(user1);

   

       // 第二次查詢ID爲1的用戶

       User user2 = mapper.findUserById(1);

       System.out.println(user2);

 

       sqlSession.close();

    }

 

只輸出一次SQL:

 

10.2.3 測試2

    @Test

    publicvoid testOneLevelCache() {

       SqlSession sqlSession = sqlSessionFactory.openSession();

       UserMapper mapper = sqlSession.getMapper(UserMapper.class);

       // 第一次查詢ID爲1的用戶,去緩存找,找不到就去查找數據庫

       User user1 = mapper.findUserById(1);

       System.out.println(user1);

       User user = new User();

       //執行增刪改操作,清空緩存

       mapper.insertUser(user);

       // 第二次查詢ID爲1的用戶

       User user2 = mapper.findUserById(1);

       System.out.println(user2);

       sqlSession.close();

    }

 

 

中間執行了commit操作,同樣的查詢SQL輸出兩次:

 

 

 

10.2.4 應用

正式開發,是將mybatis和spring進行整合開發,事務控制在service中。

一個service方法中包括 很多mapper方法調用。

 

service{

         //開始執行時,開啓事務,創建SqlSession對象

         //第一次調用mapper的方法findUserById(1)

        

         //第二次調用mapper的方法findUserById(1),從一級緩存中取數據

         //方法結束,sqlSession關閉

}

 

如果是執行兩次service調用查詢相同 的用戶信息,不走一級緩存,因爲session方法結束,sqlSession就關閉,一級緩存就清空。

10.3二級緩存

10.3.1 原理

下圖是多個sqlSession請求UserMapper的二級緩存圖解。

 

 

二級緩存是mapper級別的。

 

第一次調用mapper下的SQL去查詢用戶信息。查詢到的信息會存到該mapper對應的二級緩存區域內。

第二次調用相同namespace下的mapper映射文件中相同的SQL去查詢用戶信息。會去對應的二級緩存內取結果。

如果調用相同namespace下的mapper映射文件中的增刪改SQL,並執行了commit操作。此時會清空該namespace下的二級緩存。

10.3.2 開啓二級緩存

Mybatis默認是沒有開啓二級緩存

 

1、  在覈心配置文件SqlMapConfig.xml中加入以下內容(開啓二級緩存總開關):

在settings標籤中添加以下內容:

<!-- 開啓二級緩存總開關 -->

<settingname="cacheEnabled"value="true"/>

 

2、  在UserMapper映射文件中,加入以下內容,開啓二級緩存:

 

<!-- 開啓本mapper下的namespace的二級緩存,默認使用的是mybatis提供的PerpetualCache -->

<cache></cache>

 

10.3.3 實現序列化

由於二級緩存的數據不一定都是存儲到內存中,它的存儲介質多種多樣,所以需要給緩存的對象執行序列化。

如果該類存在父類,那麼父類也要實現序列化。

 

 

10.3.4 測試1

@Test

    publicvoid testTwoLevelCache() {

       SqlSession sqlSession1 = sqlSessionFactory.openSession();

       SqlSession sqlSession2 = sqlSessionFactory.openSession();

       SqlSession sqlSession3 = sqlSessionFactory.openSession();

 

       UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

       UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

       UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);

       // 第一次查詢ID爲1的用戶,去緩存找,找不到就去查找數據庫

       User user1 = mapper1.findUserById(1);

       System.out.println(user1);

       // 關閉SqlSession1

       sqlSession1.close();

 

       // 第二次查詢ID爲1的用戶

       User user2 = mapper2.findUserById(1);

       System.out.println(user2);

       // 關閉SqlSession2

       sqlSession2.close();

    }

 

SQL輸出結果:

 

 

Cache Hit Radio: 緩存命中率

第一次緩存中沒有記錄,則命中率0.0;

第二次緩存中有記錄,則命中率0.5(訪問兩次,有一次命中)

10.3.5 測試2

@Test

    publicvoid testTwoLevelCache() {

       SqlSession sqlSession1 = sqlSessionFactory.openSession();

       SqlSession sqlSession2 = sqlSessionFactory.openSession();

       SqlSession sqlSession3 = sqlSessionFactory.openSession();

 

       UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

       UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

       UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);

       // 第一次查詢ID爲1的用戶,去緩存找,找不到就去查找數據庫

       User user1 = mapper1.findUserById(1);

       System.out.println(user1);

       // 關閉SqlSession1

       sqlSession1.close();

 

       //修改查詢出來的user1對象,作爲插入語句的參數

       mapper3.insertUser(user1);

 

       // 提交事務

       sqlSession3.commit();

       // 關閉SqlSession3

       sqlSession3.close();

 

       // 第二次查詢ID爲1的用戶

       User user2 = mapper2.findUserById(1);

       System.out.println(user2);

       // 關閉SqlSession2

       sqlSession2.close();

    }

 

 

SQL輸出結果:

根據SQL分析,確實是清空了二級緩存了。

 

 

10.3.6 禁用二級緩存

該statement中設置userCache=false,可以禁用當前select語句的二級緩存,即每次查詢都是去數據庫中查詢,默認情況下是true,即該statement使用二級緩存。

 

<selectid="findUserById"parameterType="int"

       resultType="com.mybatis.po.User"useCache="true">

    SELECT * FROM user WHERE id = #{id}

</select>

 

10.3.7 刷新二級緩存

該statement中設置flushCache=true可以刷新當前的二級緩存,默認情況下如果是select語句,那麼flushCache是false。如果是insert、update、delete語句,那麼flushCache是true

如果查詢語句設置成true,那麼每次查詢都是去數據庫查詢,即意味着該查詢的二級緩存失效。

如果查詢語句設置成false,即使用二級緩存,那麼如果在數據庫中修改了數據,而緩存數據還是原來的,這個時候就會出現髒讀。

 

flushCache設置如下:

<selectid="findUserById"parameterType="int"

       resultType="com.mybatis.po.User"useCache="true"flushCache="true">

       SELECT * FROM user WHERE id = #{id}

</select>

 

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