2mybatis(和佔位符與拼接符區別)
6.2.3. 佔位符與拼接符區別
1. 型別處理:
佔位符#{}傳遞引數時會做引數型別處理,
拼接符${}傳遞引數時不會做型別處理只進行字串原樣拼接
2. 安全性:
${}的原樣拼接導致它存在安全漏洞,容易產生SQL注入風險
#{}的型別處理會對引數中存在的SQL敏感字元先轉義然後再對映給SQL,這就不會影響原先的SQL,因此可以有效防止SQL注入。
3. 工作中的應用:
由於拼接符${}存在安全隱患,因此在實際專案儘量使用佔位符#{}
1. 課程計劃
1、高階引數對映和返回值對映(重點)
a) Pojo包裝pojo的引數對映
b) 當結果集列名與pojo屬性名不一致的返回值對映
2、動態sql(重點)
3、關聯查詢結果(重點)
a) 一對一關聯結果
b) 一對多關聯結果
4、Mybatis整合spring
5、逆向工程
2. 事前程式碼準備
今天學習內容的練習主要以MyBatis動態代理的方式訪問編寫訪問資料庫的程式碼,因此參照昨天的工程重新建立一個新工程作為今天程式碼練習的整合,同時需要把一些動態代理需要的目錄、空檔案提前構建好,以方便後面使用。
2.1. 工程程式碼結構(UTF8)
2.2. Mapper對映檔案及對應的介面檔案
OrderMapper.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="cn.itcast.dao.OrderMapper"> <!-- SQL -->
</mapper> |
OrderMapper.java
package cn.itcast.dao;
public interface OrderMapper {
} |
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="cn.itcast.dao.UserMapper"> <!-- SQL -->
</mapper> |
UserMapper.java
package cn.itcast.dao;
public interface UserMapper {
} |
2.3. POJO定義
1. 將昨天工程中的【User.java】拷貝到pojo的包下
2. 把【資料\03.pojo\Order.java】拷貝到pojo的包下。
2.4. 配置檔案和屬性檔案
1. 把昨天工程中Source Folder【config】下的全部配置檔案和屬性檔案拷貝過來。
2. 走查一下配置檔案,把沒有必要的註釋刪除,需要修改的配置修改。
<?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" />
<!-- 資料庫環境的配置 --> <environments default="dev"> <!-- 開發資料庫環境的配置 --> <environment id="dev"> <!-- 事務管理的配置 --> <transactionManager type="JDBC"/> <!-- 資料來源配置:driver, url, username, password --> <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> <!-- 通過包掃描DAO介面的方式批量載入對映檔案 --> <package name="cn.itcast.dao"/> </mappers> </configuration> |
2.5. 測試類
package mybatis2;
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 org.junit.Before; import org.junit.Test;
public class MyTest {
private SqlSessionFactory sqlSessionFactory;
// 測試初始化函式 @Before public void init() throws Exception { // 讀取配置檔案 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根據主配置檔案建立會話工廠 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); }
// 測試通過介面載入與之對應的對映檔案 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { sqlSession = sqlSessionFactory.openSession(); // 建立DAO的動態代理物件
// 執行資料庫操作
} catch(Exception ex) { ex.printStackTrace(); throw ex; } finally { sqlSession.close(); } } } |
3. 高階輸入對映(重點)
3.1. 綜合查詢
綜合查詢在實際業務需求中十分常見。綜合查詢頁面往往是包含多種查詢維度的條件,比如上面的截圖就是淘寶的訂單查詢頁面。我們看到查詢訂單的條件包括:訂單基本資訊、使用者資訊、售後資訊。
如果持久層使用MyBatis,應該如何接收引數呢?
3.1.1. 需求
查詢:使用者名稱是姓王的並且手機是135開頭的,訂單狀態是【待發貨】的訂單資訊。
【SQL語句】有訂單又有使用者,SQL應該是一個關聯查詢:
SELECT o.orderId, o.userId, o.orderStatus, o.goodsId, o.createDateTime FROM order1 o, user u WHERE u.name LIKE '王%' AND u.mobile LIKE '135%' AND o.orderStatus = '02' AND o.userId = u.userId |
3.1.2. 定義傳遞綜合查詢條件的包裝型別
因為查詢條件是多方面的業務條件,所以我們需要定義一個專門用來傳遞條件引數的實體類,它可以包含各種各樣的查詢條件,比如可以包含簡單型別、物件、陣列、List等。
通常我們把儲存查詢條件的包裝類稱為QueryVo.java,其實就是普通的java bean。
本示例中,我們需要訂單基本資訊和使用者資訊作為查詢條件,所以包括了使用者資訊和訂單資訊。
【QueryVo.java】
public class QueryVo { // 使用者資訊 private User user;
// 訂單資訊 private Order order;
setter/getter。。。。。 } |
思考一個問題:上面的Vo定義我們為什麼不將使用者的屬性和訂單的屬性混合到一起放到這個Vo中呢?
混合到一起是沒有錯的,而且引數對映時也簡單了,但是會讓Vo變得很混亂,分不清屬性誰是誰的,這就容易出現bug,因此我們應該採用面向物件的設計思想——封裝,即封裝成一個User物件和一個Order物件,這樣就不混亂了,避免了出錯的可能。
3.1.3. SQL對映檔案
【OrderMapper.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="cn.itcast.dao.OrderMapper"> <!-- SQL --> <!-- 根據QueryVo查詢訂單資訊 --> <select id="findOrderByQueryVo" parameterType="cn.itcast.pojo.QueryVo" resultType="cn.itcast.pojo.Order"> SELECT o.orderId, o.userId, o.orderStatus, o.goodsId, o.createDateTime FROM order1 o, user u WHERE u.name LIKE #{user.name} AND u.mobile LIKE #{user.mobile} AND o.orderStatus = #{order.orderStatus} AND o.userId = u.userId </select> </mapper> |
3.1.4. 定義介面
【OrderMapper.java】
package cn.itcast.dao;
import cn.itcast.pojo.QueryVo; import cn.itcast.pojo.Order;
public interface OrderMapper {
// 根據綜合查詢條件查詢訂單資訊 public Order findOrderByQueryVo(QueryVo vo) throws Exception; } |
3.1.5. 客戶端測試程式
【MyTest.java】
// 測試根據QueryVo查詢訂單資訊 @Test public void test1() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 建立DAO的動態代理物件 OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); User user = new User(); Order order = new Order(); user.setName("王%"); user.setMobile("135%"); order.setOrderStatus("02"); QueryVo vo = new QueryVo(); vo.setOrder(order); vo.setUser(user); // 執行資料庫操作 List<Order> orderList = orderMapper.findOrderByQueryVo(vo); System.out.println(orderList); sqlSession.close(); } |
<SQL對映規範>
·引數對映規範(四)
用包裝型別傳遞引數時,parameterType="包裝型別",佔位符或拼接符的變數名等於包裝類的屬性.屬性.屬性...
4. 高階輸出對映(重點)
Mybatis的返回值對映能將結果集對映成Java物件的關鍵是:列名==屬性名
但如果列名≠屬性名怎麼辦?確切點說如果列名不等於屬性名時我們如何來做對映呢?
解決的辦法:就是手動定義返回值對映。
4.1. 需求
根據訂單id查詢資料庫中order2表的訂單資訊。但order2表的最大問題就是欄位名是以下劃線分割的,這與Order的pojo中的屬性名不一致。
4.2. 手動定義返回值對映
4.2.1. 定義返回值對映
【OrderMapper.xml】
<說明>
專案 |
解釋 |
<resultMap> |
用於自定義返回值對映的規則,即自定義哪個列名對應哪個屬性名。 |
id |
返回值對映的唯一標識 |
type |
返回值對映中java物件的型別 |
<result> |
用於定義一個返回值對映規範的標籤,一個<resultMap>可以包含多個<result> |
column |
返回值對映中的列名 |
property |
返回值對映中的屬性名 |
<id> |
用於定義返回值對映中主鍵列名與欄位名的對映關係。用法和<result>一模一樣,只是增加可讀性。 |
<SQL對映示例>
<!-- 自定義返回值對映的規範 --> <resultMap type="cn.itcast.pojo.Order" id="order2ResultMap"> <id column="order_id" property="orderId"/> <!-- <result column="order_id" property="orderId"/> --> <result column="user_id" property="userId"/> <result column="order_status" property="orderStatus"/> <result column="goods_id" property="goodsId"/> <result column="create_date_time" property="createDateTime"/> </resultMap> |
有了這個自定義了規範,即使列名≠屬性名,MyBatis也可以利用這個自定義的規範進行返回值映射了。
4.2.2. SQL
【OrderMapper.xml】
<說明>
專案 |
解釋 |
resultMap |
引用返回值對映的自定義規範 |
<SQL對映示例>
<!-- 根據id查詢order2表的訂單資訊 --> <select id="findOrder2ById" parameterType="String" resultMap="order2ResultMap"> SELECT order_id, user_id, order_status, goods_id, create_date_time FROM order2 WHERE order_id = #{orderId} </select> |
4.2.3. 介面
【OrderMapper.java】
// 根據id查詢訂單資訊 public Order findOrder2ById(String orderId) throws Exception; |
4.2.4. 客戶端測試程式
【MyTest.java】
// 測試根據id查詢訂單資訊 @Test public void test1() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 建立DAO的動態代理物件 OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); // 執行資料庫操作 Order orderInfo = orderMapper.findOrder2ById("6d081184-433e-11e7-ab09-448a5b6dba5c"); System.out.println(orderInfo); sqlSession.close(); } |
4.3. 自定義返回值對映的替代方案
利用SQL欄位的別名滿足返回值對映的列名==POJO屬性名的要求。
4.3.1. SQL
【OrderMapper.xml】
<!-- 根據id查詢order2表的訂單資訊2 --> <select id="findOrder2ById2" parameterType="String" resultType="cn.itcast.pojo.Order"> SELECT order_id as orderId, user_id as userId, order_status as orderStatus, goods_id as goodsId, create_date_time as createDateTime FROM order2 WHERE order_id = #{orderId} </select> |
4.3.2. 介面
【OrderMapper.java】
// 根據id查詢訂單資訊2 public Order findOrder2ById2(String orderId) throws Exception; |
4.3.3. 客戶端測試程式
【MyTest.java】
// 測試根據id查詢訂單資訊 @Test public void test2() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 建立DAO的動態代理物件 OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); // 執行資料庫操作 // Order orderInfo = orderMapper.findOrderById("6d081184-433e-11e7-ab09-448a5b6dba5c"); Order orderInfo = orderMapper.findOrder2ById2("6d081184-433e-11e7-ab09-448a5b6dba5c"); System.out.println(orderInfo); sqlSession.close(); } |
5. 動態SQL(重點)
本章內容針對SQL對映,賦予SQL對映更加強大靈活的特性,讓SQL對映更能適應複雜多變的引數請求。因此本節內容比較雜,內容也較多,但每塊內容都相對獨立,不難掌握。
5.1.1. 動態SQL條件
5.1.2. <if>標籤
【OrderMapper.xml】
<說明>
專案 |
解釋 |
<if> |
用於判斷它包含的SQL語句是否需要新增。 |
test |
判斷的邏輯條件,and表示與,or表示或, true時新增,false時忽略。 |
<SQL對映示例>
<!-- 根據動態條件查詢訂單資訊 --> <select id="findOrderByQueryVo2" parameterType="cn.itcast.pojo.QueryVo" resultType="cn.itcast.pojo.Order"> SELECT o.orderId, o.userId, o.orderStatus, o.goodsId, o.createDateTime FROM order1 o, user u WHERE 1 = 1 <if test="user.name != null and user.name != ''"> AND u.name LIKE #{user.name} </if> <if test="user.mobile != null and user.mobile != ''"> AND u.mobile LIKE #{user.mobile} </if> <if test="order.orderStatus != null and order.orderStatus != ''"> AND o.orderStatus = #{order.orderStatus} </if> and o.userId = u.userId </select> |
【OrderMapper.java】
// 根據動態查詢條件查詢訂單資訊 public List<Order> findOrderByQueryVo2(CustomQueryVo vo) throws Exception; |
【MyTest.java】
// 測試根據動態查詢條件查詢訂單資訊 @Test public void test1() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 建立DAO的動態代理物件 OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); User user = new User(); Order order = new Order(); user.setName("王%"); user.setMobile("135%"); order.setOrderStatus("02"); QueryVo vo = new QueryVo(); vo.setOrder(order); vo.setUser(user); // 執行資料庫操作 // List<Order> orderList = orderMapper.findOrderByQueryVo(vo); List<Order> orderList = orderMapper.findOrderByQueryVo2(vo); System.out.println(orderList); sqlSession.close(); } |
if標籤不僅僅用於動態條件,SQL中所有接收引數的部分都可以通過if判斷決定是否要追加,比如udate更新中可以實現更新專案的動態更新:
<update id="updateUser" parameterType="cn.itcast.pojo.User"> UPDATE user SET
|