基於持久層框架Mybatis的數據訪問和動態sql定義

1. MyBatis框架的作用

MyBatis是持久層框架。

在Java實現數據庫編程時,主要通過JDBC來實現,而JDBC相關代碼在實現過程中流程相對固定,所以就出現了各種減化開發的持久層框架,常見的有HibernateMyBatis

使用MyBatis框架時無需編寫JDBC相關代碼,只需要爲某些抽象方法配置其對應的需要執行的SQL語句即可。

2. 使用MyBatis實現數據訪問

2.1. 案例目標:通過MyBatis實現用戶註冊

假設用戶數據有:用戶名、密碼、年齡、手機號碼、電子郵箱,通過使用MyBatis框架實現向數據庫的表中插入用戶數據。

2.2. 創建數據庫與數據表

創建數據庫ums-->使用數據庫ums-->創建數據表t_user

2.3. 創建項目

創建項目時必須做的5件事情(添加web.xml,添加Tomcat環境,添加依賴(pom.xml),複製web.xml中的DispatcherServlet和CharacterEncodingFilter的配置,複製spring-mvc.xml)。

完成後,打開spring-mvc.xml,刪除與本次項目不相關的配置,例如攔截器的配置等等。

在使用MyBatis時,還需要添加相關依賴。首先在pom.xml中,將spring-webmvc的依賴複製一份,修改爲spring-jdbc。

在同一個項目中,如果需要使用多個以`spring-`作爲前綴的依賴,則應該使用相同的版本

然後,還應該檢查是否已經添加mysql-connector-java依賴,用於操作mysql數據庫:

繼續檢查是否已經添加commons-dbcp依賴,用於處理數據庫連接池等:

繼續檢查是否已經添加junit依賴,用於單元測試:

然後,添加mybatis依賴。

通常,會將mybatis與spring整合起來使用,添加mysql-spring依賴:

注意:後續代碼運行時,如果本應該存在的類/接口卻提示ClassNotFoundException,或提示ZipException,則意味着新添加的依賴是下載失敗的,需要刪除對應的jar文件甚至所在的文件夾,然後重新更新項目。

 

2.4. 建立連接

在resources下創建db.properties文件,用於配置數據庫連接。

將spring-mvc.xml複製,並粘貼爲spring-dao.xml,刪除文件中原有的配置,並加載db.properties,將配置的屬性注入到BasicDataSource中:

<!-- 讀取db.properties中的配置 -->

<util:properties id="db" location="classpath:db.properties" />

<!-- 配置數據源 -->

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">

<property name="url" value="#{db.url}" />

<property name="driverClassName" value="#{db.driver}" />

...

</bean>

然後,編寫並執行單元測試:

@Test

public void getConnection() throws SQLException {

// 加載Spring配置文件,獲取Spring容器

AbstractApplicationContext ac= new ClassPathXmlApplicationContext("spring-dao.xml");

// 從Spring容器中獲取對象

BasicDataSource dataSource = ac.getBean("dataSource", BasicDataSource.class);

// 測試

Connection conn = dataSource.getConnection();

System.out.println(conn);

// 釋放資源

ac.close();

}

 

2.5.創建類和接口

1)創建cn.tedu.mybatis.User類,在類中聲明與數據表對應的6個屬性:

public class User {

private Integer id;

...// SET/GET/toString

}

> 每張數據表都應該有一個與之對應的實體類。

2)創建cn.tedu.mybatis.UserMapper接口,並在接口中聲明抽象方法

public Integer addnew(User user);

在使用MyBatis時,如果抽象方法對應需要執行的數據操作是增、刪、改則抽象方法的返回值應該是Integer表示受影響的行數;抽象方法的名稱可以自由定義,符合語法規範並能表達語義即可;抽象方法的參數應該根據執行SQL語句時所需要的參數來決定

然後,需要在Spring的配置文件中配置MapperScannerConfigurer,用於掃描接口文件

<!-- MapperScannerConfigurer -->

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

<!--指定接口文件所在的根包-->

<property name="basePackage" value="cn.tedu.mybatis" />

</bean>

2.6.配置映射

從FTP下載somemapper.zip並解壓得到SomeMapper.xml文件。在resources下創建名爲mappers的文件夾,將SomeMapper.xml複製到該文件夾下,並修改文件名爲UserMapper.xml

關於UserMapper.xml的配置:

<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"      
	"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<!-- namespace:匹配的Java接口 -->

<mapper namespace="cn.tedu.mybatis.UserMapper">

<!-- 根據將要執行的SQL語句的類型選擇節點 -->

<!--  id:對應的抽象方法的方法名 -->

<!-- 在SQL語句中的?應該使用#{}來佔位 -->

<!-- 在#{}中的名稱是參數User類中的屬性名 -->

<insert id="addnew">

INSERT INTO t_user (username, password,age, phone,email

) VALUES (#{username}, #{password},#{age}, #{phone},#{email})

</insert>

</mapper>

爲了使得MyBatis框架能找到這些XML的映射文件並執行其中的SQL語句,還需要在Spring的配置文件中配置:

<!-- SqlSessionFactoryBean -->

<bean class="org.mybatis.spring.SqlSessionFactoryBean">

<!-- 映射的XML文件的位置 -->

<property name="mapperLocations" value="classpath:mappers/*.xml" />

<!-- 指定數據源,取值將引用(ref)以上配置的數據源的id -->

<property name="dataSource" ref="dataSource" />

</bean>

至此開發任務已經完成,可以編寫並執行單元測試:

@Test

public void addnew() {

AbstractApplicationContext ac= new ClassPathXmlApplicationContext("spring-dao.xml");

UserMapper userMapper= ac.getBean("userMapper", UserMapper.class);

System.out.println(userMapper);


User user = new User();

user.setUsername("root");

...

Integer rows = userMapper.addnew(user);

System.out.println("rows=" + rows);

ac.close();

}

 3. 多參數的處理

MyBatis框架只識別抽象方法中的1個參數,即:抽象方法應該最多隻有1個參數。

當某個抽象方法的參數只有1個時,例如:

Integer deleteById(Integer uid);

在配置映射時,佔位符中的名稱可以是隨意的名字,例如:

<delete id="deleteById">

DELETE FROM t_user WHERE id=#{helloworld}

</delete>

因爲參數只有1個,框架會自動的讀取對應位置的參數並應用於執行SQL語句,所以參數的名稱根本就不重要。

另外,每個.java源文件在被編譯成爲.class文件之後,也都會丟失參數名稱。

可以將抽象方法的參數設計爲1個Map,則可以滿足“參數只有1個”的需求,但使用時非常不方便,因爲方法的調用者並不明確向Map中封裝數據時應該使用什麼名稱的key。

爲了解決這個問題,MyBatis中提供了@Param註解,在設計抽象方法時,允許使用多個參數,但是每個參數之前都應該添加該註解,並在註解中設置參數的名稱,後續在執行時,MyBatis會基於這些註解將調用時的參數封裝爲Map來執行:   

  

【小結】 當需要多個參數時(超過1個參數時),每個參數前都添加@Param註解,並且註解中的配置的名稱方法的參數名稱XML中佔位符中的名稱應該都使用相同的名稱

### 2. 查詢數據

與增、刪、改相差不大,查詢時也應該先設計抽象方法,再配置映射。

在配置抽象方法時,返回值的類型應該根據查詢需求來決定,即:調用該方法後希望得到什麼的數據。

例如:**根據用戶的id查詢用戶詳情**,則對應的抽象方法應該是:

User findById(Integer id);

在配置映射時,查詢所使用的`<select>`必須配置resultType屬性或resultMap屬性(二選一):

<select id="findById" resultType="cn.tedu.mybatis.User">

SELECT id, username,password, age,phone, email  FROM  t_user  WHERE  id=#{id}

</select>

例如:**查詢當前表中所有的數據**:

List<User> findAll();

在配置映射時,使用的resultType值依然是User的類型,因爲所有的查詢得到的都可以是一系列的數據,所以查詢多條數據時,無需告訴MyBatis結果將是List集合,只需要告訴它List集合內部的元素是哪種類型就可以了:

<select id="findAll"  resultType="cn.tedu.mybatis.User">

SELECT  id, username,password, age,phone, email  FROM  t_user

</select>

例如:**獲取當前表中的數據的數量**:

Integer countById();

在配置映射時,需要注意:無論多麼簡單的返回值類型,都必須配置resultType。

<select id="countById" resultType="java.lang.Integer">

SELECT COUNT(id) FROM t_user

</select>

### 3. 關於別名

在數據表中添加is_delete INT字段,用於表示該數據是否標記爲刪除:

ALTER TABLE t_user ADD COLUMN is_delete INT;

執行以上代碼後,數據表中會出現新的is_delete字段,原有的各數據的該字段值均爲NULL,再執行:

UPDATE t_user SET is_delete=0;

則可以把所有數據的is_delete都設爲0。

當數據表的結構發生變化時,實體類cn.tedu.mybatis.User和配置文件也應該一併調整:

在增加時,需要注意填寫字段名與屬性名:

is_delete  與 #{isDelete}

對應關係應該是:

               

在查詢操作中,需要添加新的字段並定義別名:is_delete as isDelete

對應關係是:

               

4. MyBatis中的動態SQL

#### 4.1. 基本概念

在配置映射時,可以添加例如<if>、<foreach>等節點,可以根據參數的不同,從而生成不同的SQL語句。

#### 4.2. <foreach>

例如有某個需求:批量刪除用戶的數據,需要執行的SQL語句例如:

DELETE FROM t_user WHERE id=3 OR id=4 OR id=5;

通常,更推薦使用IN關鍵字:

DELETE FROM t_user WHERE id IN (3,4,5)

以上SQL語句中,IN右側的括號中的id的數量是未知的,可能由用戶的操作來決定。這就需要使用動態SQL來實現。

在MyBatis的應用中,如果需要實現以上功能,首先,還是先設計抽象方法:

Integer deleteByIds(Integer[] ids);

在以上抽象方法中,參數既可以使用數組,也可以使用`List`集合來表示。

配置映射例如:

<delete id="deleteByIds">

DELETE FROM t_user

WHERE

id IN (

<foreach collection="array"

item="id" separator=",">

#{id}

</foreach>

)

</delete>

關於`<foreach>`節點中各屬性的配置:

- collection:被遍歷的集合,當對應的抽象方法只有1個參數時,取值將根據參數的類型來選擇`array`或`list`;當對應的抽象方法有多個參數時,取值應該是@Param註解中的名稱

- item:遍歷過程中元素的名稱;

- seperator:分隔符,例如在`IN`語句中,分隔符應該是逗號`,`;

- open和close:遍歷生成的代碼區域左側與右側的值,例如:當沒有在<foreach>外部使用括號框住時,可以配置open="("`和close=")"`。

#### 4.3. <if>

使用<if>標籤可以實現SQL語句中的判斷,例如當某參數有值或沒有值時,如何執行後續的SQL語句:

<select id="findUserList" resultType="cn.tedu.mybatis.User">

SELECT * FROM  t_user

<if test="where != null">

WHERE ${where}

</if>

<if test="orderBy != null">

ORDER BY ${orderBy}

</if>

<if test="offset != null">

LIMIT #{offset}, #{count}

</if>

</select>

 

**以上代碼幾乎可以實現任何的單表且不包含聚合函數的查詢,但是,並不推薦在實際開發中這樣使用,會造成效率低下、浪費內存資源等相關問題。**

 

#### 4.4. 關於#{}和${}佔位符

在MyBatis中支持#{}和${}佔位符。

在SQL語句中,可以填寫?實現預編譯的位置,需要使用#{}佔位符,例如:

DELETE FROM t_user WHERE id=?

而不可以寫`?`的位置,其內容也表現爲SQL語句中的部分語法,需要使用${}佔位符。

使用#{}在執行時是預編譯的,而${}在執行時只是單純的通過字符串拼接形成最終的SQL語句,則可能存在SQL注入的風險。

 

### 5.關於<resultMap>

#### 5.1 使用需求

在設計查詢時,<select>節點必須配置resultType或resultMap其中的一項。通常,只有在多表查詢時,才需要使用resultMap。

#### 5.2. 準備工作

創建新的數據表,表示**部門信息表**:

CREATE TABLE t_department (

did INT AUTO_INCREMENT,

name VARCHAR(20) NOT NULL,

PRIMARY KEY (did)

) DEFAULT CHARSET=UTF8;

然後,應該創建與之對應的實體類`cn.tedu.mybatis.Department`:

public class Department {

private Integer did;

private String name;

// SET/GET/toString

}

由於**部門信息表**應該與**用戶數據表**存在關聯,則需要在**用戶信息表**中添加新的字段,用於表示每個用戶所歸屬的部門:

ALTER TABLE t_user ADD COLUMN did INT;

最後,完善一下測試數據:

INSERT INTO t_department (name) VALUES ('軟件研發部'), ('市場部'), ('人力資源部');

以及**用戶數據表**中的數據:

UPDATE t_user SET did=3 WHERE id=4;

UPDATE t_user SET did=1 WHERE id=5;

UPDATE t_user SET did=1 WHERE id=9;

假設存在需求:**顯示某用戶的詳情,其中,部門應該顯示部門的名稱,而不是部門的id值**,則SQL語句應該是:

SELECT  id, username, password, age, phone, email, name

FROM  t_user

LEFT JOIN  t_department

ON  t_user.did=t_department.did

WHERE t_user.id=5

在使用MyBatis時,首先,應該設計抽象方法:

?? findUserById(Integer id);

可以發現,並沒有某個實體類能夠存儲以上查詢結果,在處理這種關聯查詢時,應該在項目中創建對應的VO類(Value Object),通常,VO類都是與實際的查詢需求相對應的:

所以,關於這個查詢功能對應的抽象方法應該是:

UserVO findUserById(Integer id);

> 實體類與VO類從類的結構上來說,是高度相似的,區別在於:實體類是與數據表結構相對應的,VO類是與查詢結果和實際使用相對應的。

然後,配置映射:

<select id="findUserById" resultType="cn.tedu.mybatis.UserVO">

SELECT id, username,password, age,phone, email,

name AS departmentName

FROM  t_user

LEFT JOIN t_department

ON  t_user.did=t_department.did

WHERE t_user.id=#{id}

</select>

假設存在需求:**顯示某部門的詳情,其中,應該包括該部門的所有員工**,則SQL語句應該是:

SELECT

id, username, password, age, phone, email,

name AS departmentName

FROM t_department

LEFT JOIN t_user

ON t_user.did=t_department.did

WHERE t_department.did=1

如果部門id爲1的有多個用戶,在執行SQL查詢後,將得到多條查詢結果,但是,需求本身是**顯示某部門的詳情**,應該只有1個查詢結果!

首先,還是應該先創建對應的VO類cn.tedu.mybatis.DepartmentVO。

則查詢時的抽象方法應該是:

DepartmentVO findDepartmentById(Integer did);

配置映射:

<!-- 當前resultMap用於指導mybatis將多條查詢結果封裝到同1個對象中 -->

<resultMap id="DepartmentVOMap" type="cn.tedu.mybatis.DepartmentVO">

<!-- id節點:用於配置主鍵 -->

<!-- column屬性:查詢結果中的列名 -->

<!-- property屬性:resultMap中type對應的數據類型中的名稱 -->

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

<!-- result節點:用於配置主鍵以外的其它字段的查詢 -->

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

<!-- collection節點:用於配置1對多關係的數據,也就是List類型的屬性 -->

<!-- ofType屬性:List集合中的元素類型 -->

<collection property="users" ofType="cn.tedu.mybatis.User">

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

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

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

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

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

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

</collection>

</resultMap>



<select id="findDepartmentById" resultMap="DepartmentVOMap">

SELECT did, id, username, password, age, phone, email, name

FROM t_department

LEFT JOIN t_user

ON t_user.did=t_department.did

WHERE t_department.did=#{did}

</select>

 

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