MyBatis學習總結(一)——ORM概要與MyBatis快速起步
程式員應該將核心關注點放在業務上,而不應該將時間過多的浪費在CRUD中,多數的ORM框架都把增加、修改與刪除做得非常不錯了,然後資料庫中查詢無疑是使用頻次最高、複雜度大、與效能密切相關的操作,我們希望得到一種使用方便,查詢靈活的ORM框架,MyBatis可以滿足這些要求,MyBatis是一個支援普通SQL查詢,儲存過程和高階對映的優秀持久層框架,它也是SSM框架整合中的重要組成部分。
一、ORM
1.1、ORM簡介
ORM可以解決資料庫與程式間的異構性,比如在Java中我們使用String表示字串,而Oracle中可使用varchar2,MySQL中可使用varchar,SQLServer可使用nvarchar。
物件關係對映(英語:Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),用於實現面向物件程式語言裡不同型別系統的資料之間的轉換 。簡單的說,ORM是通過使用描述物件和資料庫之間對映的元資料,將程式中的物件與關係資料庫相互對映。
沒有ORM時我們是這樣完成物件與關係資料庫之間的對映的:
//將執行的sql String sql = "SELECT name, id, age, password FROM users"; //建立命令物件 preparedStatement = connection.prepareStatement(sql); //執行並獲得結果集 resultSet = preparedStatement.executeQuery(); //遍歷結果集,將資料庫中的資料轉換成Java中的物件 while(resultSet.next()){ String name = resultSet.getString("name"); int id = resultSet.getInt("id"); int age = resultSet.getInt("age"); String password = resultSet.getString("password"); User entity= new User(name,id,age,password); Users.add(entity); }
這種方案存在以下不足:
持久化層缺乏彈性。一旦出現業務需求的變更,就必須修改持久化層的介面
持久化層同時與域模型與關係資料庫模型繫結,不管域模型還是關係資料庫模型發生變化,都要修改持久化曾的相關程式程式碼,增加了軟體的維護難度。
將和資料庫互動(CRUD)的程式碼硬編碼到JDBC程式中
實現見狀的持久化層需要高超的開發技巧,而且程式設計量很大
物件模型和關係模型的轉換非常麻煩
ORM(O/R Mapping:物件關係對映):
一種將記憶體中的物件儲存到關係型資料庫中的技術
負責實體域物件的持久化,封裝資料庫訪問細節
ORM提供了實現持久化層的另一種模式,採用對映元資料(XML)來描述物件-關係的對映細節,使得ORM中介軟體能在任何一個Java應用的業務邏輯層和資料庫之間充當橋樑。
ORM提供了實現持久化層的另一種模式,它採用對映元資料來描述物件關係的對映,使得ORM中介軟體能在任何一個應用的業務邏輯層和資料庫層之間充當橋樑。
Java典型的ORM中有:
hibernate:全自動的框架,強大、複雜、笨重、學習成本較高
Mybatis:半自動的框架(懂資料庫的人 才能操作) 必須要自己寫sql
JPA:JPA全稱Java Persistence API、JPA通過JDK 5.0註解或XML描述物件-關係表的對映關係,是Java自帶的框架
ORM的方法論基於三個核心原則:
· 簡單:以最基本的形式建模資料。
· 傳達性:資料庫結構被任何人都能理解的語言文件化。
· 精確性:基於資料模型建立正確標準化了的結構。
1.2、ORM的概念
讓我們從O/R開始。字母O起源於"物件"(Object),而R則來自於"關係"(Relational)。幾乎所有的程式裡面,都存在物件和關係資料庫。在業務邏輯層和使用者介面層中,我們是面向物件的。當物件資訊發生變化的時候,我們需要把物件的資訊儲存在關係資料庫中。
當你開發一個應用程式的時候(不使用O/R Mapping),你可能會寫不少資料訪問層的程式碼,用來從資料庫儲存,刪除,讀取物件資訊,等等。你在DAL中寫了很多的方法來讀取物件資料,改變狀態物件等等任務。而這些程式碼寫起來總是重複的。
ORM解決的主要問題是物件關係的對映。域模型和關係模型分別是建立在概念模型的基礎上的。域模型是面向物件的,而關係模型是面向關係的。一般情況下,一個持久化類和一個表對應,類的每個例項對應表中的一條記錄,類的每個屬性對應表的每個欄位。
將關係資料庫中表中的記錄對映成為物件,以物件的形式展現,程式設計師可以把對資料庫的操作轉化為對物件的操作。
因此ORM的目的是為了方便開發人員以面向物件的思想來實現對資料庫的操作。
1.3、ORM的優缺點
優點:
1.提高了開發效率。由於ORM可以自動對Entity物件與資料庫中的Table進行欄位與屬性的對映,所以我們實際可能已經不需要一個專用的、龐大的資料訪問層。
2.ORM提供了對資料庫的對映,不用sql直接編碼,能夠像操作物件一樣從資料庫獲取資料。
缺點:
犧牲程式的執行效率和會固定思維模式,降低了開發的靈活性。
從系統結構上來看,採用ORM的系統一般都是多層系統,系統的層次多了,效率就會降低。ORM是一種完全的面向物件的做法,而面向物件的做法也會對效能產生一定的影響。
在我們開發系統時,一般都有效能問題。效能問題主要產生在演算法不正確和與資料庫不正確的使用上。ORM所生成的程式碼一般不太可能寫出很高效的演算法,在資料庫應用上更有可能會被誤用,主要體現在對持久物件的提取和和資料的加工處理上,如果用上了ORM,程式設計師很有可能將全部的資料提取到記憶體物件中,然後再進行過濾和加工處理,這樣就容易產生效能問題。
在對物件做持久化時,ORM一般會持久化所有的屬性,有時,這是不希望的。
但ORM是一種工具,工具確實能解決一些重複,簡單的勞動。這是不可否認的。但我們不能指望工具能一勞永逸的解決所有問題,有些問題還是需要特殊處理的,但需要特殊處理的部分對絕大多數的系統,應該是很少的。
二、MyBatis
MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了google code,並且改名為MyBatis 。2013年11月遷移到Github。
iBATIS一詞來源於“internet”和“abatis”的組合,是一個基於Java的持久層框架。iBATIS提供的持久層框架包括SQL Maps和Data Access Objects(DAOs)
MyBatis 是一款優秀的持久層框架 ,它支援定製化 SQL、儲存過程以及高階對映。MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。MyBatis 可以使用簡單的 XML 或註解來配置和對映原生資訊,將介面和 Java 的 POJOs(Plain Old Java Objects,普通的 Java物件)對映成資料庫中的記錄。
2.1、MyBatis的特點
簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝只要兩個jar檔案+配置幾個sql對映檔案易於學習,易於使用,通過文件和原始碼,可以比較完全的掌握它的設計思路和實現。
靈活:mybatis不會對應用程式或者資料庫的現有設計強加任何影響。 sql寫在xml裡,便於統一管理和優化。通過sql基本上可以實現我們不使用資料訪問框架可以實現的所有功能,或許更多。
解除sql與程式程式碼的耦合:通過提供DAO層,將業務邏輯和資料訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。sql和程式碼的分離,提高了可維護性。
提供對映標籤,支援物件與資料庫的ORM欄位關係對映
提供物件關係對映標籤,支援物件關係組建維護
提供XML標籤,支援編寫動態sql。
2.2、MyBatis工作流程
(1)、載入配置並初始化
觸發條件:載入配置檔案
配置來源於兩個地方,一處是配置檔案,一處是Java程式碼的註解,將SQL的配置資訊載入成為一個個MappedStatement物件(包括了傳入引數對映配置、執行的SQL語句、結果對映配置),儲存在記憶體中。
(2)、接收呼叫請求
觸發條件:呼叫Mybatis提供的API
傳入引數:為SQL的ID和傳入引數物件
處理過程:將請求傳遞給下層的請求處理層進行處理。
(3)、處理操作請求 觸發條件:API介面層傳遞請求過來
傳入引數:為SQL的ID和傳入引數物件
處理過程:
(A)根據SQL的ID查詢對應的MappedStatement物件。
(B)根據傳入引數物件解析MappedStatement物件,得到最終要執行的SQL和執行傳入引數。
(C)獲取資料庫連線,根據得到的最終SQL語句和執行傳入引數到資料庫執行,並得到執行結果。
(D)根據MappedStatement物件中的結果對映配置對得到的執行結果進行轉換處理,並得到最終的處理結果。
(E)釋放連線資源。
(4)、返回處理結果將最終的處理結果返回。
無論是用過的hibernate,mybatis,你都可以法相他們有一個共同點:
在java物件和資料庫之間有做mapping的配置檔案,也通常是xml 檔案
從配置檔案(通常是XML配置檔案中)得到 SessionFactory
由SessionFactory 產生 Session
在Session中完成對資料的增刪改查和事務提交等
在用完之後關閉Session
2.3、MyBatis架構
Mybatis的功能架構分為三層:
API介面層:提供給外部使用的介面API,開發人員通過這些本地API來操縱資料庫。介面層一接收到呼叫請求就會呼叫資料處理層來完成具體的資料處理。
資料處理層:負責具體的SQL查詢、SQL解析、SQL執行和執行結果對映處理等。它主要的目的是根據呼叫的請求完成一次資料庫操作。
基礎支撐層:負責最基礎的功能支撐,包括連線管理、事務管理、配置載入和快取處理,這些都是共用的東西,將他們抽取出來作為最基礎的元件。為上層的資料處理層提供最基礎的支撐。
2.4、MyBatis的主要成員如層次結構
主要成員:
Configuration:MyBatis所有的配置資訊都儲存在Configuration物件之中,配置檔案中的大部分配置都會儲存到該類中
SqlSession:作為MyBatis工作的主要頂層API,表示和資料庫互動時的會話,完成必要資料庫增刪改查功能
Executor:MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢快取的維護
StatementHandler:封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數等
ParameterHandler:負責對使用者傳遞的引數轉換成JDBC Statement 所對應的資料型別
ResultSetHandler:負責將JDBC返回的ResultSet結果集物件轉換成List型別的集合
TypeHandler:負責java資料型別和jdbc資料型別(也可以說是資料表列型別)之間的對映和轉換
MappedStatement:MappedStatement維護一條<select|update|delete|insert>節點的封裝
SqlSource:負責根據使用者傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql物件中,並返回
BoundSql:表示動態生成的SQL語句以及相應的引數資訊
層次結構:
更多請參考:《深入理解mybatis原理》 MyBatis的架構設計以及例項分析
2.5、學習資源
ofollow,noindex">mybatis3中文幫助:http://www.mybatis.org/mybatis-3/zh/index.html
mybatis-spring:http://www.mybatis.org/spring/zh/index.html
MyBatis中國分站:http://www.mybatis.cn/
原始碼:https://github.com/mybatis/mybatis-3/
三、MyBatis快速入門示例
3.1、在IDEA中建立專案
普通java專案或者是Maven專案都可以,如下圖所示:
3.2、新增依賴
下載地址: https://github.com/mybatis/mybatis-3/releases
網盤下載: http://pan.baidu.com/s/1hrB1guo
【 MyBatis 】
mybatis-3.4.6.jar
【MYSQL驅動包】
mysql-connector-java-5.1.38-bin.jar
Maven POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhangguo.mybatis01</groupId> <artifactId>MyBatis01</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!--MySql資料庫驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!-- JUnit單元測試工具 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> </dependencies> </project>
3、建立資料庫和表,針對MySQL資料庫
SQL指令碼如下:
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10) NOT NULL, `sex` enum('boy','girl','secret') DEFAULT 'secret', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
將SQL指令碼在MySQL資料庫中執行,完成建立資料庫和表的操作,如下:
表中的資料如下:
3.3、新增Mybatis配置檔案
在Resources目錄下建立一個conf.xml檔案,如下圖所示:
conf.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="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/nfmall"/> <property name="username" value="root"/> <property name="password" value="uchr@123"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/studentMapper.xml"/> </mappers> </configuration>
解釋
3.4、定義表所對應的實體類
Student實體類的程式碼如下:
package com.zhangguo.mybatis01.entities; public class Student { private int id; private String name; private String sex; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", sex='" + sex + '\'' + '}'; } }
3.5、定義操作Student表的sql對映檔案
在resources目錄下建立一個mapper目錄,專門用於存放sql對映檔案,在目錄中建立一個studentMapper.xml檔案,如下圖所示:
studentMapper.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,namespace的值習慣上設定成包名+sql對映檔名,這樣就能夠保證namespace的值是唯一的 例如namespace="com.zhangguo.mybatis01.dao.studentMapper"就是com.zhangguo.mybatis01.dao(包名)+studentMapper(studentMapper.xml檔案去除字尾) --> <mapper namespace="com.zhangguo.mybatis01.dao.studentMapper"> <!-- 在select標籤中編寫查詢的SQL語句, 設定select標籤的id屬性為selectStudentById,id屬性值必須是唯一的,不能夠重複 使用parameterType屬性指明查詢時使用的引數型別,resultType屬性指明查詢返回的結果集型別 resultType="com.zhangguo.mybatis01.entities.Student"就表示將查詢結果封裝成一個Student類的物件返回 Student類就是student表所對應的實體類 --> <!-- 根據id查詢得到一個user物件 --> <select id="selectStudentById" resultType="com.zhangguo.mybatis01.entities.Student"> select * from student where id = #{id} </select> </mapper>
解釋
參考:https://www.cnblogs.com/hellokitty1/p/5216025.html
3.6、在配置檔案中註冊對映檔案
在配置檔案conf.xml中註冊studentMapper.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="development"> <environment id="development"> <!-- 配置資料庫連線資訊 --> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/nfmall"/> <property name="username" value="root"/> <property name="password" value="uchr@123"/> </dataSource> </environment> </environments> <mappers> <!-- 註冊studentMapper.xml檔案studentMapper.xml位於mapper這個目錄下,所以resource寫成mapper/studentMapper.xml--> <mapper resource="mapper/studentMapper.xml"/> </mappers> </configuration>
3.7、編寫資料訪問類
StudentDao.java,執行定義的select語句
package com.zhangguo.mybatis01.dao; import com.zhangguo.mybatis01.entities.Student; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; public class StudentDao { public Student getStudentById(int id){ //使用類載入器載入mybatis的配置檔案(它也載入關聯的對映檔案) InputStream stream=StudentDao.class.getClassLoader().getResourceAsStream("conf.xml"); //構建sqlSession的工廠 SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(stream); //使用MyBatis提供的Resources類載入mybatis的配置檔案(它也載入關聯的對映檔案) //Reader reader = Resources.getResourceAsReader(resource); //構建sqlSession的工廠 //SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); //建立能執行對映檔案中sql的sqlSession SqlSession session=ssf.openSession(); /** * 對映sql的標識字串, * com.zhangguo.mybatis01.dao.studentMapper是studentMapper.xml檔案中mapper標籤的namespace屬性的值, * selectStudentById是select標籤的id屬性值,通過select標籤的id屬性值就可以找到要執行的SQL */ Student student=session.selectOne("com.zhangguo.mybatis01.dao.studentMapper.selectStudentById",1); return student; } public static void main(String[] args) { StudentDao dao=new StudentDao(); Student student=dao.getStudentById(1); System.out.println(student); } }
執行過程解釋:
1、mybatis配置
SqlMapConfig.xml,此檔案作為mybatis的全域性配置檔案,配置了mybatis的執行環境等資訊。
mapper.xml檔案即sql對映檔案,檔案中配置了操作資料庫的sql語句。此檔案需要在SqlMapConfig.xml中載入。
2、通過mybatis環境等配置資訊構造SqlSessionFactory即會話工廠
3、由會話工廠建立sqlSession即會話,操作資料庫需要通過sqlSession進行。
4、mybatis底層自定義了Executor執行器介面操作資料庫,Executor介面有兩個實現,一個是基本執行器、一個是快取執行器。
5、Mapped Statement也是mybatis一個底層封裝物件,它包裝了mybatis配置資訊及sql對映資訊等。mapper.xml檔案中一個sql對應一個Mapped Statement物件,sql的id即是Mapped statement的id。
6、Mapped Statement對sql執行輸入引數進行定義,包括HashMap、基本型別、pojo,Executor通過Mapped Statement在執行sql前將輸入的java物件對映至sql中,輸入引數對映就是jdbc程式設計中對preparedStatement設定引數。
7、Mapped Statement對sql執行輸出結果進行定義,包括HashMap、基本型別、pojo,Executor通過Mapped Statement在執行sql後將輸出結果對映至java物件中,輸出結果對映過程相當於jdbc程式設計中對結果的解析處理過程。
參考:https://www.cnblogs.com/selene/p/4604605.html
3.8、編寫單元測試
import com.zhangguo.mybatis01.dao.StudentDao; import com.zhangguo.mybatis01.entities.Student; import org.junit.Test; import org.junit.Before; import org.junit.After; /** * StudentDao Tester. * * @author <Authors name> * @version 1.0 * @since <pre>09/24/2018</pre> */ public class StudentDaoTest { @Before public void before() throws Exception { } @After public void after() throws Exception { } /** * Method: getStudentById(int id) */ @Test public void testGetStudentById() throws Exception { StudentDao dao=new StudentDao(); Student student=dao.getStudentById(1); System.out.println(student); } }
測試結果:
3.9、IDEA中Junit外掛與生成測試類位置
開啟IntelliJ IDEA工具,Alt+Ctrl+S,開啟設定視窗,點選進入Plugins。
從外掛資源庫中搜索JunitGenerator V2.0版本
安裝此外掛,重啟IDEA就可以了。
現在可通過此工具自動完成test類的生成了,在需要進行單元測試的類中按 Alt+Insert
選中你要建立測試用例的方法即可。
IntelliJ IDEA JUnit Generator自動建立測試用例到指定test目錄
1.開啟File->Settings 2.搜尋junit,找到JUnit Generator 3.Properties選項卡里的Output Path為測試用例生成的目錄,修改為test目錄: SOURCEPATH/../../test/java/SOURCEPATH/../../test/java/{PACKAGE}/${FILENAME} 4.切換到JUnit 4選項卡,可以修改生成測試用例的模板,比如類名、包名等
修改生成位置:
修改模板檔案:
測試類生成目錄分析:
${SOURCEPATH}/test/${PACKAGE}/${FILENAME} $SOURCEPATH/../../test/java/{PACKAGE}/${FILENAME}
對應的目錄結構為
${SOURCEPATH}是到src/main/java這一層
../是退到上一層目錄的意思,對著圖理解一下
四、基於XML對映實現完整資料訪問
MyBatis可以使用XML或註解作為對映器的描述,XML強大且可以解偶,註解方便且簡單。
因為每一個操作都需要先拿到會話,這裡先定義一個工具類以便複用:
會話工具類:
package com.zhangguo.mybatis02.utils; import com.zhangguo.mybatis02.dao.StudentDao; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; /** * MyBatis 會話工具類 * */ public class SqlSessionFactoryUtil { /** * 獲得會話工廠 * * */ public static SqlSessionFactory getFactory(){ InputStream inputStream = null; SqlSessionFactory sqlSessionFactory=null; try{ //載入conf.xml配置檔案,轉換成輸入流 inputStream = StudentDao.class.getClassLoader().getResourceAsStream("conf.xml"); //根據配置檔案的輸入流構造一個SQL會話工廠 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } finally { if(inputStream!=null){ try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return sqlSessionFactory; } /** * 獲得sql會話,是否自動提交 * */ public static SqlSession openSession(boolean isAutoCommit){ return getFactory().openSession(isAutoCommit); } /** * 關閉會話 * */ public static void closeSession(SqlSession session){ if(session!=null){ session.close(); } } }
XML對映器studentMapper:
<?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="com.zhangguo.mybatis02.mapper.studentMapper"> <select id="selectStudentById" resultType="com.zhangguo.mybatis02.entities.Student"> SELECT id,name,sex from student where id=#{id} </select> <select id="selectStudentsByName" parameterType="String" resultType="com.zhangguo.mybatis02.entities.Student"> SELECT id,name,sex from student where name like '%${value}%'; </select> <insert id="insertStudent" parameterType="com.zhangguo.mybatis02.entities.Student"> insert into student(name,sex) VALUES(#{name},'${sex}') </insert> <update id="updateStudent" parameterType="com.zhangguo.mybatis02.entities.Student"> update student set name=#{name},sex=#{sex} where id=#{id} </update> <delete id="deleteStudent" parameterType="int"> delete from student where id=#{id} </delete> </mapper>
資料訪問類StudentDao.java:
package com.zhangguo.mybatis02.dao; import com.zhangguo.mybatis02.entities.Student; import com.zhangguo.mybatis02.utils.SqlSessionFactoryUtil; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.util.List; public class StudentDao { /** * 根據學生編號獲得學生物件 */ public Student selectStudentById(int id) { Student entity = null; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //查詢單個物件,指定引數為3 entity = session.selectOne("com.zhangguo.mybatis02.mapper.studentMapper.selectStudentById", id); //關閉 SqlSessionFactoryUtil.closeSession(session); return entity; } /** * 根據學生姓名獲得學生集合 */ public List<Student> selectStudentsByName(String name) { List<Student> entities = null; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //查詢多個物件,指定引數 entities = session.selectList("com.zhangguo.mybatis02.mapper.studentMapper.selectStudentsByName", name); //關閉 SqlSessionFactoryUtil.closeSession(session); return entities; } /** * 新增學生 */ public int insertStudent(Student entity) { //影響行數 int rows=0; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //執行新增 rows = session.insert("com.zhangguo.mybatis02.mapper.studentMapper.insertStudent", entity); //關閉 SqlSessionFactoryUtil.closeSession(session); return rows; } /** * 更新學生 */ public int updateStudent(Student entity) { //影響行數 int rows=0; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //執行更新 rows = session.update("com.zhangguo.mybatis02.mapper.studentMapper.updateStudent", entity); //關閉 SqlSessionFactoryUtil.closeSession(session); return rows; } /** * 刪除學生 */ public int deleteStudent(int id) { //影響行數 int rows=0; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //執行刪除 rows = session.delete("com.zhangguo.mybatis02.mapper.studentMapper.deleteStudent", id); //關閉 SqlSessionFactoryUtil.closeSession(session); return rows; } }
單元測試:
package com.zhangguo.mybatis02.dao; import com.zhangguo.mybatis02.entities.Student; import org.junit.Assert; import org.junit.Test; import org.junit.Before; import org.junit.After; import java.util.List; /** * StudentDao Tester. * * @author <Authors name> * @version 1.0 * @since <pre>09/26/2018</pre> */ public class StudentDaoTest { StudentDao dao; @Before public void before() throws Exception { dao=new StudentDao(); } @After public void after() throws Exception { } /** * Method: selectStudentById(int id) */ @Test public void testSelectStudentById() throws Exception { Student entity=dao.selectStudentById(1); System.out.println(entity); Assert.assertNotNull(entity); } /** * Method: selectStudentsByName(String name) */ @Test public void testSelectStudentsByName() throws Exception { List<Student> students=dao.selectStudentsByName("i"); System.out.println(students); Assert.assertNotNull(students); } /** * Method: insertStudent */ @Test public void testInsertStudent() throws Exception { Student entity=new Student(); entity.setName("瑪麗"); entity.setSex("girl"); Assert.assertEquals(1,dao.insertStudent(entity)); } /** * Method: updateStudent */ @Test public void testUpdateStudent() throws Exception { Student entity=dao.selectStudentById(3); entity.setName("馬力"); entity.setSex("boy"); Assert.assertEquals(1,dao.updateStudent(entity)); } /** * Method: deleteStudent */ @Test public void testDeleteStudent() throws Exception { Assert.assertEquals(1,dao.deleteStudent(2)); } }
測試結果:
參考對映檔案1:
<?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:需要和mapper介面的全限定名一致 --> <mapper namespace="com.san.mapper.UserMapper"> <!-- 通過ID查詢使用者 --> <select id="findUserById" parameterType="int" resultType="user"> select * from user where id=#{id} </select> <!-- 定義sql片段 --> <!-- sql片段內,可以定義sql語句中的任何內容 --> <!-- sql片段內,最好不要使用where和select關鍵字宣告在內 --> <sql id="whereClause"> <!-- if標籤:對輸入的引數進行判斷 --> <!-- test標籤:指定判斷的表示式 --> <if test="user!=null"> <!-- 判斷使用者名稱不為空 --> <if test="user.username!=null and user.username!=''"> and username like '%${user.username}%' </if> <!-- 判斷性別不為空 --> <if test="user.sex!=null and user.sex!=''"> and sex=#{user.sex} </if> </if> <!-- 判斷集合 --> <!-- collection:表示pojo中集合屬性的屬性名稱 --> <!-- item:為遍歷出的結果宣告一個變數名稱 --> <!-- open:遍歷開始時,需要拼接的字串 --> <!-- close:遍歷結束時,需要拼接的字串 --> <!-- separator:遍歷中間需要拼接的字串 --> <if test="idList!=null"> and id in <foreach collection="idList" item="id" open="(" close=")" separator=","> <!-- and id in (#{id},#{id},#{id}) --> #{id} </foreach> </if> </sql> <!-- 綜合查詢,查詢使用者列表 --> <!-- #{}中的引數名稱要和包裝pojo中的物件層級一致,並且屬性名稱要一致 --> <select id="findUserList" parameterType="com.san.model.UserQueryvo" resultType="user"> select * from user <!-- where標籤:預設去掉後面第一個and,如果沒有引數,則把自己幹掉 --> <where> <!-- 引入sql片段 --> <include refid="whereClause"></include> </where> </select> <!-- 綜合查詢,查詢使用者的總數 --> <select id="findUserCount" parameterType="com.san.model.UserQueryvo" resultType="int"> select count(*) from user <where> <include refid="whereClause"></include> </where> </select> <!-- id標籤:專門為查詢結果中唯一列對映 --> <!-- result標籤:對映查詢結果中的普通列 --> <!-- type標籤:返回型別 --> <resultMap type="user" id="UserResMap"> <id column="id_" property="id"/> <result column="username_" property="username"/> <result column="sex_" property="sex"/> </resultMap> <select id="findUserRstMap" parameterType="int" resultMap="UserResMap"> select id id_,username username_,sex sex_ from user where id=#{id} </select> </mapper> View Code
參考對映檔案2:
<?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有特殊作用 --> <mapper namespace="test"> <!-- 在對映檔案中配置很多sql語句 --> <!-- 需求:通過Id查詢使用者表的記錄 --> <!-- 通過SELECT執行資料庫查詢 id:標識對映檔案中的sql,稱為statement的id; 將sql語句封裝在mapperStatement的物件中,所以Id稱為Statement的id; parameterType:指定輸入引數的型別,這裡指定int型 #{}:表示一個佔位符; #{id}:其中Id表示接收輸入的引數,引數名稱就是Id,如果輸入引數是簡單型別,#{}中的引數名可以任意,可以是value或者其它名稱; resultType:指定sql輸出結果所對映的java物件型別,select指定resultType表示將單條記錄對映成java物件。 --> <select id="findUserById" parameterType="int" resultType="com.mybatis.entity.User" > select * from t_user where id=#{id} </select> <!-- 根據使用者名稱稱模糊查詢使用者資訊,可能返回多條資料 resultType:指定的就是單條記錄所對映的java型別; ${}:表示拼接sql字串,將接收到的引數內容不加任何修飾拼接在sql中. 使用${}拼接sql,可能會引起sql注入 ${value}:接收輸入引數的內容,如果傳入的是簡單型別,${}中只能使用value --> <select id="findUserByName" parameterType="java.lang.String" resultType="com.mybatis.entity.User" > select * from t_user where username LIKE '%${value}%' </select> <!-- 新增使用者 parameterType:指定輸入的引數型別是pojo(包括使用者資訊); #{}中指定pojo的屬性名稱,接收到pojo物件的屬性值,mybatis通過OGNL(類似struts2的OGNL)獲取物件的屬性值 --> <insert id="insertUser" parameterType="com.mybatis.entity.User" > <!-- 將insert插入的資料的主鍵返回到User物件中; select last_insert_id():得到剛insert進去記錄的主鍵值,只適用於自增主鍵; keyProperty:將查詢到的主鍵值,設定到parameterType指定的物件的那個屬性 order:select last_insert_id()執行順序,相對於insert語句來說它的執行順序。 resultType:指定select last_insert_id()的結果型別; --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select last_insert_id() </selectKey> <!-- 使用mysql的uuid(),實現非自增主鍵的返回。 執行過程:通過uuid()得到主鍵,將主鍵設定到user物件的Id的屬性中,其次,在insert執行時,從user物件中取出Id屬性值; <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String"> select uuid() </selectKey> insert into t_user (id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address}) --> insert into t_user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert> <!-- 刪除使用者 根據ID刪除使用者,需要輸入Id值 --> <delete id="deleteUser" parameterType="java.lang.Integer"> delete from t_user where id=#{id} </delete> <!-- 更新使用者 需要傳入使用者的Id和使用者的更新資訊 parameterType:指定User物件,包括Id和使用者的更新資訊,注意:Id是必須存在的 #{id}:從輸入的User物件中獲取Id的屬性值 --> <update id="updateUser" parameterType="com.mybatis.entity.User"> update t_user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} </update> </mapper> View Code
五、基於註解對映實完整資料訪問
對映器,StudentMapper介面:
package com.zhangguo.mybatis02.dao; import com.zhangguo.mybatis02.entities.Student; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import java.util.List; public interface StudentMapper { /** * 根據學生編號獲得學生物件 */ @Select("select id,name,sex from student where id=#{id}") Student selectStudentById(int id); /** * 根據學生姓名獲得學生集合 */ @Select("SELECT id,name,sex from student where name like '%${value}%'") List<Student> selectStudentsByName(String name); /** * 新增學生 */ @Insert("insert into student(name,sex) values(#{name},#{sex})") int insertStudent(Student entity); /** * 更新學生 */ @Update("update student set name=#{name},sex=#{sex} where id=#{id}") int updateStudent(Student entity); /** * 刪除學生 */ @Delete("delete from student where id=#{id}") int deleteStudent(int id); }
MyBatis配置檔案:
<?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="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/nfmall?useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="uchr@123"/> </dataSource> </environment> </environments> <mappers> <!--<mapper resource="mapper/studentMapper.xml"/>--> <mapper class="com.zhangguo.mybatis02.dao.StudentMapper"></mapper> </mappers> </configuration>
StudentDaoAnno.java實現對student的資料訪問:
package com.zhangguo.mybatis02.dao; import com.zhangguo.mybatis02.entities.Student; import com.zhangguo.mybatis02.utils.SqlSessionFactoryUtil; import org.apache.ibatis.session.SqlSession; import java.util.List; public class StudentDaoAnno implements StudentMapper { /** * 根據學生編號獲得學生物件 */ public Student selectStudentById(int id) { Student entity = null; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //獲得一個對映器 StudentMapper mapper=session.getMapper(StudentMapper.class); //查詢單個物件,指定引數為3 entity = mapper.selectStudentById(id); //關閉 SqlSessionFactoryUtil.closeSession(session); return entity; } /** * 根據學生姓名獲得學生集合 */ public List<Student> selectStudentsByName(String name) { List<Student> entities = null; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //獲得一個對映器 StudentMapper mapper=session.getMapper(StudentMapper.class); //查詢多個物件,指定引數 entities =mapper.selectStudentsByName(name); //關閉 SqlSessionFactoryUtil.closeSession(session); return entities; } /** * 新增學生 */ public int insertStudent(Student entity) { //影響行數 int rows=0; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //獲得一個對映器 StudentMapper mapper=session.getMapper(StudentMapper.class); //執行新增 rows = mapper.insertStudent(entity); //關閉 SqlSessionFactoryUtil.closeSession(session); return rows; } /** * 更新學生 */ public int updateStudent(Student entity) { //影響行數 int rows=0; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //獲得一個對映器 StudentMapper mapper=session.getMapper(StudentMapper.class); //執行更新 rows =mapper.updateStudent(entity); //關閉 SqlSessionFactoryUtil.closeSession(session); return rows; } /** * 刪除學生 */ public int deleteStudent(int id) { //影響行數 int rows=0; //開啟一個會話 SqlSession session = SqlSessionFactoryUtil.openSession(true); //獲得一個對映器 StudentMapper mapper=session.getMapper(StudentMapper.class); //執行刪除 rows = mapper.deleteStudent(id); //關閉 SqlSessionFactoryUtil.closeSession(session); return rows; } }
單元測試:
package com.zhangguo.mybatis02.dao; import com.zhangguo.mybatis02.entities.Student; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.List; /** * StudentDao Tester. * * @author <Authors name> * @version 1.0 * @since <pre>09/26/2018</pre> */ public class StudentDaoAnnoTest { StudentMapper dao; @Before public void before() throws Exception { dao=new StudentDaoAnno(); } @After public void after() throws Exception { } /** * Method: selectStudentById(int id) */ @Test public void testSelectStudentById() throws Exception { Student entity=dao.selectStudentById(1); System.out.println(entity); Assert.assertNotNull(entity); } /** * Method: selectStudentsByName(String name) */ @Test public void testSelectStudentsByName() throws Exception { List<Student> students=dao.selectStudentsByName("e"); System.out.println(students); Assert.assertNotNull(students); } /** * Method: insertStudent */ @Test public void testInsertStudent() throws Exception { Student entity=new Student(); entity.setName("張小強"); entity.setSex("boy"); Assert.assertEquals(1,dao.insertStudent(entity)); } /** * Method: updateStudent */ @Test public void testUpdateStudent() throws Exception { Student entity=dao.selectStudentById(7); entity.setName("張美麗"); entity.setSex("girl"); Assert.assertEquals(1,dao.updateStudent(entity)); } /** * Method: deleteStudent */ @Test public void testDeleteStudent() throws Exception { Assert.assertEquals(1,dao.deleteStudent(7)); } }
測試結果:
參考對映檔案:
package com.winterchen.mapper; import com.winterchen.domain.User; import org.apache.ibatis.annotations.*; import java.util.List; import java.util.Map; /** * User對映類 * Created by Administrator on 2017/11/24. */ @Mapper public interface UserMapper { @Select("SELECT * FROM T_USER WHERE PHONE = #{phone}") User findUserByPhone(@Param("phone") String phone); @Insert("INSERT INTO T_USER(NAME, PASSWORD, PHONE) VALUES(#{name}, #{password}, #{phone})") int insert(@Param("name") String name, @Param("password") String password, @Param("phone") String phone); @Insert("INSERT INTO T_USER(NAME, PASSWORD, PHONE) VALUES(" + "#{name, jdbcType=VARCHAR}, #{password, jdbcType=VARCHAR}, #{phone, jdbcType=VARCHAR})") int insertByMap(Map<String, Object> map); @Insert("INSERT INTO T_USER(NAME, PASSWORD, PHONE) VALUES(#{name}, #{password}, #{phone})") int insertByUser(User user); @Update("UPDATE T_USER SET NAME = #{name}, PASSWORD = #{password} WHERE PHONE = #{phone}") void update(User user); @Delete("DELETE FROM T_USER WHERE ID = #{id}") void delete(Integer id); @Results({ @Result(property = "name", column = "NAME"), @Result(property = "password", column = "PASSWORD"), @Result(property = "phone", column = "PHONE") }) @Select("SELECT NAME, PASSWORD, PHONE FROM T_USER") List<User> findAll(); } View Code
六、說明與注意事項
6.1、parameterType和resultType的區別
parameterType:在對映檔案中通過parameterType指定 輸入引數的型別 。
resultType:在對映檔案中通過resultType指定 輸出結果的型別
6.2、#{}和${}的區別
#{}
#{}表示一個佔位符號,#{}接收輸入引數,型別可以是簡單型別,pojo、hashmap;
如果接收簡單型別,#{}中可以寫成value或其它名稱;
#{}接收pojo物件值,通過OGNL讀取物件中的屬性值,通過屬性.屬性.屬性...的方式獲取物件屬性值。使用#{}意味著使用的預編譯的語句,即在使用jdbc時的preparedStatement,sql語句中如果存在引數則會使用?作佔位符,我們知道這種方式可以防止sql注入,並且在使用#{}時形成的sql語句,已經帶有引號,例,select? * from table1 where id=#{id}? 在呼叫這個語句時我們可以通過後臺看到打印出的sql為:select * from table1 where id='2' 加入傳的值為2.也就是說在組成sql語句的時候把引數預設為字串。
${}
表示一個拼接符號,會引用sql注入,所以不建議使用${};
${}接收輸入引數,型別可以是簡單型別,pojo、hashmap;
如果接收簡單型別,${}中只能寫成value;
${}接收pojo物件值,通過OGNL讀取物件中的屬性值,通過屬性.屬性.屬性...的方式獲取物件屬性值。
使用${}時的sql不會當做字串處理,是什麼就是什麼,如上邊的語句:select * from table1 where id=${id} 在呼叫這個語句時控制檯列印的為:select * from table1 where id=2 ,假設傳的引數值為2
從上邊的介紹可以看出這兩種方式的區別,我們最好是能用#{}則用它,因為它可以防止sql注入,且是預編譯的,在需要原樣輸出時才使用${},如,
select * from ${tableName} order by ${id} 這裡需要傳入表名和按照哪個列進行排序 ,加入傳入table1、id 則語句為:select * from table1 order by id
如果是使用#{} 則變成了select * from 'table1' order by 'id' 我們知道這樣就不對了。
6.3、selectOne()和selectList()的區別
selectOne表示查詢出 一條記錄 進行對映。如果使用selectOne可以實現使用selectList也可以實現(list中只有一個物件),如果查詢結果為多條則會報錯。
selectList表示查詢出一個列表( 多條記錄 )進行對映,可以是0到n條記錄返回。
6.4、對映器選擇XML還是註解
以下是MyBatis官網對Mapper Annotations的解釋:
Mapper Annotations
Since the very beginning, MyBatis has been an XML driven framework. The configuration is XML based, and the Mapped Statements are defined in XML. With MyBatis 3, there are new options available. MyBatis 3 builds on top of a comprehensive and powerful Java based Configuration API. This Configuration API is the foundation for the XML based MyBatis configuration, as well as the new Annotation based configuration. Annotations offer a simple way to implement simple mapped statements without introducing a lot of overhead.
NOTE :Java Annotations are unfortunately limited in their expressiveness and flexibility. Despite a lot of time spent in investigation, design and trials, the most powerful MyBatis mappings simply cannot be built with Annotations – without getting ridiculous that is. C# Attributes (for example) do not suffer from these limitations, and thus MyBatis.NET will enjoy a much richer alternative to XML. That said, the Java Annotation based configuration is not without its benefits.
翻譯:
(最初MyBatis是基於XML驅動的框架。MyBatis的配置是基於XML的,語句對映也是用XML定義的。對於MyBatis3,有了新的可選方案。MyBatis3 是建立在全面且強大的Java配置API之上的。 該配置API是MyBatis基於XML配置的基礎,也是基於註解配置的基礎。註解提供了簡單的方式去實現簡單的對映語句,不需要花費大量的開銷。
注意:很不幸的是,java註解在表現和靈活性上存在限制。雖然在調研、設計和測試上花費了很多時間,但是最強大的MyBatis對映功能卻無法用註解實現。這沒有什麼可笑的。舉例來說,C#的特性就沒有這個限制,所以MyBatis.NET 能擁有一個功能豐富的多的XML替代方案。所以,Java基於註解的配置是依賴於其自身特性的。)
長遠來看建議選擇XML作為對映器
http://www.mybatis.org/mybatis-3/java-api.html
七、視訊
https://www.bilibili.com/video/av32447485/
八、示例
https://git.coding.net/zhangguo5/MyBatis02.git
九、作業
1、請使用MyBatis完成一個使用者管理的資料訪問功能,要求實現根據使用者名稱查詢使用者物件(id,username,password,name,email,state)功能,表中至少5個欄位。
2、請分別使用XML與註解兩種方式實現物件使用者表(Users)的單條記錄查詢、多條記錄查詢、增加、修改與刪除功能,要求單元測試通過。
3、新增使用者成功後返回使用者的編號,而不是影響行數。(選作)
4、實現多個條件組合查詢,類似在電商平臺購物可以選擇0-n個條件,且可以自由組合。(選作)
5、實現分頁功能。(選作)