MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了google code,并且改名為MyBatis,2013年11月遷移到Github, ●【GitHub】 GitHub就是一個互聯網上的超大SVN庫,里面維護著許多開源專案的代碼,任何人都可以把自己好多專案代碼放上去共享,同時接受其他人的共同開發, 2.2.什么是MyBatis MyBatis是使用java語言撰寫的一個優秀的持久層框架,是對JDBC操作資料庫的程序進行了全新的封裝,解決了JDBC中的問題, Mybatis框架隱藏了jdbc繁雜的業務無關代碼: ·手動注冊驅動、創建connection、statement ·手動設定引數以及引數的順序 ·手動遍歷結果集 使開發者只需關注SQL怎么寫, 3.JDBC的問題 JDBC是原生的資料庫開發,JDBC對于單機版的軟體或者一個小辦公室的小系統都還是可以應付的,畢竟業務簡單,資料量小,程式規模都不大,修改、編譯、發布都很容易,但隨著業務發展這樣的IT技術不能滿足要求了,逐漸的暴露出一些問題, public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //加載資料庫驅動 Class.forName("com.mysql.jdbc.Driver"); //通過驅動管理類獲取資料庫鏈接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root"); //定義sql陳述句 ?表示占位符 String sql = "select * from user where username = ? and age = ?"; //獲取預處理statement preparedStatement = connection.prepareStatement(sql); //設定引數,第一個引數為sql陳述句中引數的序號(從1開始),第二個引數為設定的引數值 preparedStatement.setString(1, "王五"); preparedStatement.setInt(2, 18); //向資料庫發出sql執行查詢,查詢出結果集 resultSet = preparedStatement.executeQuery(); List<User> userList = new ArrayList<User>(); //遍歷查詢結果集 while(resultSet.next()){ User user = new User(); user.setUserId(resultSet.getInt("id")); user.setName(resultSet.getString("username")); userList.add(user); } } catch (Exception e) { e.printStackTrace(); }finally{ //釋放資源 if(resultSet!=null){ try { resultSet.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(preparedStatement!=null){ try { preparedStatement.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 從上面的問題總結如下: 1.資料庫連接的創建、釋放頻繁造成系統資源浪費,缺乏有效管理,非常影響系統性能, 如果使用資料庫連接池可解決此問題, 2.程式中存在硬編碼:(硬編碼就是寫死在程式中的固定值) 1)資料庫連接字串:換資料庫就要改代碼,就要重新編譯發布,維護壓力增大, 2)SQL定義字串:SQL修改的可能性較大,修改SQL就要重新編譯發布,維護壓力增大, 3)傳引數時的引數位置:引數必須按照先后順序設定到對應的SQL條件上,十分不靈活, 4)結果集中欄位名字串:欄位名變化就要改代碼,就要重新編譯發布,維護壓力增大, 3.1.如何解決JDBC的問題 框架發明的目的之一就是為了解決jdbc問題,比如:Hibernate、MyBatis等,這些框架不僅可以解決問題還可以大大簡化開發,讓開發人員更好的集中精力實作業務邏輯, 4.MyBatis主要的內容 MyBatis最重要的就是寫好SQL,包括寫好SQL陳述句和寫好SQL映射, ·SQL陳述句就是標準的SQL陳述句(可以在當前選用的資料庫產品下,根據需求使用該產品下的SQL函式) ·SQL映射包括:引數映射和回傳值映射(回傳值只針對查詢,增刪改是沒有回傳值的) ●【引數映射】(也叫做【輸入映射】) MyBatis將java物件傳入給SQL陳述句引數的程序, ●【回傳值映射】(也叫做【輸出映射】) MyBatis將SQL查詢的結果集處理成一個java物件并回傳給java程式的程序, ●【java物件】 如果是單個引數映射,可以是java簡單型別變數:int、long、float、String、Integer、Long、Boolean、Float等,引數值可以映射給sql, 如果是多個引數映射,必須是用java bean,有一個名詞叫pojo(Plain Ordinary Java Object),里面有許多屬性以及它們的getter/setter方法,將多個引數封裝到pojo物件里面,一起映射給sql,Java bean和pojo沒有區別,就是換種叫法而已, ● SQL陳述句以及映射寫在xml或注解中, 5.MyBatis訪問資料庫的核心構成 我們通過Hibernate與MyBatis訪問資料庫核心構成的對比來學習MyBatis如何訪問資料庫的, 從這個結構圖要明確知道MyBatis訪問資料庫的核心構成包括三個核心類和兩種組態檔, 三個核心類:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession; 兩種組態檔:MyBatis核心組態檔(一個)、MyBatis映射檔案(多個), 我們在下面學習的入門程式主要學習的就是引數映射的規范和回傳值映射的規范, 6.MyBatis開發環境搭建 這個開發環境僅僅是學習使用的,并不是真正專案中使用的開發環境, 6.1.第一步:創建資料庫和表 1.創建資料庫【mybatis】,編碼utf-8 2.匯入【資料\01.資料庫\initialDB.sql】腳本, 6.2.第二步:創建工程 創建一個普通java工程, 因為這里我們只是用來學習MyBatis,所以不是實際專案中真正的開發環境,因此建立一個普通java工程就夠用了, 等都后面SSM整合之后才是我們真正在實際專案中使用的開發環境, 6.3.第三步:匯入jar包 這里我們用到【MyBatis的jar包】和【mysql的jar包】, 1.取得MyBatis的jar包 Mybatis的GitHub地址: https://github.com/mybatis/mybatis-3/releases 2.匯入MyBatis的jar包 1)Mybatis的jar包分為: 核心jar包 依賴jar包 2)匯入工程并關聯到工程 3. mysql的jar包也要匯入工程并關聯,方法同上, 6.4.第四步:框架的組態檔 6.4.1.核心組態檔 創建Source Folder【config】,它作為全部關于環境的組態檔的存放目錄, 在config下創建MyBatisConfig.xml(也有人叫SqlMapConfig.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> <!-- 資料庫環境的配置(臨時配置) --> <!-- environments:表示多個資料庫環境的配置標簽 default:當前默認使用的資料庫環境 --> <environments default="dev"> <!-- 開發資料庫環境的配置 --> <!-- environment:表示一個資料庫環境配置的標簽 id:表示唯一標識一個資料庫環境 --> <environment id="dev"> <!-- 事務管理的配置 --> <!-- transactionManager:表示事務管理的配置標簽 type:表示事務管理的型別,由于MyBatis是對JDBC的封裝,所以通常使用JDBC的事務 --> <transactionManager type="JDBC"/> <!-- 資料源配置:driver, url, username, password --> <!-- dataSource:表示資料源的配置標簽 type:表示資料源的型別,標識是否支持資料庫連接池 POOLED:表示支持資料庫連接池的資料源 UNPOOLED:表示不支持資料庫連接池的資料源 --> <dataSource type="POOLED"> <property name="driver" value="https://www.cnblogs.com/haizai/p/com.mysql.jdbc.Driver"/> <property name="url" value="https://www.cnblogs.com/haizai/p/jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/> <property name="username" value="https://www.cnblogs.com/haizai/p/root"/> <property name="password" value="https://www.cnblogs.com/haizai/p/123"/> </dataSource> </environment> </environments> </configuration> 這個檔案就是MyBatis的核心組態檔,里面主要配置連接資料庫的資料源、事務管理以及MyBatis的映射檔案有哪些, 其中整個【environments】部分不需要掌握,大概知道里面配置的是資料庫資訊就可以了,因為到實際專案中框架整合后就不用這么配置了,所以它沒有實際用途,這里只是臨時這么用一下, 6.4.2.SQL映射檔案 MyBatis的SQL映射檔案就是用來寫SQL陳述句和配置SQL的引數映射和回傳值映射的,可以根據業務創建多個映射檔案,比如關于用戶資訊的映射檔案:UserMapper.xml,關于訂單資訊的映射檔案:OrderMapper.xml等, 1.創建SQL映射檔案(因為SQL映射檔案是關于業務的,所以不要放到config里面) 創建一個包【cn.baidu.mapper】,在其下創建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"> <!-- namespace:隔離SQL映射檔案的,是一個SQL映射檔案的唯一標識 --> <mapper namespace="user"> <!-- SQL映射 --> </mapper> 2.配置SQL映射檔案 在MyBatis核心組態檔中配置映射檔案,目的是為了讓MyBatis知道有這個映射檔案, <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> ,,,,,, <!-- 配置映射檔案 --> <mappers> <!-- 通過映射檔案在編譯后類目錄下的相對路徑加載映射檔案 resource:用來指定映射檔案的相對路徑 --> <mapper resource="cn/baidu/mapper/UserMapper.xml" /> </mappers> </configuration> 6.5.第六步:測驗開發環境 1.利用junit進行測驗,eclipse本身包含junit的jar直接匯入即可: 2.手動撰寫客戶端測驗程式 創建Source Folder【test】用于存放測驗程式的,并創建一個普通的class: package mybatis; 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.Test; public class MyTest { @Test public void envTest() throws Exception { SqlSession sqlSession = null; try { // 1. 讀取組態檔(MyBatis有專門讀取組態檔的工具類Resources) InputStream inputStream = Resources.getResourceAsStream("MyBatisCofig.xml"); // 2. 根據主組態檔創建會話工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3. 根據會話工廠創建會話物件 // 業務層通過SqlSession物件來訪問資料庫進行CRUD操作,每個執行方法中會話物件要私有 sqlSession = sqlSessionFactory.openSession(); System.out.println(sqlSession); } catch(Exception e) { e.printStackTrace(); throw e; } finally { // 關倍訓話 sqlSession.close(); } } } 注意:上面的核心類不是真實專案開發中需要寫的,都是臨時的寫法,這里由于只使用了MyBatis框架,所以只能臨時手動的加載核心組態檔、手動創建會話工廠以及會話物件,到真實專案中這些都不用寫,這里只是做測驗用的代碼,大家只要知道三個核心類是什么就可以, 3.能夠列印出SqlSession物件的資訊說明客戶端與資料庫連接的會話已經建立起來了, 6.6.第七步:給MyBatis加日志輸出 把【資料\04.參考案例\config\log4j.properties】拷貝到工程的config目錄下, 6.7.小結 這一節主要是操作,因此記憶的東西很少,主要是課后多加練習開發環境的搭建, 搭建開發環境的目錄步驟可以看目錄, 7.MyBatis入門程式(重點) MyBatis程式開發分為兩步: 1. 在SQL映射檔案中撰寫SQL以及SQL映射,包括引數映射和回傳值映射, 2. 在客戶端用SqlSession物件使用指定的方法呼叫SQL,包括兩個引數:第一個引數是某個配置好的SQL映射,第二個引數是要傳給SQL的引數, 7.1.查 7.1.1.根據id查詢用戶資訊 1.【UserMapper.xml】映射檔案中增加查詢SQL的映射配置: <說明>(需要掌握) 專案 解釋 <select> 用于查詢SQL的標簽, id 在相同映射檔案中SQL的唯一標識(名稱不允許包含點【.】) parameterType 傳入引數的型別(當沒有引數時可以省略) resultType SQL回傳給程式的java結果的型別 #{userId} 以#{xxx}作為樣式,叫做占位符,用來接收引數,xxx表示引數的變數名稱, MyBatis都是按名稱進行引數映射的,如果只寫#{}會報錯,有了名稱就不用考慮jdbc引數賦值的先后順序了,所以解決了jdbc傳值順序的硬編碼問題, <SQL示例> 注意:要嚴格按照MyBatis的要求和映射規范去寫xml,否則MyBatis就無法決議了, <?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="user"> <!-- SQL --> <!-- 根據id查詢用戶資訊 --> <select id="findUserById" parameterType="int" resultType="cn.baidu.pojo.User"> SELECT userId, name, mobile, sex, age, address FROM user WHERE userId = #{userId} </select> </mapper> <SQL映射規范> ·引數映射規范(一) 傳單個引數時,parameterType="java簡單型別",占位符中的變數可以任意名稱,但不能沒有, ·回傳值映射規范(一) 回傳單條記錄時,resultType="pojo型別",結果集的列名必須等于pojo的屬性名, (注意單條記錄中的多個值不能分散的回傳,MyBatis不支持) 2.【MyTest.java】中增加一個測驗方法: <說明>(需要掌握) 專案 解釋 selectOne 查詢單個記錄(單值或單條都使用它) 第一個引數 namespace屬性的值 + . + SQL id屬性的值(namespace確定映射檔案,id確定SQL) 第二個引數 傳給SQL的引數,型別 = parameterType指定的型別(當沒有引數時可省略) 回傳值 SQL查詢的結果,型別 = resultType指定的型別 <代碼示例> // 測驗根據id查詢用戶資訊 @Test public void test1() throws Exception { // 讀取組態檔 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根據組態檔創建會話工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 根據會話工廠創建會話物件 SqlSession sqlSession = sqlSessionFactory.openSession(); // 根據id查詢用戶資訊 User userInfo = sqlSession.selectOne("user.findUserById", 1001); System.out.println(userInfo); sqlSession.close(); } 7.2.根據用戶名查詢用戶資訊 因為用戶名是一個不確定的查詢條件,因此多半在SQL采用模糊查詢的方式進行條件匹配, 7.2.1.用占位符接收引數映射 1.【UserMapper.xml】映射檔案中增加查詢SQL配置 占位符有一個特性:可以做引數型別的判斷,如果是字串型別的引數會自動添加單引號,不需要手動添加, <!-- 根據用戶名查詢用戶資訊(方式一:用占位符接收引數映射) --> <select id="findUserByUserName" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT userId, name,mobile,sex,age,address FROM user WHERE name LIKE #{userName} </select> <SQL映射規范> ·回傳值映射規范(二) 回傳多條記錄時,resultType="集合的pojo泛型的型別",結果集的列名必須等于pojo泛型的屬性名, 2.【MyTest.java】中增加一個測驗方法: <說明>(需要掌握) 專案 解釋 selectList 查詢多條記錄(回傳List<pojo>型別的java物件) 第一個引數 同上 第二個引數 同上 回傳值 SQL查詢的List集合結果,List集合的泛型 = resultType指定的型別 MyBatis內部會通過回傳值映射產生多個java物件,這些物件會放到一個List物件中,每個物件的型別就是resultType配置的泛型,最終將List物件回傳給java程式, <代碼示例> // 測驗根據用戶名查詢用戶資訊(方式一) @Test public void test1() throws Exception { // 讀取組態檔 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根據組態檔創建會話工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 根據會話工廠創建會話物件 SqlSession sqlSession = sqlSessionFactory.openSession(); //根據用戶名查詢用戶資訊 List<User> userList = sqlSession.selectList("user.findUserByUserName", "%王%"); System.out.println(userList); sqlSession.close(); } 注意:這里有一個問題就是如果SQL不是你寫的,你在呼叫的時候可能不知道里面是否用的是模糊查詢,所以也就不好判斷是否需要加百分號了,最好是能將百分號加在SQL上面,這樣外面不管是不是模糊查詢都需要傳姓名肯定是沒有錯的,但這時就不能使用占位符的,因為單引號需要自己加在SQL中, 這就需要使用MyBatis的另一種用來接收引數映射的符號——字串連接符,也叫字串拼接符, 7.2.2.用字串拼接符接收引數映射 1.【UserMapper.xml】映射檔案中增加查詢SQL配置 <說明> 專案 解釋 ${value} 以${xxx}作為樣式,叫做字串拼接符, 拼接符讓MyBatis把SQL陳述句和引數值當成字串進行字串原樣拼接,所謂原樣拼接就是不做任何jdbc型別轉換,原來什么樣就拼成什么樣,所以SQL配置中必須人為加單引號才行, <SQL示例> <!-- 根據用戶名查詢用戶資訊(方式二:用拼接符接收引數) --> <select id="findUserByUserName2" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT userId, name,mobile,sex,age,address FROM user WHERE name LIKE '%${value}%' </select> <SQL映射規范> ·引數映射規范(二) 傳單個引數時,parameterType="java簡單型別",拼接符中的變數名必須是value,也不能沒有, 2.【MyTest.java】中增加一個測驗方法: // 測驗根據用戶名查詢用戶資訊(方式二) @Test public void test1() throws Exception { // 讀取組態檔 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根據組態檔創建會話工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 根據會話工廠創建會話物件 SqlSession sqlSession = sqlSessionFactory.openSession(); //根據用戶名查詢用戶資訊 List<User> userList = sqlSession.selectList("user.findUserByUserName2", "王"); System.out.println(userList); sqlSession.close(); } 7.2.3.占位符與拼接符區別 1.型別處理: 占位符#{}傳遞引數時會做引數型別處理, 拼接符${}傳遞引數時不會做型別處理只進行字串原樣拼接 2.安全性: ${}的原樣拼接導致它存在安全漏洞,容易產生SQL注入風險 #{}的型別處理會對引數中存在的SQL敏感字符先轉義然后再映射給SQL,這就不會影響原先的SQL,因此可以有效防止SQL注入, 3.作業中的應用: 由于拼接符${}存在安全隱患,因此在實際專案盡量使用占位符#{} 附:SQL注入的一個示例 1 映射檔案中的配置 <!-- 用拼接符接收引數 --> <select id="selectUserByUserName3" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT u.userId, u.name, u.age, u.address FROM user u WHERE u.name LIKE '${value}' </select> <!-- 用占位符接收引數 --> <select id="selectUserByUserName4" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT u.userId, u.name, u.age, u.address FROM user u WHERE u.name LIKE #{name} </select> 2 傳遞引數是一致的,左邊拼接符最外面的單引號已經在映射檔案中寫上了;右邊占位符按照預想由于傳入的是String字串型別的引數,所以會做型別處理自動的在引數外面加上一對單引號,但事情會想我們想象的那樣進行嗎? List<User> userInfoList = sqlSession.selectList("user. selectUserByUserName3", "' OR 1=1 OR 1='"); List<User> userInfoList = sqlSession.selectList("user. selectUserByUserName4", "' OR 1=1 OR 1='"); 3 結果發現左邊確實發生了sql注入,右邊沒有發生: DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee] DEBUG [main] - ==> Preparing: SELECT u.userId, u.name, u.age, u.address FROM user u WHERE u.name LIKE '' OR 1=1 OR 1='' DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 14 [[1001, 王小一, null, 56, null, 南京], [1002, 王小二, null, 48, null, 青島], [1003, 王小三, null, 32, null, 大連], [1004, 張三, null, 23, null, 廣州], [1005, 王小五, null, 34, null, 重慶], [1006, 王小六, null, 31, null, 石家莊], [1007, 迎春, null, 28, null, 蘇州], [1008, 張三, null, 23, null, 廣州], [1009, 迎秋, null, 20, null, 長沙], [1010, 迎冬, null, 18, null, 佛山], [1011, 張三, null, 30, null, 廣州], [1013, 張三, null, 30, null, 廣州], [1014, 張三, null, 30, null, 廣州], [1015, 張三, null, 30, null, 廣州]] DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee] DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee] DEBUG [main] - Returned connection 1177377518 to pool. DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@58c1670b] DEBUG [main] - ==> Preparing: SELECT u.userId, u.name, u.age, u.address FROM user u WHERE u.name LIKE ? DEBUG [main] - ==> Parameters: ' OR 1=1 OR 1='(String) DEBUG [main] - <== Total: 0 [] DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@58c1670b] DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@58c1670b] DEBUG [main] - Returned connection 1489069835 to pool. 左邊拼接是原樣拼接因此出現了漏洞,形成的SQL相當于mysql的工具中左邊的樣子: 右邊占位符由于做型別處理,首先后把校驗傳入的引數是否有敏感字符,這里單引號就是一個敏感字符,其次如果有敏感字符需要進行轉義,上面的引數轉義為:\' OR 1=1 OR 1=\',再次再把轉義完的引數映射給SQL并在引數外面加一對單引號,轉義后的引數就不會對原先的SQL產生影響,僅僅被當作普通引數值進行處理,形成的SQL相當于mysql的工具中右邊的樣子: 7.3.查詢用戶表記錄數 1.【UserMapper.xml】映射檔案中增加查詢SQL配置 <說明> 專案 解釋 <select> 同上 id 同上 parameterType 沒有引數時可以省略 resultType SQL回傳給程式的java結果的型別 #{xxx}或${xxx} 沒有引數可以省略 <SQL示例> <!-- 取得用戶表的記錄數 --> <select id="countUserRecord" resultType="int"> SELECT COUNT(userId) FROM user </select> 注意:不要使用count(*),因為count(*)效率低,可以count(1)或count(欄位名)都可以, <SQL映射規范> ·回傳值映射規范(三) 回傳單值時,resultType="java簡單型別",值直接回傳給java程式, 2.【MyTest.java】中增加一個測驗方法: // selectOne也可以回傳單值結果 int count = ss.selectOne("user.countUserRecord"); System.out.println(count); 7.4.增 7.4.1.插入單條記錄 1.【UserMapper.xml】映射檔案中增加插入SQL配置 <說明> 專案 解釋 <insert> 用于插入SQL的標簽, id 同查詢 parameterType 同查詢 resultType 插入SQL沒有回傳值,所以沒有這個屬性 #{xxx} 同查詢,這里能體現出名稱的價值, <SQL示例> <!-- 插入用戶資訊 --> <insert id="addUserInfo" parameterType="cn.baidu.pojo.User"> INSERT INTO USER (name,mobile,sex,age,address) VALUES (#{name},#{mobile},#{sex},#{age},#{address}) </insert> <SQL映射規范> ·引數映射規范(三) 傳多個引數時,parameterType="pojo型別",占位符或拼接符的變數名必須等于pojo中的屬性名, (在引數pojo中屬性是沒有順序的,所以很好的解決了jdbc引數順序硬編碼的問題) 2.【MyTest.java】中增加一個測驗方法: <說明> 專案 解釋 insert 插入處理 第一個引數 同查詢 第二個引數 同查詢 回傳值 SQL沒有回傳值,但是方法本身有一個int的回傳值,表示插入的記錄條數, <代碼示例> // 測驗插入一條用戶資訊 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { // 根據會話工廠創建會話物件 sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setAge(18); user.setAddress("北京"); user.setMobile("13500099000"); user.setName("張三"); user.setSex("男"); // 插入用戶資訊 int count = sqlSession.insert("user.addUserInfo", user); System.out.println("count=" + count); sqlSession.commit(); } catch(Exception ex) { ex.printStackTrace(); sqlSession.rollback(); throw ex; } finally { sqlSession.close(); } } 7.5.改 7.5.1.根據id更新用戶資訊 1.【UserMapper.xml】映射檔案中增加插入SQL配置 <說明> 專案 解釋 <update> 用于更新SQL的標簽, id 同查詢 parameterType 同查詢 resultType 更新SQL沒有回傳值,所以沒有這個屬性 #{xxx} 同查詢,這里能體現出名稱的價值, <SQL示例> <!-- 根據id修改用戶資訊 --> <update id="updateUserById" parameterType="cn.baidu.pojo.User"> UPDATE user SET name = #{name}, mobile = #{mobile}, sex = #{sex}, age = #{age}, address = #{address} WHERE userId = #{userId} </update> <SQL映射規范> 同插入的規范, 2.【MyTest.java】中增加一個測驗方法: <說明> 專案 解釋 update 更新處理 第一個引數 同查詢 第二個引數 同查詢 回傳值 SQL沒有回傳值,但是方法本身有一個int的回傳值,表示更新的記錄條數, <代碼示例> // 測驗根據id修改用戶資訊 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { // 根據會話工廠創建會話物件 sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setAddress("天津"); user.setAge(28); user.setMobile("13600099000"); user.setName("李四"); user.setSex("女"); user.setUserId(1011); // 更新用戶資訊 int count = sqlSession.update("user.updateUserById", user); System.out.println("count=" + count); sqlSession.commit(); } catch(Exception ex) { ex.printStackTrace(); sqlSession.rollback(); throw ex; } finally { sqlSession.close(); } } 7.6.刪 7.6.1.根據id洗掉用戶資訊 1.【UserMapper.xml】映射檔案中增加插入SQL配置 <說明> 專案 解釋 <delete> 用于洗掉SQL的標簽, id 同查詢 parameterType 同查詢 resultType 洗掉SQL沒有回傳值,所以沒有這個屬性 #{xxx} 同查詢 <SQL示例> <!-- 根據id洗掉用戶資訊 --> <delete id="deleteUserById" parameterType="int"> DELETE FROM user WHERE userId = #{id} </delete> <SQL映射規范> 同查詢的規范, 2.【MyTest.java】中增加一個測驗方法: <說明> 專案 解釋 delete 洗掉處理 第一個引數 同查詢 第二個引數 同查詢 回傳值 SQL沒有回傳值,但是方法本身有一個int的回傳值,表示洗掉的記錄條數, <代碼示例> // 測驗根據id洗掉用戶資訊 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { // 根據會話工廠創建會話物件 sqlSession = sqlSessionFactory.openSession(); // 根據id洗掉用戶資訊 int count = sqlSession.delete("user.deleteUserById", 1011); System.out.println("count=" + count); sqlSession.commit(); } catch(Exception ex) { ex.printStackTrace(); sqlSession.rollback(); throw ex; } finally { sqlSession.close(); } } 7.7.增刪改查小結 本小結下的內容都是重點需要重點記憶, <SQL映射規范>(需要掌握) ·引數映射規范 傳單個引數時,parameterType="java簡單型別",占位符中的變數可以任意名稱,但不能沒有, 傳單個引數時,parameterType="java簡單型別",拼接符中的變數名必須是value,也不能沒有, 傳多個引數時,parameterType="pojo型別",占位符或拼接符的變數名必須等于pojo中的屬性名, ·回傳值映射規范 回傳單值時,resultType="java簡單型別",值直接回傳給java程式, 回傳單條記錄時,resultType="pojo型別",結果集的列名必須等于pojo的屬性名, 回傳多條記錄時,resultType="集合的pojo泛型的型別",結果集列名必須等于pojo泛型的屬性名, <增刪改查對應的標簽和java客戶端呼叫的方法>(需要掌握) 區分 標簽 客戶端呼叫方法 增 <insert> insert(namespace名+.+sql id, sql的引數變數) 刪 <delete> delete(引數同上) 改 <update> update(引數同上) 查 <select> 單值或單條selectOne(引數同上) 多條selectList(引數同上) 7.8.MyBatis對JDBC問題的解決 1.如何解決JDBC資料連接資源缺乏管理的問題? 解決:在MyBatis組態檔中配置了資料庫連接池, 2.如何解決SQL的硬編碼 解決:將Sql陳述句配置在SQL映射檔案中與java代碼分離, 3.如何解決SQL引數的順序硬編碼問題 解決:MyBatis的引數映射,可以幫我們把java物件自動的映射給SQL 4.如何解決結果集中欄位名字串的硬編碼 解決:MyBatis的回傳值映射,可以幫我們把結果集自動的映射給java物件, 7.9.MyBatis與Hibernate對比總結 7.10.插入標簽中的子查詢(了解) 插入的子查詢主要是用來查詢資料表主鍵的作用,查詢出來的主鍵需要賦值給引數映射中傳入的pojo物件某屬性上, 7.10.1.功能一: 取得插入資料的自增主鍵 selectKey + LAST_INSERT_ID(),可以解決如何查詢自增型主鍵的資料表中剛插入記錄的主鍵的問題,先插入后取得,取得后可以和其他表做外鍵關聯的操作, 1.【UserMapper.xml】映射檔案中增加插入SQL配置 <說明> 專案 解釋 <selectKey> 用于<insert>操作的子查詢, order 子查詢相對于insert SQL的執行順序(AFTER:在插入之后執行 BEFORE:在插入之前執行) keyProperty 傳入的java物件引數的某個屬性名,用于將子查詢結果賦值給引數物件的指定屬性,這樣在java程式中只要執行完insert()方法,就可以從引數物件中指定的屬性上取得這個子查詢的結果, resultType 子查詢的值按照什么型別回傳結果 LAST_INSERT_ID() mysql的函式,可以回傳最新插入記錄的主鍵,要和insert陳述句配合使用,否則單獨執行的值就是0 <SQL示例> <!-- 插入一條用戶資訊并回傳新插入記錄的主鍵 --> <insert id="addUserInfo" parameterType="cn.baidu.pojo.User"> <!-- 插入操作中的子查詢 --> <selectKey order="AFTER" keyProperty="userId" resultType="int"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO USER (name,mobile,sex,age,address) VALUES (#{name},#{mobile},#{sex},#{age},#{address}) </insert> 2.【MyTest.java】中增加一個測驗方法: // 測驗插入一條用戶資訊 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { // 根據會話工廠創建會話物件 sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setAge(18); user.setAddress("北京"); user.setMobile("13500099000"); user.setName("張三"); user.setSex("男"); System.out.println("user.userId=" + user.getUserId()); // 插入用戶資訊 int count = sqlSession.insert("user.addUserInfo", user); System.out.println("count=" + count); System.out.println("user.userId=" + user.getUserId()); sqlSession.commit(); } catch(Exception ex) { ex.printStackTrace(); sqlSession.rollback(); throw ex; } finally { sqlSession.close(); } } 7.10.2.功能二: 使用UUID實作主鍵 selectKey + UUID(),可以解決非自增型主鍵的資料表中在插入資料前先創建主鍵的問題, <說明> 專案 解釋 <selectKey> 同上 order 同上(這里指定為BEFORE) keyProperty 同上 resultType 同上 UUID() mysql的函式,可以回傳隨機的UUID,可以作為主鍵用, 映射檔案: <!-- 取得插入資料的主鍵后插入資料 --> <insert id="insertOrderData" parameterType="cn.baidu.pojo.Order"> <selectKey order="BEFORE" keyProperty="orderId" resultType="String"> SELECT UUID() </selectKey> INSERT INTO order1 (orderId, userId, orderStatus, goodsId, createDateTime) VALUES (#{orderId}, #{userId}, #{orderStatus}, #{goodsId}, now()); </insert> 客戶端程式: // 資料庫操作... // insert:表示插入SQL的方法 Order order = new Order(); order.setGoodsId("123456789"); order.setOrderStatus("01"); order.setUserId(1008); System.out.println(order.getOrderId()); ss.insert("order.insertOrderData", order); System.out.println(order.getOrderId()); ss.commit(); 8.DAO開發方法 8.1.傳統DAO開發方式 傳統的DAO開發方式就是撰寫DAO介面和DAO實作類來實作對資料庫的訪問, 8.1.1.撰寫SQL 從【UserMapper.xml】中挑選兩個SQL <!-- 根據id查詢用戶資訊 --> <select id="findUserById" parameterType="int" resultType="cn.baidu.pojo.User"> SELECT userId, name, mobile, sex, age, address FROM user WHERE userId = #{userId} </select> <!-- 根據用戶名查詢用戶資訊 --> <select id="findUserByUserName2" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT userId, name,mobile,sex,age,address FROM user WHERE name LIKE '%${value}%' </select> 8.1.2.撰寫DAO介面 在【cn.baidu.dao】包下創建DAO介面【UserDao】 package cn.baidu.dao; import java.util.List; import cn.baidu.pojo.User; public interface UserDao { // 根據id查詢用戶資訊 public User findUserById(Integer id) throws Exception; // 根據姓名查詢用戶資訊 public List<User> findUserByName(String name) throws Exception; } 8.1.3.撰寫DAO介面實作類 在【cn.baidu.dao】包下創建介面【UserDao】的實作類【UserDaoImpl】 package cn.baidu.dao; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import cn.baidu.pojo.User; public class UserDaoImpl implements UserDao { private SqlSessionFactory sqlSessionFactory; public void setSqlSessionFactory(SqlSessionFactory sqlSf) { this.sqlSessionFactory = sqlSf; } @Override public User findUserById(Integer id) throws Exception { SqlSession sqlSession = null; try { sqlSession = sqlSessionFactory.openSession(); // 根據id查詢 User userInfo = sqlSession.selectOne("user.findUserById", id); return userInfo; } catch(Exception ex) { ex.printStackTrace(); throw ex; } finally { sqlSession.close(); } } @Override public List<User> findUserByName(String name) throws Exception { SqlSession sqlSession = null; try { // 根據會話工廠創建會話物件 sqlSession = sqlSessionFactory.openSession(); // 根據用戶名查詢 List<User> userInfoList = sqlSession.selectList("user.findUserByUserName2", name); return userInfoList; } catch(Exception ex) { ex.printStackTrace(); throw ex; } finally { sqlSession.close(); } } } 8.1.4.撰寫客戶端測驗程式 在【test】目錄下創建【MyTest2.java】測驗程式 package mybatis; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import cn.baidu.dao.UserDaoImpl; import cn.baidu.pojo.User; /** * DAO開發方式 */ public class MyTest2 { private SqlSessionFactory sqlSessionFactory; private UserDaoImpl userDao; // 測驗初始化函式 @Before public void init() throws Exception { // 讀取組態檔 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根據主組態檔創建會話工廠 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); userDao = new UserDaoImpl(); userDao.setSqlSessionFactory(sqlSessionFactory); } // 測驗根據id查詢用戶資訊 @Test public void test1() throws Exception { User user = userDao.findUserById(1001); System.out.println(user); } // 測驗根據用戶名查詢用戶資訊 @Test public void test2() throws Exception { List<User> userList = userDao.findUserListByName("迎"); System.out.println(userList); } } 8.1.5.傳統DAO開發方法的問題 正常的傳統DAO介面的實作類中各個方法的邏輯基本相同,代碼重復的部分較多,除非有特殊業務要求才會加入特殊的業務邏輯,否則實作類中的方法幾乎一致, 8.2.MyBatis動態代理DAO開發方式(重點) 8.2.1.什么是MyBatis動態代理 MyBatis打破傳統DAO的開發方式,不需要程式員再寫DAO的實作類了,可以直接用DAO介面的物件呼叫資料庫處理的方法,MyBatis在執行時由代理物件代替DAO介面的實作類執行資料庫處理, 要使用MyBatis動態代理就必須遵守動態代理的開發規范,即四個相等, 8.2.2.MyBatis動態代理開發規范 (需要掌握) 介面 映射檔案 完全限定名 = Namespace的值 方法名 = SQL的id的值 介面方法的引數型別 = parameterType的值 介面方法的回傳值型別 = resultType的值 8.2.3.撰寫SQL映射檔案并添加配置 在【cn.baidu.mapper】包下創建新的映射檔案【UserMapper2.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"> <!-- namespace:整個MyBatis管理的映射檔案中必須唯一 --> <mapper namespace="cn.baidu.dao.UserDao2"> <!-- SQL --> <!-- 根據id查詢用戶資訊 --> <select id="findUserById" parameterType="int" resultType="cn.baidu.pojo.User"> SELECT userId, name, mobile, sex, age, address FROM user WHERE userId = #{userId} </select> <!-- 根據用戶名查詢用戶資訊 --> <select id="findUserByUserName" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT userId, name,mobile,sex,age,address FROM user WHERE name LIKE '%${value}%' </select> </mapper> 8.2.4.撰寫DAO介面 在【cn.baidu.dao】包下創建DAO介面【UserDao2】 package cn.baidu.dao; import java.util.List; import cn.baidu.pojo.User; public interface UserDao2 { // 根據id查詢用戶資訊 public User findUserById(Integer id) throws Exception; // 根據姓名查詢用戶資訊 public List<User> findUserByUserName(String name) throws Exception; } 8.2.5.撰寫客戶端測驗程式 在【MyTest2.java】測驗程式中增加兩個測驗方法 <說明> 專案 解釋 getMapper 生成介面的代理物件, 引數 介面的型別描述,(Xxxx.class) 回傳值 介面型別的代理物件, <示例代碼> // 測驗根據id查詢用戶資訊(動態代理DAO開發方式) @Test public void test3() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 用getMapper取得自動生成的DAO介面的實作類 UserDao2 userDao = sqlSession.getMapper(UserDao2.class); User userInfo = userDao.findUserById(1001); System.out.println(userInfo); sqlSession.close(); } // 測驗根據id查詢用戶資訊(動態代理DAO開發方式) @Test public void test4() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserDao2 userDao = sqlSession.getMapper(UserDao2.class); List<User> userList = userDao.findUserByUserName("迎"); System.out.println(userList); sqlSession.close(); } 8.3.動態代理DAO開發方式的好處 Mybatis已經把開發中能簡化的都簡化了,對于我們開發人員就可以好好的集中精力寫SQL了, 8.4.傳統DAO開發方式與MyBatis動態代理的區別 傳統方式需要撰寫DAO介面的實作類,并通過實體化實作類的物件來訪問資料庫, 動態代理不需撰寫DAO介面的實作類,并通過介面類的代理物件來訪問資料庫,代理物件由MyBatis自動生成, 8.5.小結 本章的重點是MyBatis動態代理DAO開發方式,要掌握如何開發動態代理DAO,牢記動態代理四個開發規范,這也是今天課程中第二個重點, 9.MyBatis核心組態檔 9.1.核心組態檔配置項結構 官方說明Url:http://www.mybatis.org/mybatis-3/zh/configuration.html ·注意:配置項必須按照上面的順序配置 紅框中的為常用配置項,需要知道,其他配置項不常用,今后作業中用到時再查資料了解, 9.2.properties(屬性)配置項 9.2.1.properties檔案的好處 1.容易維護:相比于xml,properties檔案更易于修改,降低修改時出錯的幾率,在實際專案中經常把properties屬性檔案與xml組態檔結合使用,把真正的值都放在properties屬性檔案中,在xml中使用的時候直接引過來就可以使用了,非常方便, 2.一處修改多處生效 9.2.2.properties標簽配置 <!-- 屬性檔案的配置 --> <!-- properties:表示屬性檔案配置的標簽 resource:表示類的相對路徑下的java屬性檔案 url:表示檔案的絕對路徑 --> <properties resource="jdbc.properties" /> 對應資料源配置的修改: <!-- 資料庫環境的配置 --> <environments default="dev"> <!-- 開發資料庫環境的配置 --> <environment id="dev"> <!-- 事務管理的配置 --> <transactionManager type="JDBC"/> <!-- 資料源配置:driver, url, username, password --> <dataSource type="POOLED"> <property name="driver" value="https://www.cnblogs.com/haizai/p/${jdbc.driver}"/> <property name="url" value="https://www.cnblogs.com/haizai/p/${jdbc.url}"/> <property name="username" value="https://www.cnblogs.com/haizai/p/${jdbc.username}"/> <property name="password" value="https://www.cnblogs.com/haizai/p/${jdbc.password}"/> </dataSource> </environment> </environments> properties屬性檔案中key的命名方式: 檔案名 + . + 具體的key名,這樣做的好處是不容易造成兩個properties檔案中出現同名的key,key一旦同名,這兩個properties檔案又都被同時引入到一個xml中就會出錯, 9.3.typeAliases(型別別名)配置項 9.3.1.MyBatis內建的java型別的別名 注意: 紅色框中的才是java基本型別以及對應的別名,從中可以看出java基本型別的別名都是以下劃線開頭的, 藍色框中的是java基本型別的包裝類以及對應的別名,這也是我們前面一直在使用的, 沒有畫框的都是正常使用的別名, 為什么在MyBatis內建議使用java基本型別的包裝型別來傳遞引數? MyBatis在處理引數時,如果傳遞的是java基本型別:int、long、char、bool等,MyBatis都會將基本型別包裝成:Integer、Long、String、Boolean等,然后再進行傳遞,這樣做的好處是避免java基本型別的默認值問題,Java基本型別引數在沒有實際賦值時都會有默認值,如果你不主動給引數變數賦值就直接傳給SQL,就會把引數變數的默認值傳給SQL陳述句,這樣就可能造成破壞業務資料的風險,因此在MyBatis內部這樣的引數都會自動的被包裝成物件進行傳值,物件的好處是一旦沒有傳值,由于是物件,它的默認值就是null,給SQL傳遞null時一般都不會執行成功, 因此, 為了資料安全盡量使用藍框的包裝型別來傳值賦值. 9.3.2.別名都是大小寫不敏感的 int和INT、iNt、Int等都是一樣的,其它的也一樣不區分大小寫 9.3.3.別名的目的 簡化映射檔案的配置,縮短配置項的長度 9.3.4.POJO型別的自定義別名配置格式 MyBatisConfig.xml中(注意主組態檔中的專案是有順序的) <!-- 自定義別名 --> <typeAliases> <typeAlias type="cn.baidu.pojo.User" alias="User"/> </typeAliases> 映射檔案: <!-- 根據id查詢用戶資訊 --> <select id="findUserById" parameterType="int" resultType="User"> select userId, name,mobile,sex,age,address FROM user WHERE userId = #{userId} </select> 9.3.5.結論 對于java本身擁有的型別可以使用別名,而自定義型別不要使用別名, 推薦使用java包裝型別或它們的別名(籃框中的),而不要使用java基本型別和它們的別名(紅框中的), 9.4.mappers(映射檔案)配置項 9.4.1.映射檔案配置形式一 直接指定映射檔案在類根目錄下的相對路徑 <說明> 專案 解釋 <mappers> 用于SQL映射檔案的配置,下面可以配置多個映射檔案的掃描 <mapper> 是<mappers>的子標簽,用于一個映射檔案的配置 resource 映射檔案相對于類根目錄下的相對路徑 適用范圍 兩種DAO開發方式都適用這種配置,DAO的路徑和SQL映射檔案的路徑之間沒有任何聯系,隨意定義, 配置: <!-- 配置映射檔案 --> <mappers> <mapper resource="cn/baidu/mapper/UserMapper.xml"/> <mapper resource="cn/baidu/mapper/UserMapper2.xml"/> </mappers> 9.4.2.映射檔案配置形式二 重新創建一個包【cn.baidu.mapper2】,在其中創建兩個映射檔案和兩個對應同名的DAO介面,然后進行試驗, 通過一個java介面的完全限定名加載映射檔案 <說明> 專案 解釋 class 介面的完全限定名 要求 DAO介面檔案與映射檔案必須同名同目錄 適用范圍 只適用于MyBatis動態代理DAO開發方式 配置: <!-- 配置映射檔案 --> <mappers> <mapper class="cn.baidu.mapper2.UserMapper" /> </mappers> 9.4.3.映射檔案配置形式三 <說明> 專案 解釋 <package> 是第二種形式的批量加載形式 name 指定掃描的包路徑 要求 指定包下的DAO介面檔案與映射檔案必須同名同目錄 適用范圍 只適用于MyBatis動態代理DAO開發方式 配置: <!-- 配置映射檔案 --> <mappers> <package name="cn.baidu.mapper2"/> </mappers> 9.4.4.三種形式的辨析 1.形式一: 兩種DAO開發方式都使用,但比較死板,一旦路徑寫錯了不容易發現,一次只能加載一個映射檔案, 2.形式二: 只適用于動態代理方式,比較靈活,檢驗是否寫錯很容易(按住ctrl鍵,滑鼠指標放上去有連接說明寫的沒有錯),SM整合后可以放到spring容器中, 3.形式三: 只適用于動態代理方式,方式更加實用,是形式二的批量掃描方式,比形式二好在可以批量加載,SM整合后可以放到spring容器中,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/48426.html
標籤:架構設計
上一篇:談談技術債務
