Mybatis操作數據庫的兩種方式:Mapper代理模式

1.Mapper代理模式的特點

程序員沒有寫接口的子實現——直接獲取數據庫的數據

因為Mybatis定義了一套規則,對方法進行了實現,程序員只要遵循這套方法就可以直接使用

2.如何實現Mapper代理模式

步驟:

1.創建一個dao接口,在接口中寫增刪改查的方法

2.創建一個子清單文件,且子清單文件中的命名空間必須namespace="包名.接口名"

3.在子清單文件中:select\insert\update\delete 節點id為接口中的方法名稱

? ? ? ? 方法的參數對應parameterType,返回值對應resultType

4.在總清單文件引入子清單文件

5.在需要的地方:接口類型 jdk動態代理對象=sqlSqssion.getMapper(接口類型.class)

? ? ? ?返回類型 返回類型對象?jdk動態代理對象.調用目標方法();

程序員沒有寫接口子實現,就能獲得數據庫數據

configruation.xml:

?設置類別名,方便書寫:

    <typeAliases><!--設置別名--><typeAlias type="org.example.entity.User" alias="User"/></typeAliases>

注意添加子清單文件?

 <mapper resource="mapper/userMapper.xml"/>

增刪改實現?

xxMapper.java

    public int addUser(User user);public int deleteUser(Integer id);public int updateUser(User user);

userMapper.xml

<mapper namespace="org.example.dao.UserMapper"><!--插入用戶--><insert id="addUser"parameterType="User">insert into t_user(user_name,user_password,address)values(#{name},#{password},#{address});</insert><!--刪除用戶--><delete id="deleteUser"parameterType="java.lang.Integer">delete from t_userwhere id=#{id};</delete><!--根據id更新用戶--><update id="updateUser"parameterType="org.example.entity.User">update t_user setuser_name=#{name},user_password=#{password},address=#{address}whereid=#{id}</update>
</mapper>

單元測試:

@Testpublic void addUser() {//假數據User user=new User();user.setName("add");user.setPassword("654321");user.setAddress("測試用例|mapper.addUser()");Integer rowAffect=0;SqlSession sqlSession=null;try{sqlSession= MybatisUtil.getSession();//接口類型, jdk動態代理對象=sqlSession.getMapper();UserMapper userMapper=sqlSession.getMapper(UserMapper.class);rowAffect= userMapper.addUser(user);sqlSession.commit();}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {if(sqlSession!=null){sqlSession.close();}}System.out.println(rowAffect);}@Testpublic void deleteUser() {Integer rowAffect=0;SqlSession sqlSession=null;try{sqlSession= MybatisUtil.getSession();//接口類型, jdk動態代理對象=sqlSession.getMapper();UserMapper userMapper=sqlSession.getMapper(UserMapper.class);rowAffect= userMapper.deleteUser(9);sqlSession.commit();}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {if(sqlSession!=null){sqlSession.close();}}System.out.println(rowAffect);}@Testpublic void updateUser() {//假數據User user=new User();user.setId(9);user.setName("update");user.setPassword("654321");user.setAddress("測試用例|mapper.updateUser()");Integer rowAffect=0;SqlSession sqlSession=null;try{sqlSession= MybatisUtil.getSession();//接口類型, jdk動態代理對象=sqlSession.getMapper();UserMapper userMapper=sqlSession.getMapper(UserMapper.class);rowAffect= userMapper.updateUser(user);sqlSession.commit();}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {if(sqlSession!=null){sqlSession.close();}}System.out.println(rowAffect);}

?效果展示:

addUser

updateUser?

?deleteUser

總結:

mybatis 的 mapper代理模式和原生api相比,可以不寫接口的具體實現類。

mapper模式是純接口調用,因為有接口的存在,可以使用jdk動態代理為其動態生成實現類。

jdk生成的實現類和接口之間是實現和被實現的關系,

jdk生成的實現類是實現類,也是代理類,其創建動態代理對象,通過代理對象.目標方法的方式,即invacationHandler調用invoke(),來進行實現。

查詢:根據id獲取用戶信息

xxMapper.java

  public User findUserById(Integer id);

userMapper.xml

    <!--根據id獲得用戶信息--><select id="findUserById"resultType="org.example.entity.User"parameterType="java.lang.Integer">selectid,user_name as name,user_password as password,addressfrom t_userwhere id = #{id}</select>

單元測試:

    @Testpublic void findUserById() {User user=null;SqlSession sqlSession=null;try{sqlSession= MybatisUtil.getSession();//接口類型, jdk動態代理對象=sqlSession.getMapper();UserMapper userMapper=sqlSession.getMapper(UserMapper.class);user= userMapper.findUserById(1);sqlSession.commit();}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {if(sqlSession!=null){sqlSession.close();}}System.out.println(user);}

結果展示:

查詢:根查所有用戶List

xxMapper.java

    //返回list結構public List<User> findAllUser1();public List<Map<String,Object>> findAllUser2();

userMapper.xml

<!--查所有返回list<User>--><select id="findAllUser1"resultType="User">selectid,user_name as name,user_password as password,addressfrom t_user</select><!--查所有返回list<Map>--><select id="findAllUser2"resultType="java.util.Map">selectid,user_name,user_password,addressfrom t_user</select>

單元測試:

@Testpublic void findAllUser1() {List<User> userList=new ArrayList<User>();SqlSession sqlSession=null;try{sqlSession= MybatisUtil.getSession();//接口類型, jdk動態代理對象=sqlSession.getMapper();UserMapper userMapper=sqlSession.getMapper(UserMapper.class);userList= userMapper.findAllUser1();sqlSession.commit();}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {if(sqlSession!=null){sqlSession.close();}}for(User user:userList){System.out.println(user);}}@Testpublic void findAllUser2() {List<Map<String,Object>> userList=new ArrayList<Map<String, Object>>();SqlSession sqlSession=null;try{sqlSession= MybatisUtil.getSession();//接口類型, jdk動態代理對象=sqlSession.getMapper();UserMapper userMapper=sqlSession.getMapper(UserMapper.class);userList= userMapper.findAllUser2();sqlSession.commit();}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {if(sqlSession!=null){sqlSession.close();}}for(Map<String,Object> user:userList){System.out.println(user);}}

結果展示:

?

查詢:根查所有用戶Map

xxMapper.java

    //返回map結構@MapKey("id")public Map<Integer,User> findAllUser3();@MapKey("id")public Map<Integer,Map<String,Object>> findAllUser4();

userMapper.xml

    <!--查所有返回Map<User>--><select id="findAllUser3"resultType="User">selectid,user_name as name,user_password as password,addressfrom t_user</select><!--查所有返回Map<Map>--><select id="findAllUser4"resultType="java.util.Map">selectid,user_name,user_password,addressfrom t_user</select>

單元測試:

    @Testpublic void findAllUser3() {Map<Integer,User> userMap= new HashMap<Integer, User>();SqlSession sqlSession=null;try{sqlSession= MybatisUtil.getSession();//接口類型, jdk動態代理對象=sqlSession.getMapper();UserMapper userMapper=sqlSession.getMapper(UserMapper.class);userMap= userMapper.findAllUser3();sqlSession.commit();}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {if(sqlSession!=null){sqlSession.close();}}for(Integer key:userMap.keySet()){System.out.println(key+" "+userMap.get(key));}}@Testpublic void findAllUser4() {Map<Integer,Map<String,Object>> userMap= new HashMap<Integer, Map<String,Object>>();SqlSession sqlSession=null;try{sqlSession= MybatisUtil.getSession();//接口類型, jdk動態代理對象=sqlSession.getMapper();UserMapper userMapper=sqlSession.getMapper(UserMapper.class);userMap= userMapper.findAllUser4();sqlSession.commit();}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {if(sqlSession!=null){sqlSession.close();}}for(Integer key:userMap.keySet()){System.out.println(key+" "+userMap.get(key));}}

結果展示:

3.JDKProxy模擬Mapper代理實現

Mybatis的Mapper代理模式,可以通過接口使用jdk的代理機制實現自動代理。(jdk的動態代理是基于接口的,而此處的xxMapper接口即為被代理的對象接口)

? ? ? ? 本質上xxMapper并沒有實現類,jdk的動態代理也沒有對所謂的老方法進行代理。

程序員不需要寫接口的具體實現,只要根據規則定義好接口,即可使用相關功能。

我們使用JDKProxy類模擬Mybatis實現getMapper()的方法,理解其中的內部實現。

package org.example.proxy;import org.example.proxy.handler.MapperHandler;import java.lang.reflect.Proxy;public class JDKProxy {/*** 根據接口獲取代理對象* @param clazz* @return*/public static Object getMapper(Class clazz){Object proxyObject=null;/*** 參數一: 類加載器* 參數二: 接口數組* 參數三: InvocationHandler 接口的回調*/proxyObject= Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},new MapperHandler());return proxyObject;}}

這里雖然使用了動態代理,但是并不是動態代理的常規用法,這里只有代理對象的接口,且該接口沒有任何實現。動態代理在這里的目的并不是調用老方法做切面設計,而是獲取方法信息:參數列表、返回值等,進行判斷,根據方法選擇調用mybatis的原生api。所以Mapper代理模式底層調用的還是Mybatis的原生api,??其只是通過動態代理實現了一個默認實現版本,來較少程序員的代碼量。

關于JDK的第三個參數?InvocationHandler 接口的回調,我們構建實現類MapperHandler,其實現InvocationHandler接口。具體來說就是:

package org.example.proxy.handler;import org.example.entity.User;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class MapperHandler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object returnValue=null;System.out.println("由Mybatis根據若干信息選擇執行對應的api");/*** mybatis 根據method能得到目標方法的返回值,方法名,參數類型* if判斷返回的數據為一個值,則調用mybatis的原生api  selectOne方法* if判斷返回的數據為多個值時,看返回類型是否為List,則調用mybatis的原生 selectList()方法* if判斷返回的數據為多個值時,看返回類型是否為Map,則調用mybatis的原生 selectMap()方法* if判斷為insert/delete/update時,調用原生api  insert()  delete()  update()*///eg:執行selectOne()User user=new User();user.setId(1);user.setName("aaa");user.setPassword("123456");user.setAddress("諾亞方舟");returnValue=user;return returnValue;}
}

?使用單元測試來對方法進行測試:

package org.example.proxy;import org.example.dao.UserMapper;
import org.example.entity.User;
import org.junit.Test;import static org.junit.Assert.*;public class JDKProxyTest {@Testpublic void getMapper() {//接口類型 jdk動態代理對象=sqlSession.getMapper(接口類型.class);UserMapper userMapper=(UserMapper) JDKProxy.getMapper(UserMapper.class);User user=userMapper.findUserById(1);System.out.println(user);}
}

值得注意的是,getMapper中我們并沒有寫具體的實現邏輯,因為測試的是findUserById()方法,我們在getMapper()中返回了一個假數據,用來做測試。而Mybatis則根據邏輯寫了具體的實現。?

4.Mybatis 中getMapper的實現

重點:模擬getMapper的實現

public Object execute(SqlSession sqlSession, Object[] args) {Object param;Object result;switch (this.command.getType()) {case INSERT:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));break;case UPDATE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.update(this.command.getName(), param));break;case DELETE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));break;case SELECT:if (this.method.returnsVoid() && this.method.hasResultHandler()) {this.executeWithResultHandler(sqlSession, args);result = null;} else if (this.method.returnsMany()) {result = this.executeForMany(sqlSession, args);} else if (this.method.returnsMap()) {result = this.executeForMap(sqlSession, args);} else if (this.method.returnsCursor()) {result = this.executeForCursor(sqlSession, args);} else {param = this.method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(this.command.getName(), param);}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + this.command.getName());}if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");} else {return result;}}

基本流程:getMapper()接口——>創建Proxy對象——>execute()實現具體實現???

特別的關于select()方法

其中method.hasResultHandler()來實現自定義的方法處理

還可以匹配不同的返回類型,其中游標模式Cursor不怎么用了

5.總結

接口定義:userDao 和UserMapper一樣的寫法不同,用來定義方法接口

? ? ? ? 其可以自己寫實現類也可以通過mapper() 方式來操作數據

總清單文件:

????????數據庫配置

????????子清單文件

????????????????模板統一

????????????????原生寫法的子清單文件 namespace和增刪改查節點信息任意配置

? ? ? ? ? ? ? ? mapper寫法的子清單 namespace="包名.接口"

? ? ? ? ? ? ? ? ? ? ? ? 接口方法名和節點id一致

? ? ? ? ? ? ? ? ? ? ? ? 接口方法參數和節點parameterType一樣

? ? ? ? ? ? ? ? ? ? ? ? 接口的返回值跟節點resultType一樣

mybatis自己的api解析xml來構建sqlSessionFactory,用于生產SqlSession

使用sqlSession來做增刪改查

mapper接口方法底層還是原生api? api結合jdk動態代理

原生api

  • insert() delete() update()
  • selectOne()? selectList() selectMap()
  • select() 自定義返回的數據結構(策略+回調)
  • 返回結果類型豐富

6.設計模式:

代理模式:MapperProxy實現jdk動態代理

7.補充

什么是面向接口編程?

如select面向接口、jdbc面向接口和spring面向接口都有相關的面向對象接口編程,其目的是實現解耦操作

  • 面向接口編程是一種編程范式,它強調的是在設計軟件應用時,應該先定義接口,然后再實現接口。這種方式有很多優點,包括提高代碼的可讀性、可維護性和可擴展性,以及降低代碼之間的耦合度。
  • 接口是一種契約,它定義了一組方法,這些方法應該在實現接口的類中實現。接口本身并不包含任何實現細節,它只是定義了一種規范,規定了實現接口的類應該做什么,而不是怎么做。

摘自:《17.Spring 面向接口編程 - 知乎》

select()也是面向接口編程提供select接口但沒有實現,其具體的實現在handler中去處理。實現了接口和實現的分離。


?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/10849.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/10849.shtml
英文地址,請注明出處:http://en.pswp.cn/web/10849.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java項目之英語知識應用網站源碼(springboot+vue+mysql)

風定落花生&#xff0c;歌聲逐流水&#xff0c;大家好我是風歌&#xff0c;混跡在java圈的辛苦碼農。今天要和大家聊的是一款基于springboot的英語知識應用網站。項目源碼以及部署相關請聯系風歌&#xff0c;文末附上聯系信息 。 項目簡介&#xff1a; 英語知識應用網站的主要…

【免費】AME最新Adobe Media Encoder電腦軟件安裝包2024-2018支持WIN和MAC

Adobe MediaEncoder「Me」2024是一款功能強大的轉碼和媒體處理軟件&#xff0c;它不僅能輕松應對各種媒體文件的編碼和導出需求&#xff0c;還支持多種視頻格式和分辨率&#xff0c;讓你的視頻處理變得更加高效。此外&#xff0c;該軟件界面簡潔明了&#xff0c;操作簡便&#…

【一步一步了解Java系列】:了解Java與C語言的運算符的“大同小異”

看到這句話的時候證明&#xff1a;此刻你我都在努力~ 加油陌生人~ 個人主頁&#xff1a; Gu Gu Study ?? 專欄&#xff1a;一步一步了解Java 喜歡的一句話&#xff1a; 常常會回顧努力的自己&#xff0c;所以要為自己的努…

【Element-UI快速入門】

文章目錄 **Element-UI快速入門****一、Element-UI簡介****二、安裝Element-UI****三、引入Element-UI****四、使用Element-UI組件****五、自定義Element-UI組件樣式****六、Element-UI布局組件****七、Element-UI表單組件****八、插槽&#xff08;Slots&#xff09;和主題定制…

【數據結構】排序(一)—— 希爾排序(思路演進版)

目錄 一、常見的排序算法分類 二、常見排序算法的實現 2.1插入排序 2.1.1基本思想 2.1.2直接插入排序 思路 step1.單趟控制 step2.總體控制 代碼實現 測試 特性總結 2.1.3 希爾排序( 縮小增量排序 ) 基本思想 思路演進 &#x1f308;1.代碼實現單組排序&#…

你能堅持二十年如一日地積極試錯嗎?

你能堅持二十年如一日地積極試錯嗎&#xff1f;先說一個大家都耳熟能詳的人物&#xff1a;克里斯托弗哥倫布&#xff0c;他被稱為新大陸的發現者&#xff0c;是具有極高歷史地位的偉大航海家。 但是新大陸本來就不是所謂的“新”大陸&#xff0c;而是在4萬年前從白令海峽遷徙過…

端午節線上活動方案怎么寫?

一年一端午&#xff0c;一歲一安康。 如果您想組織端午活動&#xff0c;卻不知道如何安排&#xff0c;可以看看何策網&#xff0c;有很多案例參考&#xff0c;仿造模板修改即可。 下面分享一個線上端午節活動策劃方案&#xff0c;希望能幫到你&#xff01; 端午節作為祭祖祈…

Qt 實現TCP 協議的斷開重連

在Qt中實現TCP斷開重連&#xff0c;你可以使用QTcpSocket類&#xff0c;并結合QTimer來處理重連邏輯&#xff0c;在Qt中實現TCP斷開后的自動重連功能&#xff0c;通常可以通過以下步驟進行&#xff1a; 1. 初始化QTcpSocket&#xff1a; 首先&#xff0c;需要創建一個QTcpSock…

Docker使用注意事項

docker import 和 docker load 有什么區別&#xff1f; 想要了解 docker load 與 docker import 命令的區別&#xff0c;還必須知道 docker save 與 docker export docker save&#xff1a;將一個鏡像導出為文件&#xff0c;再使用docker load命令將文件導入為一個鏡像&#…

mysql集群NDBcluster引擎在寫入數據時報錯 (1114, “The table ‘ads‘ is full“)

問題描述&#xff1a;mysql集群在寫入數據時&#xff0c;出現上述報錯 問題原因&#xff1a;表數據已滿&#xff0c;一般是在集群的管理節點設置里面datamemory的值太小&#xff0c;當數據量超過該值時就會出現該問題 解決方案&#xff1a; 修改集群管理節點的config.ini里面…

ICode國際青少年編程競賽- Python-4級訓練場-嵌套for循環練習2

ICode國際青少年編程競賽- Python-4級訓練場-嵌套for循環練習2 1、 for i in range(3):Dev.turnRight()for j in range(3):Dev.step(-3)Dev.turnRight()Dev.step(4-2*i)2、 for i in range(6):for j in range(2):Dev.step(2 2 * i)if i > 3: Dev.step(i - 2)Dev.turnRi…

更相減損術求最大公約數

1.定義 更相減損術是出自《九章算術》的一種求最大公約數的算法&#xff0c;它原本是為約分而設計的&#xff0c;但它適用于任何需要求最大公約數的場合。 原文是&#xff1a; 可半者半之&#xff0c;不可半者&#xff0c;副置分母、子之數&#xff0c;以少減多&#xff0c;…

C++小程序:同一路由器下兩臺計算機間簡單通信(2/2)——客戶端

客戶端的程序結構前半部分與服務器端基本相同&#xff0c;后半部分也相對簡單。相關函數的解釋可以參考前文服務器端的內容。有關客戶端的內容除個別地方外&#xff0c;就不再做長篇大論的解釋。強調一點&#xff0c;如果將此程序移到其它電腦上運行&#xff0c;編譯需要releas…

Ciphey無法安裝的解決辦法

安裝過程純屬自己實踐&#xff0c;滿滿干貨 困擾我幾天的問題終于解決了 我看著教程在window上安裝 python3.8/python3.9/python3.10無論如何都安裝不上&#xff0c; 在win10虛擬機仍然安裝不上 可能是我電腦環境問題 解決辦法&#xff1a; 在kali中安裝&#xff0c;但是…

18_文件系統的制作-Ramdisk

文件系統的制作(Ramdisk) 本文介紹如何制作文件系統。另外, 由于Busybox 集合了很多工具,編譯起來也非常方便。在講解制作文件系統的時候,也會介紹 busybox 的編譯和安裝過程;介紹制作文件系統時,會詳細介紹 Ramdisk 、 YAFFS2、JFFS2 及其它文件系統的制作。 1. 根文件系…

列表、字典、集合推導式

文章目錄 前言1.列表推導式&#xff08;List Comprehension&#xff09;:2 字典推導式&#xff08;Dictionary Comprehension&#xff09;3 集合推導式&#xff08;Set Comprehension) 前言 在Python中&#xff0c;列表、字典、集合推導式是一種便捷的語法&#xff0c;用于根據…

第13節 第二種shellcode編寫實戰(2)

在第二種shellcode編寫實戰(1)的基礎上&#xff0c;新增加一個CAPI類&#xff0c;將所有用到的函數都在這個類中做動態調用的處理&#xff0c;這樣使得整個shellcode功能結構更加清晰。 1. 新建類CAPI&#xff08;即api.h和api.cpp兩個文件&#xff09;&#xff1a; api.h&…

#DELPHI BASS庫Windows平臺下,實時更換輸出設備

DELPHI BASS庫Windows平臺下&#xff0c;實時更換輸出設備 #DELPHI BASS庫Windows平臺下&#xff0c;實時更換輸出設備 取自網絡&#xff0c;分享&#xff0c;項目嵌入無損音樂播放后&#xff0c;畫蛇添足的功能分享&#xff01; 直接貼核心代碼&#xff0c;看不明白去看說…

flutter自定義日期選擇器按日、按月、自定義開始、結束時間

導入包flutter_datetime_picker: 1.5.0 封裝 import package:atui/jade/utils/JadeColors.dart; import package:flutter/cupertino.dart; import package:flutter/material.dart; import package:flutter_datetime_picker/flutter_datetime_picker.dart; import package:flut…

景源暢信電商:經營抖店需要電腦嗎?

經營抖店是否需要電腦?這個問題看似簡單&#xff0c;實則關乎著商家的運營效率和成本投入。在當前數字化、網絡化的商業環境中&#xff0c;電腦已經成為了不可或缺的工具。那么&#xff0c;經營抖店究竟是否需要電腦呢?答案是肯定的。 一、高效處理訂單 電腦能夠高效地處理大…