MyBatis概述
MyBatis本是Apache的一個開源項目iBatis, 2010年這個項目由apache software foundation遷移到了google code,並且改名爲MyBatis。2013年11月遷移到Github。
MyBatis 是一個優秀的持久層框架,它對jdbc的操作數據庫的過程進行封裝,使開發者只需要關注 SQL 本身,而不需要花費精力去處理例如註冊驅動、 創建connection、創建 statement、手動設置參數、結果集檢索等 jdbc繁雜的過程代碼。
Mybatis 通過 xml 或註解的方式將要執行的各種 statement(statement、 preparedStatemnt、CallableStatement)配置起來,並通過 java 對象和 statement 中的 sql 進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射成java對象並返回。
使用JDBC存在的問題
JDBC的編程步驟
- 加載數據庫驅動
- 創建獲取數據庫連接
- 創建Statement對象
- 執行SQL語句並返回ResultSet結果集
- 處理結果集
- 釋放資源
存在的問題
- 數據庫連接創建、釋放頻繁造成系統資源浪費,從而影響系統性能。如果使用數據庫連接池可解決此問題。
- SQL語句在代碼中硬編碼,造成代碼不易維護,實際應用中SQL變化的可能較大,SQL語句變動需要改變 java代碼。
- 使用preparedStatement向佔位符傳參數存在硬編碼,因爲sql語句的where條件不一定,可能多也可能少,修改sql還要修改代碼,系統不易維護。
- 對結果集解析存在硬編碼(查詢列名),sql變化導致解析代碼變化,系統不易維護,如果能將數據庫記錄封裝成pojo對象解析比較方便。
MyBatis架構
MyBatis架構圖
架構圖解釋
MyBatis配置文件
-
SqlMapConfig.xml
MyBatis的全局配置文件,配置了MyBatis的運行環境等信息
-
SqlSessionFactory
會話工廠,用於創建SqlSession,通過MyBatis環境等配置信息構造SqlSessionFactory
-
SqlSession
會話,操作數據庫主要通過SqlSession進行
-
Executor
MyBatis底層自定義了Executor執行器接口操作數據庫,Executor接口有兩個實現,一個是基本執行器、一個是緩存執行器
-
MappedStatement
Mapped Statement也是mybatis一個底層封裝對象,它包裝了mybatis配置信息及sql映射信息等。mapper.xml文件中一個sql對應一個Mapped Statement對象,sql的id即是Mapped statement的id。
-
輸入映射
Mapped Statement對sql執行輸入參數進行定義,包括HashMap、基本類型、pojo,Executor通過Mapped Statement在執行sql前將輸入的java對象映射至sql中,輸入參數映射就是jdbc編程中對preparedStatement設置參數
-
輸出映射
Mapped Statement對sql執行輸出結果進行定義,包括HashMap、基本類型、pojo,Executor通過Mapped Statement在執行sql後將輸出結果映射至java對象中,輸出結果映射過程相當於jdbc編程中對結果的解析處理過程
-
Mybatis入門程序
MyBatis下載
點擊上述鏈接進入頁面後,再點擊下圖中的位置即可下載;下載完畢後壓縮包中有MyBatis的依賴包(在lib目錄下)。
環境配置
-
導入MyBatis依賴包以及數據庫連接驅動
-
編寫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> <!-- 與spring整合以後,environments配置將被廢除 --> <environments default="development"> <environment id="development"> <!--事務管理,使用jdbc--> <transactionManager type="jdbc"></transactionManager> <!--數據庫連接池--> <dataSource type="POOLED"> <!--配置數據庫連接信息--> <property name="driver" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/mybatistest?characterEncoding=UTF-8"></property> <property name="username" value="數據庫用戶名"></property> <property name="password" value="數據庫密碼"></property> </dataSource> </environment> </environments> </configuration>
-
編寫日誌輸出文件log4j.properties,日誌輸出文件的編寫如下:
# Global logging configuration log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
-
在數據庫中創建表,我創建的是user表:
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT '用戶名稱', `birthday` date DEFAULT NULL COMMENT '生日', `sex` char(1) DEFAULT NULL COMMENT '性別', `address` varchar(256) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
-
編寫數據庫中表的對應的pojo(Plain Ordinary Java Object,其實就是Java Bean)
-
編寫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 -> <mapper namespace="com.xurenyi.pojo.User"> <!-- id:sql的id parameterType:輸入參數的類型 resultType:輸出結果的類型,應該填寫對應POJO的全路徑 #{v}:佔位符,相當於jdbc中的? --> <select id="findUserById" parameterType="Integer" resultType="com.xurenyi.pojo.User"> select * from user where id=#{v} </select> </mapper>
-
在SqlMapperConfig.xml中配置UserMapper文件,寫法如下:
<!--配置UserMapper.xml文件的位置--> <mappers> <mapper resource="config/UserMapper.xml"></mapper> </mappers>
案例
根據id查詢單個用戶
- 在UserMapper.xml中編寫SQL語句:
<!-- mapper:用於編寫SQL語句,mapper元素中可以寫多個SQL語句 namespace:命名空間,用於隔離SQL --> <mapper namespace="com.xurenyi.pojo.User"> <!-- id:SQL語句的id parameterType:輸入參數的類型 resultType:輸出結果的類型,應該填寫對應POJO的全路徑 '#{v}':佔位符,相當於jdbc中的? --> <!-- 根據id查詢單個用戶 --> <select id="findUserById" parameterType="Integer" resultType="com.xurenyi.pojo.User"> select * from user where id=${value} </select> </mapper>
- 編寫測試方法:
//根據id查找單個用戶 @Test public void findUserById() throws IOException { //加載SqlMapConfig.xml配置文件,把配置文件的路徑作爲參數傳入 InputStream in = Resources.getResourceAsStream("config/SqlMapConfig.xml"); //創建SqlSessionFactoryBuilder SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); //獲取SqlSessionFactory(根據配置文件創建SqlSessionFactory) SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(in); //獲取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //執行select操作,傳入參數是SQL語句的id、SQL語句中的查找參數 User user = sqlSession.selectOne("findUserById", 1); System.out.println(user); }
注意: 由於下面的例子中測試代碼的前面部分與根據id查詢單個用戶
中的測試代碼是一樣的,所以在下述的例子中,就只寫SQL語句(寫在<mapper></mapper>
元素中)以及通過sqlSession
進行操作數據庫的語句(例如根據id查詢單個用戶
測試代碼中的User user = sqlSession.selectOne("findUserById", 1);
這句),省略獲取SqlSession對象的代碼。
根據用戶名模糊查詢用戶
- 方式一
- SQL語句
<!--模糊查詢--> <select id="findUserListByLike" parameterType="String" resultType="com.xurenyi.pojo.User"> select * from user where username like #{v} </select>
- 測試代碼
/* 獲取SqlSession對象的代碼,略 */ List<User> userList = sqlSession.selectList("findUserListByLike", "%張%");
- SQL語句
- 方式二
- SQL語句
<!--模糊查詢--> <select id="findUserListByLike" parameterType="String" resultType="com.xurenyi.pojo.User"> select * from user where username like '%${value}%' </select>
- 測試代碼
/* 獲取SqlSession對象的代碼,略 */ List<User> userList = sqlSession.selectList("findUserListByLike", "張");
- SQL語句
- 方式三
- SQL語句
<!--模糊查詢--> <select id="findUserListByLike" parameterType="String" resultType="com.xurenyi.pojo.User"> select * from user where username like "%"#{v}"%" </select>
- 測試代碼
/* 獲取SqlSession對象的代碼,略 */ List<User> userList = sqlSession.selectList("findUserListByLike", "張");
- SQL語句
添加用戶
- SQL語句
<!--添加用戶 參數類型是User對象,所以在SQL語句中取參數值的時候,需要把User實體類裏面的屬性名寫進去,而不能在'#{}'裏面隨便寫幾個字母 --> <insert id="addUser" parameterType="com.xurenyi.pojo.User"> <!--取參數的時候需要把User裏面的屬性寫進'#{}',而不能隨便寫--> insert into user values(#{id},#{username},#{birthday},#{sex},#{address}) </insert>
- 測試代碼
/* 獲取SqlSession對象的代碼,略 */ User user = new User(); user.setId(null); user.setUsername("用戶名"); user.setBirthday(new Date()); user.setSex("男"); user.setAddress("用戶地址"); // 需要"改變"數據庫中數據的操作(例如:插入、刪除、修改數據)都有一個int類型的返回值,該返回值表示對數據庫中記錄影響的行數 int addUser = sqlSession.insert("addUser", user); sqlSession.commit(); //需要自己提交事務(在增刪改查這四個操作中,除了查詢操作,其餘三個操作都需要提交事務)
添加用戶返回ID
- 需求:在使用MyBatis插入一個用戶數據以後,獲取該用戶的id。
- select LAST_INSERT_ID();
- 這是MySQL中提供的查詢最新添加數據的id,但是執行該條語句時,需要先執行一條插入語句才能返回id,否則就返回0。
- SQL語句
<insert id="addUser" parameterType="com.xurenyi.pojo.User"> <!-- 標籤實現主鍵返回 keyColumn:主鍵對應數據庫表中的哪一列 keyProperty:主鍵對應pojo中的哪一個屬性 order:設置在執行insert語句之前執行查詢id的操作,還是在執行insert語句之後執行查詢id的操作 --> <selectKey keyColumn="id" keyProperty="id" resultType="Integer" order="AFTER"> select LAST_INSERT_ID() </selectKey> <!--取參數的時候需要把User裏面的屬性寫進#{},而不能隨便寫--> insert into user values(#{id},#{username},#{birthday},#{sex},#{address}) </insert>
- 測試代碼
/* 獲取SqlSession對象的代碼,略 */ User user = new User(); user.setId(null); user.setUsername("用戶名"); user.setBirthday(new Date()); user.setSex("男"); user.setAddress("用戶地址"); // 需要"改變"數據庫中數據的操作(例如:插入、刪除、修改數據)都有一個int類型的返回值,該返回值表示對數據庫中記錄影響的行數 int addUser = sqlSession.insert("addUser", user); sqlSession.commit(); //需要自己提交事務(在增刪改查這四個操作中,除了查詢操作,其餘三個操作都需要提交事務) System.out.println(user.getId()); //在提交事務之後獲取id並打印
根據id修改用戶數據
- SQL語句
<!--根據id修改用戶數據--> <update id="updateUserById" parameterType="com.xurenyi.pojo.User"> update user set address=#{address},birthday=#{birthday} where id=#{id} </update>
- 測試代碼
/* 獲取SqlSession對象的代碼,略 */ User user = new User(); user.setId(28); user.setAddress("修改後的地址"); user.setUsername("修改後的姓名"); user.setSex("女"); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Date date = simpleDateFormat.parse("1994-01-01 17:06:00"); user.setBirthday(date); sqlSession.update("updateUserById", user); //提交事務 sqlSession.commit(); //關閉sqlSession sqlSession.close();
根據id刪除用戶
- SQL語句
<!--根據id刪除用戶數據--> <delete id="deleteUserById" parameterType="Integer"> delete from user where id=#{id} </delete>
- 測試代碼
/* 獲取SqlSession對象的代碼,略 */ int count=sqlSession.delete("deleteUserById",28); //提交事務 sqlSession.commit(); System.out.println(count); //關閉sqlSession sqlSession.close();
小結
- #{}和${}的區別
- #{}
#{}表示一個佔位符號,通過#{}可以實現preparedStatement向佔位符中設置值,自動進行java類型和jdbc類型轉換。#{}可以有效防止sql注入。 #{}可以接收簡單類型值或pojo屬性值。 如果parameterType傳輸單個簡單類型值, #{}括號中可以是 value 或其它名稱。 - $ {}
$ {}表示拼接 sql 串,通過$ {}可以將parameterType傳入的內容拼接在sql中且不進行 jdbc類型轉換,$ {}可以接收簡單類型值或 pojo 屬性值,如果 parameterType 傳輸單個簡單類型值,${}括號中只能是 value。
- #{}
- parameterType和resultType
- parameterType
指定輸入參數類型,mybatis通過ognl從輸入對象中獲取參數值拼接在 sql 中。 - resultType
指定輸出結果類型,mybatis 將 sql 查詢結果的一行記錄數據映射爲resultType 指定類型的對象。如果有多條數據,則分別進行映射,並把對象放到容器 List 中。
- parameterType
- selectOne和selectList
- selectOne
selectOne用於查詢一條記錄,如果使用selectOne查詢多條記錄會報錯。 - selectList
selectList用於查詢一條或多條記錄。
- selectOne
- MyBatis解決JDBC編程的問題
-
問題1
- 描述:數據庫連接創建、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用數據庫連接池可解決此問題
- 解決:在 SqlMapConfig.xml 中配置數據連接池,使用連接池管理數據庫鏈接
-
問題2
- 描述:Sql 語句寫在代碼中造成代碼不易維護,實際應用 sql 變化的可能較大,sql 變動需要改變 java代碼
- 解決:將 Sql 語句配置在 XXXXmapper.xml 文件中與 java 代碼分離
-
問題3
- 描述:向sql語句傳參數麻煩,因爲sql語句的where條件不一定,可能多也可能少,佔位符需要和參數一一對應
- 解決:Mybatis 自動將 java 對象映射至 sql 語句,通過 statement 中的 parameterType 定義輸入參數的類型
-
問題4
- 描述:對結果集解析麻煩,sql變化導致解析代碼變化,且解析前需要遍歷,如果 能將數據庫記錄封裝成pojo對象解析比較方便
- 解決:Mybatis自動將sql執行結果映射至java對象,通過statement中的 resultType定義輸出結果的類型
-
- MyBatis和Hibernate的異同
- Mybatis和hibernate不同,它不完全是一個ORM框架,因爲MyBatis需要程序員自己編寫Sql語句。mybatis可以通過XML或註解方式靈活配置要運行的sql語句,並將java對象和sql語句映射生成最終執行的sql,最後將sql執行的結果再映射生成java對象。
- Mybatis學習門檻低,簡單易學,程序員直接編寫原生態sql,可嚴格控制sql執行性能,靈活度高,非常適合對關係數據模型要求不高的軟件開發,例如:互聯網軟件、企業運營類軟件等,因爲這類軟件需求變化頻繁,一旦需求變化要求成果輸出迅速。但是靈活的前提是 mybatis 無法做到數據庫無關性,如果需要實現支持多種數據庫的軟件則需要自定義多套 sql 映射文件,工作量大。
- Hibernate 對象/關係映射能力強,數據庫無關性好,對於關係模型要求高的軟件(例如需求固定的定製化軟件)如果用hibernate開發可以節省很多代碼,提高效率。但是Hibernate的學習門檻高,要精通門檻更高,而且怎麼設計O/R映射,在性能和對象模型之間如何權衡,以及怎樣用好Hibernate需要具有很強的經驗和能力才行。
- 總之,按照用戶的需求在有限的資源環境下只要能做出維護性、擴展性良好的軟件架構都是好架構,所以框架只有適合纔是最好。
SqlMapConfig.xml配置文件詳解
SqlMapConfig.xml中配置的內容和順序
這裏選講幾個元素。
-
properties(屬性)
-
properties是用來加載外部配置文件的,其寫法如下:
<!--使用resource加載外部配置文件--> <properties resource="外部配置文件的路徑"> <!--如果外部配置文件中有該屬性,那麼內部定義的屬性被外部屬性覆蓋--> <property name="" value=""/> </properties>
-
MyBatis加載屬性的順序
先讀取properties元素體內自定義的屬性,再讀取properties中resource指定路徑的外部配置文件中的屬性,如果在外部配置文件中有同名屬性,則後者會覆蓋前者。
-
-
typeAliases(類型別名)
有時候寫實體類的全路徑比較麻煩,所以可以取一個別名,這樣可以在Mapper.xml中直接寫別名調用。
-
定義單個別名
定義單個別名的配置寫法如下:
<typeAliases> <!--定義單個別名 type:需要被取別名的對象全路徑 alias:對應的別名 --> <typeAlias type="com.xurenyi.pojo.User" alias="user"></typeAlias> </typeAliases>
-
批量定義別名
批量定義別名的配置寫法如下:
<typeAliases> <!--批量定義別名 會掃描整個包下的類,別名是類名(大小寫不敏感) --> <package name="com.xurenyi.pojo"></package>
-
-
mappers(映射器)
-
用法1
<mapper resource="mapper.xml配置文件的全路徑"></mapper>
-
用法2
<mapper class="對應的mapper類的路徑"></mapper>
此種方法要求mapper接口名稱和mapper映射文件名稱相同,且放在同一個目錄中。
-
用法3
<package name="包路徑"></mapper>
此種方法要求mapper接口名稱和mapper映射文件名稱相同,且放在同一個目錄中。
實際項目開發中,會使用這種方法。
-