MyBatis学习随记
1?????? Mybatis入門
1.1???? 單獨使用jdbc編程問題總結
1.1.1? jdbc程序
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", "mysql");
????????????? //定義sql語句 ?表示占位符
?????????? String sql = "select * from user where username = ?";
????????????? //獲取預處理statement
????????????? preparedStatement = connection.prepareStatement(sql);
????????????? //設置參數,第一個參數為sql語句中參數的序號(從1開始),第二個參數為設置的參數值
????????????? preparedStatement.setString(1, "王五");
????????????? //向數據庫發出sql執行查詢,查詢出結果集
????????????? resultSet =? preparedStatement.executeQuery();
????????????? //遍歷查詢結果集
??????????????while(resultSet.next()){
????????????????? System.out.println(resultSet.getString("id")+"? "+resultSet.getString("username"));
????????????? }
?????????? }?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();
????????????????? }
????????????? }
?
?????????? }
?
?????? }
?
上邊使用jdbc的原始方法(未經封裝)實現了查詢數據庫表記錄的操作。
1.1.2? jdbc編程步驟:
1、? 加載數據庫驅動
2、? 創建并獲取數據庫鏈接
3、? 創建jdbc statement對象
4、? 設置sql語句
5、? 設置sql語句中的參數(使用preparedStatement)
6、? 通過statement執行sql并獲取結果
7、? 對sql執行結果進行解析處理
8、? 釋放資源(resultSet、preparedstatement、connection)
?
1.1.3? jdbc問題總結如下:
1、? 數據庫鏈接創建、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用數據庫鏈接池可解決此問題。
2、? Sql語句在代碼中硬編碼,造成代碼不易維護,實際應用sql變化的可能較大,sql變動需要改變java代碼。
3、? 使用preparedStatement向占有位符號傳參數存在硬編碼,因為sql語句的where條件不一定,可能多也可能少,修改sql還要修改代碼,系統不易維護。
4、? 對結果集解析存在硬編碼(查詢列名),sql變化導致解析代碼變化,系統不易維護,如果能將數據庫記錄封裝成pojo對象解析比較方便。
?
1.2???? MyBatis介紹
MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,并且改名為MyBatis,實質上Mybatis對ibatis進行一些改進。
MyBatis是一個優秀的持久層框架,它對jdbc的操作數據庫的過程進行封裝,使開發者只需要關注 SQL 本身,而不需要花費精力去處理例如注冊驅動、創建connection、創建statement、手動設置參數、結果集檢索等jdbc繁雜的過程代碼。
Mybatis通過xml或注解的方式將要執行的各種statement(statement、preparedStatemnt、CallableStatement)配置起來,并通過java對象和statement中的sql進行映射生成最終執行的sql語句,最后由mybatis框架執行sql并將結果映射成java對象并返回。
?
?
1.3???? Mybatis架構
?
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編程中對結果的解析處理過程。
?
1.4???? mybatis下載
mybaits的代碼由github.com管理,地址:https://github.com/mybatis/mybatis-3/releases
mybatis-3.2.7.jar----mybatis的核心包
lib----mybatis的依賴包
mybatis-3.2.7.pdf----mybatis使用手冊
?
1.5???? 創建mysql數據庫
先導入sql_table.sql,再導入 sql_data.sql腳本:
如下:
1.6???? Mybatis入門程序
?
1.6.1? 需求
實現以下功能:
根據用戶id查詢一個用戶信息
根據用戶名稱模糊查詢用戶信息列表
添加用戶
更新用戶
刪除用戶
?
1.6.2? 第一步:創建java工程
使用eclipse創建java工程,jdk使用1.7.0_72。
1.6.3? 第二步:加入jar包
加入mybatis核心包、依賴包、數據驅動包。
?
?
1.6.4? 第三步:log4j.properties
在classpath下創建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
?
mybatis默認使用log4j作為輸出日志信息。
?
1.6.5? 第四步:SqlMapConfig.xml
在classpath下創建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"?/>
????? <!-- 數據庫連接池-->
???????? <dataSource type="POOLED">
??????????? <property name="driver"?value="com.mysql.jdbc.Driver"?/>
??????????? <property name="url"?value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"?/>
??????????? <property name="username"?value="root"?/>
??????????? <property name="password"?value="mysql"?/>
???????? </dataSource>
????? </environment>
?? </environments>
??
</configuration>
?
SqlMapConfig.xml是mybatis核心配置文件,上邊文件的配置內容為數據源、事務管理。
1.6.6? 第五步:po類
?
Po類作為mybatis進行sql映射使用,po類通常與數據庫表對應,User.java如下:
?
Public class?User {
????private int?id;
????private?String username;// 用戶姓名
????private?String sex;// 性別
????private?Date birthday;// 生日
????private?String address;// 地址
get/set……
?
1.6.7? 第六步:程序編寫
1.6.7.1 查詢
?
1.6.7.1.1?????? 映射文件:
在classpath下的sqlmap目錄下創建sql映射文件Users.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="test">
</mapper>
?
namespace :命名空間,用于隔離sql語句,后面會講另一層非常重要的作用。
?
?
在SqlMapConfig.xml中添加:
<!-- 根據id獲取用戶信息 -->
??? <select id="findUserById"?parameterType="int"?resultType="cn.itcast.mybatis.po.User">
?????? select * from user where id = #{id}
??? </select>
??? <!-- 自定義條件查詢用戶列表 -->
??? <select id="findUserByUsername"?parameterType="java.lang.String"
?????????? resultType="cn.itcast.mybatis.po.User">
??? ?? select * from user where?username?like '%${value}%'
??? </select>
?
parameterType:定義輸入到sql中的映射類型,#{id}表示使用preparedstatement設置占位符號并將輸入變量id傳到sql。
resultType:定義結果映射類型。
?
1.6.7.1.2?????? 加載映射文件
mybatis框架需要加載映射文件,將Users.xml添加在SqlMapConfig.xml,如下:
?
<mappers>
?????? <mapper resource="sqlmap/User.xml"/>
</mappers>
?
?
1.6.7.1.3?????? 測試程序:
?
public?class?Mybatis_first {
???
??? //會話工廠
????private?SqlSessionFactory sqlSessionFactory;
?
??? @Before
????public?void?createSqlSessionFactory()?throws?IOException {
?????? // 配置文件
?????? String resource = "SqlMapConfig.xml";
?????? InputStream inputStream = Resources.getResourceAsStream(resource);
?
?????? // 使用SqlSessionFactoryBuilder從xml配置文件中創建SqlSessionFactory
?????? sqlSessionFactory =?new?SqlSessionFactoryBuilder()
????????????? .build(inputStream);
?
??? }
?
??? // 根據 id查詢用戶信息
?? @Test
???public?void?testFindUserById() {
????? // 數據庫會話實例
????? SqlSession sqlSession =?null;
??????try?{
???????? // 創建數據庫會話實例sqlSession
???????? sqlSession = sqlSessionFactory.openSession();
???????? // 查詢單個記錄,根據用戶id查詢用戶信息
???????? User user = sqlSession.selectOne("test.findUserById", 10);
???????? // 輸出用戶信息
???????? System.out.println(user);
????? }?catch?(Exception e) {
???????? e.printStackTrace();
????? }?finally?{
?????????if?(sqlSession !=?null) {
??????????? sqlSession.close();
???????? }
????? }
?
?? }
?
?? // 根據用戶名稱模糊查詢用戶信息
?? @Test
???public?void?testFindUserByUsername() {
????? // 數據庫會話實例
????? SqlSession sqlSession =?null;
??????try?{
???????? // 創建數據庫會話實例sqlSession
???????? sqlSession = sqlSessionFactory.openSession();
???????? // 查詢單個記錄,根據用戶id查詢用戶信息
???????? List<User> list = sqlSession.selectList("test.findUserByUsername", "張");
???????? System.out.println(list.size());
????? }?catch?(Exception e) {
???????? e.printStackTrace();
????? }?finally?{
?????????if?(sqlSession !=?null) {
??????????? sqlSession.close();
???????? }
????? }
?
?? }
}
?
1.6.7.1.4?????? #{}和${}
#{}表示一個占位符號,通過#{}可以實現preparedStatement向占位符中設置值,自動進行java類型和jdbc類型轉換,#{}可以有效防止sql注入。 #{}可以接收簡單類型值或pojo屬性值。 如果parameterType傳輸單個簡單類型值,#{}括號中可以是value或其它名稱。
?
${}表示拼接sql串,通過${}可以將parameterType 傳入的內容拼接在sql中且不進行jdbc類型轉換, ${}可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,${}括號中只能是value。
?
?
1.6.7.1.5?????? parameterType和resultType
parameterType:指定輸入參數類型,mybatis通過ognl從輸入對象中獲取參數值拼接在sql中。
resultType:指定輸出結果類型,mybatis將sql查詢結果的一行記錄數據映射為resultType指定類型的對象。
?
1.6.7.1.6?????? selectOne和selectList
?
selectOne查詢一條記錄,如果使用selectOne查詢多條記錄則拋出異常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
??? at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
?
selectList可以查詢一條或多條記錄。
?
1.6.7.2 添加
1.6.7.2.1?????? 映射文件:
在SqlMapConfig.xml中添加:
<!-- 添加用戶 -->
??? <insert id="insertUser"?parameterType="cn.itcast.mybatis.po.User">
??? <selectKey keyProperty="id"?order="AFTER"?resultType="java.lang.Integer">
?????? select LAST_INSERT_ID()
??? </selectKey>
??? ? insert into user(username,birthday,sex,address)
??? ? values(#{username},#{birthday},#{sex},#{address})
??? </insert>
?
1.6.7.2.2?????? 測試程序:
// 添加用戶信息
??? @Test
????public?void?testInsert() {
?????? // 數據庫會話實例
?????? SqlSession sqlSession =?null;
???????try?{
?????????? // 創建數據庫會話實例sqlSession
?????????? sqlSession = sqlSessionFactory.openSession();
?????????? // 添加用戶信息
?????????? User user =?new User();
?????????? user.setUsername("張小明");
?????????? user.setAddress("河南鄭州");
?????????? user.setSex("1");
?????????? user.setPrice(1999.9f);
?????????? sqlSession.insert("test.insertUser", user);
?????????? //提交事務
?????????? sqlSession.commit();
?????? }?catch?(Exception e) {
?????????? e.printStackTrace();
?????? }?finally?{
???????????if?(sqlSession !=?null) {
????????????? sqlSession.close();
?????????? }
?????? }
??? }
1.6.7.2.3?????? mysql自增主鍵返回
通過修改sql映射文件,可以將mysql自增主鍵返回:
<insert id="insertUser"?parameterType="cn.itcast.mybatis.po.User">
?????? <!-- selectKey將主鍵返回,需要再返回 -->
?????? <selectKey keyProperty="id"?order="AFTER"?resultType="java.lang.Integer">
?????????? select LAST_INSERT_ID()
?????? </selectKey>
??? ?? insert into user(username,birthday,sex,address)
??? ??? values(#{username},#{birthday},#{sex},#{address});
??? </insert>
添加selectKey實現將主鍵返回
keyProperty:返回的主鍵存儲在pojo中的哪個屬性
order:selectKey的執行順序,是相對與insert語句來說,由于mysql的自增原理執行完insert語句之后才將主鍵生成,所以這里selectKey的執行順序為after
resultType:返回的主鍵是什么類型
LAST_INSERT_ID():是mysql的函數,返回auto_increment自增列新記錄id值。
?
1.6.7.2.4?????? Mysql使用 uuid實現主鍵
?
需要增加通過select uuid()得到uuid值
?
<insert ?id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.String" order="BEFORE"
keyProperty="id">
select uuid()
</selectKey>
insert into user(id,username,birthday,sex,address)
????? ?values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
注意這里使用的order是“BEFORE”
?
?
1.6.7.2.5?????? Oracle使用序列生成主鍵
首先自定義一個序列且用于生成主鍵,selectKey使用如下:
<insert ?id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.Integer" order="BEFORE"
keyProperty="id">
SELECT 自定義序列.NEXTVAL FROM DUAL
</selectKey>
insert into user(id,username,birthday,sex,address)
????? ?values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
注意這里使用的order是“BEFORE”
1.6.7.3 刪除
1.6.7.3.1?????? 映射文件:
?
<!-- 刪除用戶 -->
??? <delete id="deleteUserById"?parameterType="int">
?????? delete from user where id=#{id}
??? </delete>
?
?
1.6.7.3.2?????? 測試程序:
?
// 根據id刪除用戶
??? @Test
????public?void?testDelete() {
?????? // 數據庫會話實例
?????? SqlSession sqlSession =?null;
???????try?{
?????????? // 創建數據庫會話實例sqlSession
?????????? sqlSession = sqlSessionFactory.openSession();
?????????? // 刪除用戶
?????????? sqlSession.delete("test.deleteUserById",18);
?????????? // 提交事務
?????????? sqlSession.commit();
?????? }?catch?(Exception e) {
?????????? e.printStackTrace();
?????? }?finally?{
???????????if?(sqlSession !=?null) {
????????????? sqlSession.close();
?????????? }
?????? }
??? }
1.6.7.4 修改
1.6.7.4.1?????? 映射文件
?
<!-- 更新用戶 -->
??? <update id="updateUser"?parameterType="cn.itcast.mybatis.po.User">
?????? update user set?username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
?????? where id=#{id}
??? </update>
?
1.6.7.4.2?????? 測試程序
?
// 更新用戶信息
??? @Test
????public?void?testUpdate() {
?????? // 數據庫會話實例
?????? SqlSession sqlSession =?null;
???????try?{
?????????? // 創建數據庫會話實例sqlSession
?????????? sqlSession = sqlSessionFactory.openSession();
?????????? // 添加用戶信息
?????????? User user =?new User();
?????????? user.setId(16);
?????????? user.setUsername("張小明");
?????????? user.setAddress("河南鄭州");
?????????? user.setSex("1");
?????????? user.setPrice(1999.9f);
?????????? sqlSession.update("test.updateUser", user);
?????????? // 提交事務
?????????? sqlSession.commit();
?
?????? }?catch?(Exception e) {
?????????? e.printStackTrace();
?????? }?finally?{
???????????if?(sqlSession !=?null) {
????????????? sqlSession.close();
?????????? }
?????? }
??? }
?
?
1.6.8? 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定義輸出結果的類型。
?
?
1.6.9? 與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需要具有很強的經驗和能力才行。
總之,按照用戶的需求在有限的資源環境下只要能做出維護性、擴展性良好的軟件架構都是好架構,所以框架只有適合才是最好。?
?
?
2?????? Dao開發方法
???????? 使用Mybatis開發Dao,通常有兩個方法,即原始Dao開發方法和Mapper接口開發方法。
?
2.1???? 需求
將下邊的功能實現Dao:
根據用戶id查詢一個用戶信息
根據用戶名稱模糊查詢用戶信息列表
添加用戶信息
?
2.2???? SqlSession的使用范圍
???????? SqlSession中封裝了對數據庫的操作,如:查詢、插入、更新、刪除等。
通過SqlSessionFactory創建SqlSession,而SqlSessionFactory是通過SqlSessionFactoryBuilder進行創建。
?
2.2.1? SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于創建SqlSessionFacoty,SqlSessionFacoty一旦創建完成就不需要SqlSessionFactoryBuilder了,因為SqlSession是通過SqlSessionFactory生產,所以可以將SqlSessionFactoryBuilder當成一個工具類使用,最佳使用范圍是方法范圍即方法體內局部變量。
?
2.2.2? SqlSessionFactory
???????? SqlSessionFactory是一個接口,接口中定義了openSession的不同重載方法,SqlSessionFactory的最佳使用范圍是整個應用運行期間,一旦創建后可以重復使用,通常以單例模式管理SqlSessionFactory。
?
2.2.3? SqlSession
???????? SqlSession是一個面向用戶的接口, sqlSession中定義了數據庫操作,默認使用DefaultSqlSession實現類。
?
執行過程如下:
1、? 加載數據源等配置信息
Environment environment = configuration.getEnvironment();
2、? 創建數據庫鏈接
3、? 創建事務對象
4、? 創建Executor,SqlSession所有操作都是通過Executor完成,mybatis源碼如下:
?
if?(ExecutorType.BATCH?== executorType) {
????? executor =?newBatchExecutor(this, transaction);
??? }?elseif?(ExecutorType.REUSE?== executorType) {
????? executor =?new?ReuseExecutor(this, transaction);
??? }?else?{
????? executor =?new?SimpleExecutor(this, transaction);
??? }
if?(cacheEnabled) {
????? executor =?new?CachingExecutor(executor, autoCommit);
??? }
?
5、? SqlSession的實現類即DefaultSqlSession,此對象中對操作數據庫實質上用的是Executor
?
結論:
???????? 每個線程都應該有它自己的SqlSession實例。SqlSession的實例不能共享使用,它也是線程不安全的。因此最佳的范圍是請求或方法范圍。絕對不能將SqlSession實例的引用放在一個類的靜態字段或實例字段中。
???????? 打開一個 SqlSession;使用完畢就要關閉它。通常把這個關閉操作放到 finally 塊中以確保每次都能執行關閉。如下:
???????? SqlSession session = sqlSessionFactory.openSession();
???????? try {
???????????????? ?// do work
???????? } finally {
? ????????????? session.close();
???????? }
?
?
2.3???? 原始Dao開發方式
???????? 原始Dao開發方法需要程序員編寫Dao接口和Dao實現類。
2.3.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">
<mapper namespace="test">
<!-- 根據id獲取用戶信息 -->
??? <select id="findUserById"?parameterType="int"?resultType="cn.itcast.mybatis.po.User">
?????? select * from user where id = #{id}
??? </select>
<!-- 添加用戶 -->
??? <insert id="insertUser"?parameterType="cn.itcast.mybatis.po.User">
??? <selectKey keyProperty="id"?order="AFTER"?resultType="java.lang.Integer">
?????? select LAST_INSERT_ID()
??? </selectKey>
??? ? insert into user(username,birthday,sex,address)
??? ? values(#{username},#{birthday},#{sex},#{address})
??? </insert>
</mapper>
?
?
2.3.2? Dao接口
Public interface?UserDao {
????public?User getUserById(int?id)?throws?Exception;
????public void?insertUser(User user)?throws?Exception;
}
?
Public class?UserDaoImpl?implements?UserDao {
???
??? //注入SqlSessionFactory
????public?UserDaoImpl(SqlSessionFactory sqlSessionFactory){
???????this.setSqlSessionFactory(sqlSessionFactory);
??? }
???
????private?SqlSessionFactory sqlSessionFactory;
??? @Override
????public User?getUserById(int?id)?throws?Exception {
?????? SqlSession session = sqlSessionFactory.openSession();
?????? User user =?null;
???????try?{
?????????? //通過sqlsession調用selectOne方法獲取一條結果集
?????????? //參數1:指定定義的statement的id,參數2:指定向statement中傳遞的參數
?????????? user = session.selectOne("test.findUserById", 1);
?????????? System.out.println(user);
????????????????????
?????? }?finally{
?????????? session.close();
?????? }
???????return?user;
??? }
???
??? @Override
????Public void?insertUser(User user)?throws?Exception {
?????? SqlSession sqlSession = sqlSessionFactory.openSession();
???????try?{
?????????? sqlSession.insert("insertUser", user);
?????????? sqlSession.commit();
?????? }?finally{
?????????? session.close();
?????? }
??????
??? }
}
?
2.3.3? 問題
原始Dao開發中存在以下問題:
u? Dao方法體存在重復代碼:通過SqlSessionFactory創建SqlSession,調用SqlSession的數據庫操作方法
u? 調用sqlSession的數據庫操作方法需要指定statement的id,這里存在硬編碼,不得于開發維護。
?
2.4???? Mapper動態代理方式?????
2.4.1? 實現原理
???????? Mapper接口開發方法只需要程序員編寫Mapper接口(相當于Dao接口),由Mybatis框架根據接口定義創建接口的動態代理對象,代理對象的方法體同上邊Dao接口實現類方法。
Mapper接口開發需要遵循以下規范:
1、? Mapper.xml文件中的namespace與mapper接口的類路徑相同。
2、? Mapper接口方法名和Mapper.xml中定義的每個statement的id相同
3、? Mapper接口方法的輸入參數類型和mapper.xml中定義的每個sql 的parameterType的類型相同
4、? Mapper接口方法的輸出參數類型和mapper.xml中定義的每個sql的resultType的類型相同
2.4.2? Mapper.xml(映射文件)
???????? 定義mapper映射文件UserMapper.xml(內容同Users.xml),需要修改namespace的值為 UserMapper接口路徑。將UserMapper.xml放在classpath 下mapper目錄 下。
?
<?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.mybatis.mapper.UserMapper">
<!-- 根據id獲取用戶信息 -->
??? <select id="findUserById"?parameterType="int"?resultType="cn.itcast.mybatis.po.User">
?????? select * from user where id = #{id}
??? </select>
<!-- 自定義條件查詢用戶列表 -->
??? <select id="findUserByUsername"?parameterType="java.lang.String"
?????????? resultType="cn.itcast.mybatis.po.User">
??? ?? select * from user where?username?like '%${value}%'
??? </select>
<!-- 添加用戶 -->
??? <insert id="insertUser"?parameterType="cn.itcast.mybatis.po.User">
??? <selectKey keyProperty="id"?order="AFTER"?resultType="java.lang.Integer">
?????? select LAST_INSERT_ID()
??? </selectKey>
??? ? insert into user(username,birthday,sex,address)
??? ? values(#{username},#{birthday},#{sex},#{address})
??? </insert>
?
</mapper>
?
2.4.3? Mapper.java(接口文件)
?
/**
?* 用戶管理mapper
?*/
Public interface?UserMapper {
??? //根據用戶id查詢用戶信息
????public?User findUserById(int?id)?throws?Exception;
??? //查詢用戶列表
????public?List<User> findUserByUsername(String username)?throws?Exception;
??? //添加用戶信息
????public?void?insertUser(User user)throws?Exception;
}
?
接口定義有如下特點:
1、? Mapper接口方法名和Mapper.xml中定義的statement的id相同
2、? Mapper接口方法的輸入參數類型和mapper.xml中定義的statement的parameterType的類型相同
3、? Mapper接口方法的輸出參數類型和mapper.xml中定義的statement的resultType的類型相同
?
2.4.4? 加載UserMapper.xml文件
修改SqlMapConfig.xml文件:
?
? <!-- 加載映射文件 -->
? <mappers>
??? <mapper resource="mapper/UserMapper.xml"/>
? </mappers>
?
?
2.4.5? 測試
?
Public class?UserMapperTest?extends?TestCase {
?
????private?SqlSessionFactory sqlSessionFactory;
???
????protected void?setUp()?throws?Exception {
?????? //mybatis配置文件
?????? String resource = "sqlMapConfig.xml";
?????? InputStream inputStream = Resources.getResourceAsStream(resource);
?????? //使用SqlSessionFactoryBuilder創建sessionFactory
?????? sqlSessionFactory =?new?SqlSessionFactoryBuilder().build(inputStream);
??? }
?
???
????Public void?testFindUserById()?throws?Exception {
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲取mapper接口的代理對象
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //調用代理對象方法
?????? User user = userMapper.findUserById(1);
?????? System.out.println(user);
?????? //關閉session
?????? session.close();
??????
??? }
??? @Test
????public?void?testFindUserByUsername()?throws?Exception {
?????? SqlSession sqlSession = sqlSessionFactory.openSession();
?????? UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
?????? List<User> list = userMapper.findUserByUsername("張");
?????? System.out.println(list.size());
?
??? }
Public void?testInsertUser()?throws?Exception {
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲取mapper接口的代理對象
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //要添加的數據
?????? User user =?new User();
?????? user.setUsername("張三");
?????? user.setBirthday(new?Date());
?????? user.setSex("1");
?????? user.setAddress("北京市");
?????? //通過mapper接口添加用戶
?????? userMapper.insertUser(user);
?????? //提交
?????? session.commit();
??? ??? //關閉session
?????? session.close();
??? }
???
?
}
?
2.4.6? 總結
u? selectOne和selectList
動態代理對象調用sqlSession.selectOne()和sqlSession.selectList()是根據mapper接口方法的返回值決定,如果返回list則調用selectList方法,如果返回單個對象則調用selectOne方法。
?
u? namespace
mybatis官方推薦使用mapper代理方法開發mapper接口,程序員不用編寫mapper接口實現類,使用mapper代理方法時,輸入參數可以使用pojo包裝對象或map對象,保證dao的通用性。
?
?
3?????? SqlMapConfig.xml配置文件
3.1???? 配置內容
SqlMapConfig.xml中配置的內容和順序如下:
?
properties(屬性)
settings(全局配置參數)
typeAliases(類型別名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環境集合屬性對象)
environment(環境子屬性對象)
transactionManager(事務管理)
dataSource(數據源)
mappers(映射器)
?
3.2???? properties(屬性)
?
SqlMapConfig.xml可以引用java屬性文件中的配置信息如下:
?
在classpath下定義db.properties文件,
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=mysql
?
?
SqlMapConfig.xml引用如下:
?
<properties resource="db.properties"/>
??? <environments default="development">
?????? <environment id="development">
?????????? <transactionManager type="JDBC"/>
?????????? <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>
?
注意: MyBatis 將按照下面的順序來加載屬性:
u? 在 properties 元素體內定義的屬性首先被讀取。
u? 然后會讀取properties 元素中resource或 url 加載的屬性,它會覆蓋已讀取的同名屬性。
u? 最后讀取parameterType傳遞的屬性,它會覆蓋已讀取的同名屬性。
?
因此,通過parameterType傳遞的屬性具有最高優先級,resource或 url 加載的屬性次之,最低優先級的是 properties 元素體內定義的屬性。
?
?
3.3???? settings(配置)
mybatis全局配置參數,全局參數將會影響mybatis的運行行為。
?
詳細參見“學習資料/mybatis-settings.xlsx”文件
?
?
?
?
?
?
3.4???? typeAliases(類型別名)
3.4.1? mybatis支持別名:
| 別名 | 映射的類型 |
| _byte | byte |
| _long | long |
| _short | short |
| _int | int |
| _integer | int |
| _double | double |
| _float | float |
| _boolean | boolean |
| string | String |
| byte | Byte |
| long | Long |
| short | Short |
| int | Integer |
| integer | Integer |
| double | Double |
| float | Float |
| boolean | Boolean |
| date | Date |
| decimal | BigDecimal |
| bigdecimal | BigDecimal |
?
?
3.4.2? 自定義別名:
在SqlMapConfig.xml中配置:
<typeAliases>
??? <!-- 單個別名定義 -->
??? <typeAlias alias="user"?type="cn.itcast.mybatis.po.User"/>
??? <!-- 批量別名定義,掃描整個包下的類,別名為類名(首字母大寫或小寫都可以) -->
??? <package name="cn.itcast.mybatis.po"/>
??? <package name="其它包"/>
</typeAliases>
?
?
3.5???? typeHandlers(類型處理器)
類型處理器用于java類型和jdbc類型映射,如下:
?
<select id="findUserById"?parameterType="int"?resultType="user">
?????? select * from user where id = #{id}
</select>
?
mybatis自帶的類型處理器基本上滿足日常需求,不需要單獨定義。
?
mybatis支持類型處理器:
?
| 類型處理器 | Java類型 | JDBC類型 |
| BooleanTypeHandler | Boolean,boolean | 任何兼容的布爾值 |
| ByteTypeHandler | Byte,byte | 任何兼容的數字或字節類型 |
| ShortTypeHandler | Short,short | 任何兼容的數字或短整型 |
| IntegerTypeHandler | Integer,int | 任何兼容的數字和整型 |
| LongTypeHandler | Long,long | 任何兼容的數字或長整型 |
| FloatTypeHandler | Float,float | 任何兼容的數字或單精度浮點型 |
| DoubleTypeHandler | Double,double | 任何兼容的數字或雙精度浮點型 |
| BigDecimalTypeHandler | BigDecimal | 任何兼容的數字或十進制小數類型 |
| StringTypeHandler | String | CHAR和VARCHAR類型 |
| ClobTypeHandler | String | CLOB和LONGVARCHAR類型 |
| NStringTypeHandler | String | NVARCHAR和NCHAR類型 |
| NClobTypeHandler | String | NCLOB類型 |
| ByteArrayTypeHandler | byte[] | 任何兼容的字節流類型 |
| BlobTypeHandler | byte[] | BLOB和LONGVARBINARY類型 |
| DateTypeHandler | Date(java.util) | TIMESTAMP類型 |
| DateOnlyTypeHandler | Date(java.util) | DATE類型 |
| TimeOnlyTypeHandler | Date(java.util) | TIME類型 |
| SqlTimestampTypeHandler | Timestamp(java.sql) | TIMESTAMP類型 |
| SqlDateTypeHandler | Date(java.sql) | DATE類型 |
| SqlTimeTypeHandler | Time(java.sql) | TIME類型 |
| ObjectTypeHandler | 任意 | 其他或未指定類型 |
| EnumTypeHandler | Enumeration類型 | VARCHAR-任何兼容的字符串類型,作為代碼存儲(而不是索引)。 |
?
3.6???? mappers(映射器)
Mapper配置的幾種方法:
3.6.1? <mapper resource=" " />
使用相對于類路徑的資源
如:<mapper resource="sqlmap/User.xml" />
?
3.6.2? <mapper url=" " />
使用完全限定路徑
如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
?
3.6.3? <mapper class=" " />
使用mapper接口類路徑
如:<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
?
注意:此種方法要求mapper接口名稱和mapper映射文件名稱相同,且放在同一個目錄中。
?
3.6.4? <package name=""/>
注冊指定包下的所有mapper接口
如:<package name="cn.itcast.mybatis.mapper"/>
注意:此種方法要求mapper接口名稱和mapper映射文件名稱相同,且放在同一個目錄中。
?
?
4?????? Mapper.xml映射文件
Mapper.xml映射文件中定義了操作數據庫的sql,每個sql是一個statement,映射文件是mybatis的核心。
?
4.1???? parameterType(輸入類型)
4.1.1?? #{}與${}
#{}實現的是向prepareStatement中的預處理語句中設置參數值,sql語句中#{}表示一個占位符即?。
?
<!-- 根據id查詢用戶信息 -->
??? <select id="findUserById"?parameterType="int"?resultType="user">
?????? select * from user where id = #{id}
??? </select>
使用占位符#{}可以有效防止sql注入,在使用時不需要關心參數值的類型,mybatis會自動進行java類型和jdbc類型的轉換。#{}可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,#{}括號中可以是value或其它名稱。
?
${}和#{}不同,通過${}可以將parameterType 傳入的內容拼接在sql中且不進行jdbc類型轉換, ${}可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,${}括號中只能是value。使用${}不能防止sql注入,但是有時用${}會非常方便,如下的例子:
?
<!-- 根據名稱模糊查詢用戶信息 -->
??? <select id="selectUserByName"?parameterType="string"?resultType="user">
??? ?? select * from user where?username?like '%${value}%'
??? </select>
?
如果本例子使用#{}則傳入的字符串中必須有%號,而%是人為拼接在參數中,顯然有點麻煩,如果采用${}在sql中拼接為%的方式則在調用mapper接口傳遞參數就方便很多。
?
//如果使用占位符號則必須人為在傳參數中加%
List<User> list = userMapper.selectUserByName("%管理員%");
?
?
//如果使用${}原始符號則不用人為在參數中加%
List<User>list?= userMapper.selectUserByName("管理員");
?
再比如order by排序,如果將列名通過參數傳入sql,根據傳的列名進行排序,應該寫為:
ORDER BY ${columnName}
如果使用#{}將無法實現此功能。
?
?
4.1.2?? 傳遞簡單類型
參考上邊的例子。
4.1.3?? 傳遞pojo對象
?
Mybatis使用ognl表達式解析對象字段的值,如下例子:
?
<!—傳遞pojo對象綜合查詢用戶信息 -->
??? <select id="findUserByUser"?parameterType="user"?resultType="user">
??? ?? select * from user where id=#{id} and username like '%${username}%'
??? </select>
?
上邊紅色標注的是user對象中的字段名稱。
?
測試:
?
Public void?testFindUserByUser()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //構造查詢條件user對象
?????? User user =?new User();
?????? user.setId(1);
?????? user.setUsername("管理員");
?????? //傳遞user對象查詢用戶列表
?????? List<User>list?= userMapper.findUserByUser(user);
?????? //關閉session
?????? session.close();
??? }
?
異常測試:
?
Sql中字段名輸入錯誤后測試,username輸入dusername測試結果報錯:
?
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.? Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'dusername' in 'class cn.itcast.mybatis.po.User'
### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'dusername' in 'class cn.itcast.mybatis.po.User'
?
4.1.4? 傳遞pojo包裝對象
?
???????? 開發中通過pojo傳遞查詢條件 ,查詢條件是綜合的查詢條件,不僅包括用戶查詢條件還包括其它的查詢條件(比如將用戶購買商品信息也作為查詢條件),這時可以使用包裝對象傳遞輸入參數。
?
4.1.4.1 定義包裝對象
定義包裝對象將查詢條件(pojo)以類組合的方式包裝起來。
?
public?class?QueryVo {
???
????private?User user;
???
??? //自定義用戶擴展類
????private?UserCustom userCustom;
?
?
4.1.4.2 mapper.xml映射文件
?
?
說明:mybatis底層通過ognl從pojo中獲取屬性值:#{user.username},user即是傳入的包裝對象的屬性。queryVo是別名,即上邊定義的包裝對象類型。
?
?
4.1.5?? 傳遞hashmap
Sql映射文件定義如下:
<!-- 傳遞hashmap綜合查詢用戶信息 -->
??? <select id="findUserByHashmap"?parameterType="hashmap"?resultType="user">
??? ?? select * from user where id=#{id} and username like '%${username}%'
??? </select>
?
上邊紅色標注的是hashmap的key。
?
測試:
Public void?testFindUserByHashmap()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //構造查詢條件Hashmap對象
?????? HashMap<String, Object> map =?new?HashMap<String, Object>();
?????? map.put("id", 1);
?????? map.put("username", "管理員");
??????
?????? //傳遞Hashmap對象查詢用戶列表
?????? List<User>list?= userMapper.findUserByHashmap(map);
?????? //關閉session
?????? session.close();
??? }
?
?
異常測試:
傳遞的map中的key和sql中解析的key不一致。
測試結果沒有報錯,只是通過key獲取值為空。
?
?
4.2???? resultType(輸出類型)
4.2.1? 輸出簡單類型
參考getnow輸出日期類型,看下邊的例子輸出整型:
?
Mapper.xml文件
<!-- 獲取用戶列表總數 -->
??? <select id="findUserCount"?parameterType="user"?resultType="int">
??? ?? select count(1) from user
??? </select>
?
Mapper接口
public int findUserCount(User user) throws Exception;
?
調用:
?
Public void?testFindUserCount()?throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲取mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
???
?????? User user =?new User();
?????? user.setUsername("管理員");
?
?????? //傳遞Hashmap對象查詢用戶列表
???????int?count?= userMapper.findUserCount(user);
??????
?????? //關閉session
?????? session.close();
??? }
?
總結:
輸出簡單類型必須查詢出來的結果集有一條記錄,最終將第一個字段的值轉換為輸出類型。
使用session的selectOne可查詢單條記錄。
?
?
4.2.2? 輸出pojo對象
參考findUserById的定義:
Mapper.xml
?? <!-- 根據id查詢用戶信息 -->
??? <select id="findUserById"?parameterType="int"?resultType="user">
?????? select * from user where id = #{id}
??? </select>
Mapper接口:
public User findUserById(int id) throws Exception;
?
測試:
?
Public void?testFindUserById()?throws?Exception {
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //通過mapper接口調用statement
?????? User user = userMapper.findUserById(1);
?????? System.out.println(user);
?????? //關閉session
?????? session.close();
??? }
使用session調用selectOne查詢單條記錄。
?
?
?
4.2.3? 輸出pojo列表
?
參考selectUserByName的定義:
Mapper.xml
<!-- 根據名稱模糊查詢用戶信息 -->
??? <select id="findUserByUsername"?parameterType="string"?resultType="user">
??? ?? select * from user where?username?like '%${value}%'
??? </select>
?
?
?
Mapper接口:
?
public List<User> findUserByUsername(String username) throws Exception;
?
測試:
Public void?testFindUserByUsername()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //如果使用占位符號則必須人為在傳參數中加%
?????? //List<User> list = userMapper.selectUserByName("%管理員%");
?????? //如果使用${}原始符號則不用人為在參數中加%
?????? List<User>?list?= userMapper.findUserByUsername("管理員");
?????? //關閉session
?????? session.close();
??? }
?
?
使用session的selectList方法獲取pojo列表。
?
4.2.4? resultType總結:
輸出pojo對象和輸出pojo列表在sql中定義的resultType是一樣的。
返回單個pojo對象要保證sql查詢出來的結果集為單條,內部使用session.selectOne方法調用,mapper接口使用pojo對象作為方法返回值。
?
返回pojo列表表示查詢出來的結果集可能為多條,內部使用session.selectList方法,mapper接口使用List<pojo>對象作為方法返回值。
?
?
4.2.5? 輸出hashmap
輸出pojo對象可以改用hashmap輸出類型,將輸出的字段名稱作為map的key,value為字段值。
?
4.3???? resultMap
???????? resultType可以指定pojo將查詢結果映射為pojo,但需要pojo的屬性名和sql查詢的列名一致方可映射成功。
???????? 如果sql查詢字段名和pojo的屬性名不一致,可以通過resultMap將字段名和屬性名作一個對應關系 ,resultMap實質上還需要將查詢結果映射到pojo對象中。
???????? resultMap可以實現將查詢結果映射為復雜類型的pojo,比如在查詢結果映射對象中包括pojo和list實現一對一查詢和一對多查詢。
????????
4.3.1? Mapper.xml定義
?
?
使用resultMap指定上邊定義的personmap。
?
4.3.2? 定義resultMap
由于上邊的mapper.xml中sql查詢列和Users.java類屬性不一致,需要定義resultMap:userListResultMap將sql查詢列和Users.java類屬性對應起來
?
?
?
<id />:此屬性表示查詢結果集的唯一標識,非常重要。如果是多個字段為復合唯一約束則定義多個<id />。
Property:表示person類的屬性。
Column:表示sql查詢出來的字段名。
Column和property放在一塊兒表示將sql查詢出來的字段映射到指定的pojo類屬性上。
?
<result />:普通結果,即pojo的屬性。
?
?
4.3.3? Mapper接口定義
?
public List<User> findUserListResultMap() throws Exception;
?
?
?
4.4???? 動態sql(重點)
通過mybatis提供的各種標簽方法實現動態拼接sql。
?
4.4.1? If
<!-- 傳遞pojo綜合查詢用戶信息 -->
??? <select id="findUserList"?parameterType="user"?resultType="user">
?????? select * from user
?????? where 1=1
?????? <if test="id!=null and id!=''">
?????? and id=#{id}
?????? </if>
?????? <if test="username!=null and username!=''">
?????? and?username?like '%${username}%'
?????? </if>
??? </select>
?
注意要做不等于空字符串校驗。
?
?
4.4.2? Where
上邊的sql也可以改為:
?
<select id="findUserList"?parameterType="user"?resultType="user">
?????? select * from user
?????? <where>
?????? <if test="id!=null and id!=''">
?????? and id=#{id}
??? ??? </if>
?????? <if test="username!=null and username!=''">
?????? and?username?like '%${username}%'
?????? </if>
?????? </where>
??? </select>
?
<where />可以自動處理第一個and。
?
?
4.4.3? foreach
?
向sql傳遞數組或List,mybatis使用foreach解析,如下:
?
4.4.3.1 通過pojo傳遞list
?
u? 需求
傳入多個id查詢用戶信息,用下邊兩個sql實現:
?
SELECT * FROM USERS WHERE username LIKE '%張%' AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE '%張%'? id IN (10,89,16)
?
u? 在pojo中定義list屬性ids存儲多個用戶id,并添加getter/setter方法
?
?
u? mapper.xml
?
<if test="ids!=null and ids.size>0">
??? ??? ??? <foreach collection="ids"?open=" and id in("?close=")"?item="id"?separator=","?>
??? ??? ?????? #{id}
??? ??? ??? </foreach>
</if>
?
?
u? 測試代碼:
?
List<Integer> ids =?new?ArrayList<Integer>();
?????? ids.add(1);//查詢id為1的用戶
?????? ids.add(10); //查詢id為10的用戶
?????? queryVo.setIds(ids);
?????? List<User> list = userMapper.findUserList(queryVo);
?
?
?
4.4.3.2 傳遞單個List
?
傳遞List類型在編寫mapper.xml沒有區別,唯一不同的是只有一個List參數時它的參數名為list。
?
如下:
u? Mapper.xml
<select id="selectUserByList"?parameterType="java.util.List"?resultType="user">
?????? select * from user
?????? <where>
?????? <!-- 傳遞List,List中是pojo?-->
?????? <if test="list!=null">
?????? <foreach collection="list"?item="item"?open="and id in("separator=","close=")">
?????? ??? #{item.id}
?????? </foreach>
?????? </if>
?????? </where>
??? </select>
?
u? Mapper接口
?
public List<User> selectUserByList(List userlist) throws Exception;
?
u? 測試:
Public void?testselectUserByList()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //構造查詢條件List
?????? List<User> userlist =?new?ArrayList<User>();
?????? User user =?new User();
?????? user.setId(1);
?????? userlist.add(user);
?????? user =?new User();
?????? user.setId(2);
?????? userlist.add(user);
?????? //傳遞userlist列表查詢用戶列表
?????? List<User>list?= userMapper.selectUserByList(userlist);
?????? //關閉session
?????? session.close();
??? }
?
?
4.4.3.3 傳遞單個數組(數組中是pojo):
請閱讀文檔學習。
u? Mapper.xml
?
<!-- 傳遞數組綜合查詢用戶信息 -->
??? <select id="selectUserByArray"?parameterType="Object[]"?resultType="user">
?????? select * from user
?????? <where>
?????? <!-- 傳遞數組 -->
?????? <if test="array!=null">
?????? <foreach collection="array"?index="index"?item="item"?open="and id in("separator=","close=")">
?????? ??? #{item.id}
?????? </foreach>
?????? </if>
?????? </where>
??? </select>
sql只接收一個數組參數,這時sql解析參數的名稱mybatis固定為array,如果數組是通過一個pojo傳遞到sql則參數的名稱為pojo中的屬性名。
index:為數組的下標。
item:為數組每個元素的名稱,名稱隨意定義
open:循環開始
close:循環結束
separator:中間分隔輸出
?
u? Mapper接口:
?
public List<User> selectUserByArray(Object[] userlist) throws Exception;
?
u? 測試:
?
Public void?testselectUserByArray()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //構造查詢條件List
?????? Object[] userlist =?new?Object[2];
?????? User user =?new User();
?????? user.setId(1);
?????? userlist[0]=user;
?????? user =?new User();
?????? user.setId(2);
?????? userlist[1]=user;
?????? //傳遞user對象查詢用戶列表
?????? List<User>list?= userMapper.selectUserByArray(userlist);
?????? //關閉session
?????? session.close();
??? }
?
4.4.3.4 傳遞單個數組(數組中是字符串類型):
請閱讀文檔學習。
?
u? Mapper.xml
?
<!-- 傳遞數組綜合查詢用戶信息 -->
??? <select id="selectUserByArray"?parameterType="Object[]"?resultType="user">
?????? select * from user
??? ??? <where>
?????? <!-- 傳遞數組 -->
?????? <if test="array!=null">
?????? <foreach collection="array"index="index"item="item"open="and id in("separator=","close=")">
?????? ??? #{item}
?????? </foreach>
?????? </if>
?????? </where>
??? </select>
如果數組中是簡單類型則寫為#{item},不用再通過ognl獲取對象屬性值了。
u? Mapper接口:
?
public List<User> selectUserByArray(Object[] userlist) throws Exception;
?
u? 測試:
?
Public void?testselectUserByArray()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //構造查詢條件List
?????? Object[] userlist =?new?Object[2];
?????? userlist[0]=”1”;
?????? userlist[1]=”2”;
?????? //傳遞user對象查詢用戶列表
?????? List<User>list?= userMapper.selectUserByArray(userlist);
?????? //關閉session
?????? session.close();
??? }
?
?
?
?
4.4.4? Sql片段
?
Sql中可將重復的sql提取出來,使用時用include引用即可,最終達到sql重用的目的,如下:
?
<!-- 傳遞pojo綜合查詢用戶信息 -->
??? <select id="findUserList"?parameterType="user"?resultType="user">
?????? select * from user
?????? <where>
?????? <if test="id!=null and id!=''">
?????? and id=#{id}
?????? </if>
?????? <if test="username!=null and username!=''">
?????? and?username?like '%${username}%'
?????? </if>
?????? </where>
??? </select>
?
u? 將where條件抽取出來:
?
<sql id="query_user_where">
??? <if test="id!=null and id!=''">
?????? and id=#{id}
??? </if>
??? <if test="username!=null and username!=''">
?????? and?username?like '%${username}%'
??? </if>
</sql>
?
u? 使用include引用:
?
<select id="findUserList"?parameterType="user"?resultType="user">
?????? select * from user
?????? <where>
?????? <include refid="query_user_where"/>
?????? </where>
??? </select>
?
注意:如果引用其它mapper.xml的sql片段,則在引用時需要加上namespace,如下:
<include refid="namespace.sql片段”/>
?
?
?
?
?
5?????? 關聯查詢
?
5.1???? 商品訂單數據模型
?
?
5.2???? 一對一查詢
案例:查詢所有訂單信息,關聯查詢下單用戶信息。
?
注意:因為一個訂單信息只會是一個人下的訂單,所以從查詢訂單信息出發關聯查詢用戶信息為一對一查詢。如果從用戶信息出發查詢用戶下的訂單信息則為一對多查詢,因為一個用戶可以下多個訂單。
?
5.2.1? 方法一:
使用resultType,定義訂單信息po類,此po類中包括了訂單信息和用戶信息:
?
5.2.1.1 Sql語句:
?
SELECT
? orders.*,
? user.username,
? userss.address
FROM
? orders,
? user
WHERE orders.user_id = user.id
?
?
5.2.1.2 定義po類
Po類中應該包括上邊sql查詢出來的所有字段,如下:
?
public?class?OrdersCustom?extends?Orders {
?
????private?String username;// 用戶名稱
????private?String address;// 用戶地址
get/set。。。。
?
OrdersCustom類繼承Orders類后OrdersCustom類包括了Orders類的所有字段,只需要定義用戶的信息字段即可。
?
5.2.1.3 Mapper.xml
?
<!-- 查詢所有訂單信息 -->
??? <select id="findOrdersList"?resultType="cn.itcast.mybatis.po.OrdersCustom">
??? SELECT
??? orders.*,
??? user.username,
??? user.address
??? FROM
??? orders,??? user
??? WHERE orders.user_id = user.id
??? </select>
?
5.2.1.4 Mapper接口:
public List<OrdersCustom> findOrdersList() throws Exception;
?
5.2.1.5 測試:
?
Public void?testfindOrdersList()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //查詢訂單信息
?????? List<OrdersCustom> list = userMapper.findOrdersList();
?????? System.out.println(list);
?????? //關閉session
?????? session.close();
??? }
?
5.2.1.6 總結:
???????? 定義專門的po類作為輸出類型,其中定義了sql查詢結果集所有的字段。此方法較為簡單,企業中使用普遍。
5.2.2? 方法二:
使用resultMap,定義專門的resultMap用于映射一對一查詢結果。
?
5.2.2.1 Sql語句:
?
SELECT
? orders.*,
? user.username,
? user.address
FROM
? orders,
? user
WHERE orders.user_id = user.id
5.2.2.2 定義po類
???????? 在Orders類中加入User屬性,user屬性中用于存儲關聯查詢的用戶信息,因為訂單關聯查詢用戶是一對一關系,所以這里使用單個User對象存儲關聯查詢的用戶信息。
?
5.2.2.3 Mapper.xml
<select id="findOrdersListResultMap"?resultMap="userordermap">
??? SELECT
??? orders.*,
??? user.username,
??? user.address
??? FROM
??? orders,??? user
??? WHERE orders.user_id = user.id
??? </select>
?
這里resultMap指定userordermap。
?
5.2.2.4 定義resultMap
需要關聯查詢映射的是用戶信息,使用association將用戶信息映射到訂單對象的用戶屬性中。
<!-- 訂單信息resultmap?-->
<resultMap type="cn.itcast.mybatis.po.Orders"?id="userordermap">
<!-- 這里的id,是mybatis在進行一對一查詢時將user字段映射為user對象時要使用,必須寫 -->
<id property="id"?column="id"/>
<result property="user_id"?column="user_id"/>
<result property="number"?column="number"/>
<association property="user"?javaType="cn.itcast.mybatis.po.User">
<!-- 這里的id為user的id,如果寫上表示給user的id屬性賦值 -->
<id property="id"?column="user_id"/>
<result property="username"?column="username"/>
<result property="address"?column="address"/>
</association>
</resultMap>
association:表示進行關聯查詢單條記錄
property:表示關聯查詢的結果存儲在cn.itcast.mybatis.po.Orders的user屬性中
javaType:表示關聯查詢的結果類型
<id property="id"?column="user_id"/>:查詢結果的user_id列對應關聯對象的id屬性,這里是<id />表示user_id是關聯查詢對象的唯一標識。
<result property="username"?column="username"/>:查詢結果的username列對應關聯對象的username屬性。
?
5.2.2.5 Mapper接口:
public List<Orders> findOrdersListResultMap() throws Exception;
?
5.2.2.6 測試:
?
Public void?testfindOrdersListResultMap()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //查詢訂單信息
?????? List<Orders> list = userMapper.findOrdersList2();
?????? System.out.println(list);
?????? //關閉session
?????? session.close();
??? }
?
5.2.2.7 小結:
使用association完成關聯查詢,將關聯查詢信息映射到pojo對象中。
?
5.3???? 一對多查詢
?
案例:查詢所有訂單信息及訂單下的訂單明細信息。
?
訂單信息與訂單明細為一對多關系。
?
使用resultMap實現如下:
?
5.3.1? Sql語句:
?
SELECT
? orders.*,
? user.username,
? user.address,
? orderdetail.id orderdetail_id,
? orderdetail.items_id,
? orderdetail.items_num
FROM
? orders,user,orderdetail
?
WHERE orders.user_id = user.id
AND orders.id = orderdetail.orders_id
?
5.3.2? 定義po類
在Orders類中加入User屬性。
在Orders類中加入List<Orderdetail> orderdetails屬性
?
?
5.3.3? Mapper.xml
?
<select id="findOrdersDetailList"?resultMap="userorderdetailmap">
??? SELECT
??? orders.*,
??? user.username,
??? user.address,
??? orderdetail.id orderdetail_id,
??? orderdetail.items_id,
??? orderdetail.items_num
??? FROM orders,user,orderdetail
??? WHERE orders.user_id = user.id
??? AND orders.id = orderdetail.orders_id
??? </select>
?
5.3.4? 定義resultMap
<!-- 訂單信息resultmap?-->
<resultMap type="cn.itcast.mybatis.po.Orders"?id="userorderdetailmap">
<id property="id"column="id"/>
<result property="user_id"?column="user_id"/>
<result property="number"?column="number"/>
<association property="user"?javaType="cn.itcast.mybatis.po.User">
<id property="id"?column="user_id"/>
<result property="username"?column="username"/>
<result property="address"?column="address"/>
</association>
<collection property="orderdetails"?ofType="cn.itcast.mybatis.po.Orderdetail">
??? <id property="id"?column="orderdetail_id"/>
??? <result property="items_id"?column="items_id"/>
??? <result property="items_num"?column="items_num"/>
</collection>
</resultMap>
?
黃色部分和上邊一對一查詢訂單及用戶信息定義的resultMap相同,
collection部分定義了查詢訂單明細信息。
collection:表示關聯查詢結果集
property="orderdetails":關聯查詢的結果集存儲在cn.itcast.mybatis.po.Orders上哪個屬性。
ofType="cn.itcast.mybatis.po.Orderdetail":指定關聯查詢的結果集中的對象類型即List中的對象類型。
<id />及<result/>的意義同一對一查詢。
?
5.3.4.1 resultMap使用繼承
上邊定義的resultMap中黃色部分和一對一查詢訂單信息的resultMap相同,這里使用繼承可以不再填寫重復的內容,如下:
?
<resultMap type="cn.itcast.mybatis.po.Orders"?id="userorderdetailmap"?extends="userordermap">
<collection property="orderdetails"?ofType="cn.itcast.mybatis.po.Orderdetail">
?? <id property="id"?column="orderdetail_id"/>
?? <result property="items_id"?column="items_id"/>
?? <result property="items_num"?column="items_num"/>
</collection>
</resultMap>
?
使用extends繼承訂單信息userordermap。
?
?
5.3.5? Mapper接口:
public List<Orders>findOrdersDetailList () throws Exception;
?
?
?
5.3.6? 測試:
?
Public void?testfindOrdersDetailList()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //查詢訂單信息
?????? List<Orders> list = userMapper.findOrdersDetailList();
?????? System.out.println(list);
?????? //關閉session
?????? session.close();
??? }
?
5.3.7? 小結
使用collection完成關聯查詢,將關聯查詢信息映射到集合對象。
?
?
5.4???? 多對多查詢
5.4.1? 查詢用戶購買的商品信息
5.4.1.1 需求
查詢用戶購買的商品信息。
5.4.1.2 sql
需要查詢所有用戶信息,關聯查詢訂單及訂單明細信息,訂單明細信息中關聯查詢商品信息
?
SELECT
???????? orders.*,
???????? USER .username,
???????? USER .address,
???????? orderdetail.id orderdetail_id,
???????? orderdetail.items_id,
???????? orderdetail.items_num,
???????? items.name items_name,
???????? items.detail items_detail
FROM
???????? orders,
???????? USER,
???????? orderdetail,
???????? items
WHERE
???????? orders.user_id = USER .id
AND orders.id = orderdetail.orders_id
AND orderdetail.items_id = items.id
?
5.4.1.3 po定義
???????? 在User中添加List<Orders> orders 屬性,在Orders類中加入List<Orderdetail> orderdetails屬性
?
5.4.1.4 resultMap
?
需要關聯查詢映射的信息是:訂單、訂單明細、商品信息
訂單:一個用戶對應多個訂單,使用collection映射到用戶對象的訂單列表屬性中
訂單明細:一個訂單對應多個明細,使用collection映射到訂單對象中的明細屬性中
商品信息:一個訂單明細對應一個商品,使用association映射到訂單明細對象的商品屬性中。
<!-- 一對多查詢
??? 查詢用戶信息、關聯查詢訂單、訂單明細信息、商品信息
??? ?-->
??? ?<resultMap type="cn.itcast.mybatis.po.User"?id="userOrderListResultMap">
??? ??? <id column="user_id"?property="id"/>
?????? <result column="username"?property="username"/>
??? ??? <collection property="orders"?ofType="cn.itcast.mybatis.po.Orders">
??? ????? <id? column="id"?property="id"/>
??? ????? <result property="number"?column="number"/>
??? ???? ?? <collection property="orderdetails"?ofType="cn.itcast.mybatis.po.Orderdetail">
??? ???? ?? ??? <id? column="orderdetail_id"?property="id"/>
??? ????? ???? <result property="ordersId"?column="id"/>
??? ???? ????? <result property="itemsId"?column="items_id"/>
??? ????? ???? <result property="itemsNum"?column="items_num"/>
??? ????? ???? <association property="items"?javaType="cn.itcast.mybatis.po.Items">
?????? ? ???????? ?<id column="items_id"?property="id"/>
?????? ?? ??????? ?<result column="items_name"?property="name"/>
?????? ?? ??????? ?<result column="items_detail"?property="detail"/>
????????????? </association>
??? ?? ??? </collection>
??? ?? </collection>
??? ?</resultMap>
?
5.4.2? 小結
一對多是多對多的特例,如下需求:
查詢用戶購買的商品信息,用戶和商品的關系是多對多關系。
需求1:
查詢字段:用戶賬號、用戶名稱、用戶性別、商品名稱、商品價格(最常見)
企業開發中常見明細列表,用戶購買商品明細列表,
使用resultType將上邊查詢列映射到pojo輸出。
?
需求2:
查詢字段:用戶賬號、用戶名稱、購買商品數量、商品明細(鼠標移上顯示明細)
使用resultMap將用戶購買的商品明細列表映射到user對象中。
?
5.5???? resultMap小結
resultType:
作用:
???????? 將查詢結果按照sql列名pojo屬性名一致性映射到pojo中。
場合:
???????? 常見一些明細記錄的展示,比如用戶購買商品明細,將關聯查詢信息全部展示在頁面時,此時可直接使用resultType將每一條記錄映射到pojo中,在前端頁面遍歷list(list中是pojo)即可。
?
resultMap:
???????? 使用association和collection完成一對一和一對多高級映射(對結果有特殊的映射要求)。
?
association:
作用:
???????? 將關聯查詢信息映射到一個pojo對象中。
場合:
???????? 為了方便查詢關聯信息可以使用association將關聯訂單信息映射為用戶對象的pojo屬性中,比如:查詢訂單及關聯用戶信息。
???????? 使用resultType無法將查詢結果映射到pojo對象的pojo屬性中,根據對結果集查詢遍歷的需要選擇使用resultType還是resultMap。
????????
collection:
作用:
???????? 將關聯查詢信息映射到一個list集合中。
場合:
???????? 為了方便查詢遍歷關聯信息可以使用collection將關聯信息映射到list集合中,比如:查詢用戶權限范圍模塊及模塊下的菜單,可使用collection將模塊映射到模塊list中,將菜單列表映射到模塊對象的菜單list屬性中,這樣的作的目的也是方便對查詢結果集進行遍歷查詢。
???????? 如果使用resultType無法將查詢結果映射到list集合中。
????????
?
?
5.6???? 延遲加載
???????? 需要查詢關聯信息時,使用mybatis延遲加載特性可有效的減少數據庫壓力,首次查詢只查詢主要信息,關聯信息等用戶獲取時再加載。
?
5.6.1? 打開延遲加載開關
在mybatis核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading
| 設置項 | 描述 | 允許值 | 默認值 |
| lazyLoadingEnabled | 全局性設置懶加載。如果設為‘false’,則所有相關聯的都會被初始化加載。 | true | false | false |
| aggressiveLazyLoading | 當設置為‘true’的時候,懶加載的對象可能被任何懶屬性全部加載。否則,每個屬性都按需加載。 | true | false | true |
?
?
<settings>
?????? <setting name="lazyLoadingEnabled"?value="true"/>
?????? <setting name="aggressiveLazyLoading"?value="false"/>
</settings>
?
?
5.6.2? 一對一查詢延遲加載
5.6.2.1 需求
查詢訂單信息,關聯查詢用戶信息。
默認只查詢訂單信息,當需要查詢用戶信息時再去查詢用戶信息。
?
5.6.2.2 Sql語句:
?
SELECT
? orders.*
FROM
? orders
?
5.6.2.3 定義po類
在Orders類中加入User屬性。
?
?
5.6.2.4 Mapper.xml
<select id="findOrdersList3"?resultMap="userordermap2">
??? SELECT
??? orders.*
??? FROM
??? orders
</select>
?
5.6.2.5 定義resultMap
<!-- 訂單信息resultmap?-->
<resultMap type="cn.itcast.mybatis.po.Orders"?id="userordermap2">
<id property="id"?column="id"/>
<result property="user_id"?column="user_id"/>
<result property="number"?column="number"/>
<association property="user"?javaType="cn.itcast.mybatis.po.User"?select="findUserById"?column="user_id"/>
</resultMap>
?
association:
select="findUserById":指定關聯查詢sql為findUserById
column="user_id":關聯查詢時將users_id列的值傳入findUserById
最后將關聯查詢結果映射至cn.itcast.mybatis.po.User。
?
?
5.6.2.6 Mapper接口:
public List<Orders> findOrdersList3() throws Exception;
?
5.6.2.7 測試:
?
Public void?testfindOrdersList3()throws?Exception{
?????? //獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //查詢訂單信息
?????? List<Orders> list = userMapper.findOrdersList3();
?????? System.out.println(list);
?????? //開始加載,通過orders.getUser方法進行加載
for(Orders orders:list){
?????????? System.out.println(orders.getUser());
?????? }
?????? //關閉session
?????? session.close();
??? }
?
5.6.2.8 延遲加載的思考
不使用mybatis提供的延遲加載功能是否可以實現延遲加載?
?
實現方法:
針對訂單和用戶兩個表定義兩個mapper方法。
1、訂單查詢mapper方法
2、根據用戶id查詢用戶信息mapper方法
默認使用訂單查詢mapper方法只查詢訂單信息。
當需要關聯查詢用戶信息時再調用根據用戶id查詢用戶信息mapper方法查詢用戶信息。
?
5.6.3? 一對多延遲加載
一對多延遲加載的方法同一對一延遲加載,在collection標簽中配置select內容。
本部分內容自學。
?
5.6.4? 延遲加載小結
作用:
???????? 當需要查詢關聯信息時再去數據庫查詢,默認不去關聯查詢,提高數據庫性能。
???????? 只有使用resultMap支持延遲加載設置。
場合:
???????? 當只有部分記錄需要關聯查詢其它信息時,此時可按需延遲加載,需要關聯查詢時再向數據庫發出sql,以提高數據庫性能。
???????? 當全部需要關聯查詢信息時,此時不用延遲加載,直接將關聯查詢信息全部返回即可,可使用resultType或resultMap完成映射。
?
????????
?
6?????? 查詢緩存
6.1???? mybatis緩存介紹
如下圖,是mybatis一級緩存和二級緩存的區別圖解:
?
?
Mybatis一級緩存的作用域是同一個SqlSession,在同一個sqlSession中兩次執行相同的sql語句,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次會從緩存中獲取數據將不再從數據庫查詢,從而提高查詢效率。當一個sqlSession結束后該sqlSession中的一級緩存也就不存在了。Mybatis默認開啟一級緩存。
?
Mybatis二級緩存是多個SqlSession共享的,其作用域是mapper的同一個namespace,不同的sqlSession兩次執行相同namespace下的sql語句且向sql中傳遞參數也相同即最終執行相同的sql語句,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次會從緩存中獲取數據將不再從數據庫查詢,從而提高查詢效率。Mybatis默認沒有開啟二級緩存需要在setting全局參數中配置開啟二級緩存。
?
?
6.2???? 一級緩存
6.2.1? 原理
下圖是根據id查詢用戶的一級緩存圖解:
?
?
一級緩存區域是根據SqlSession為單位劃分的。
每次查詢會先從緩存區域找,如果找不到從數據庫查詢,查詢到數據將數據寫入緩存。
Mybatis內部存儲緩存使用一個HashMap,key為hashCode+sqlId+Sql語句。value為從查詢出來映射生成的java對象
sqlSession執行insert、update、delete等操作commit提交后會清空緩存區域。
?
6.2.2? 測試1
//獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //第一次查詢
?????? User user1 = userMapper.findUserById(1);
?????? System.out.println(user1);
//第二次查詢,由于是同一個session則不再向數據發出語句直接從緩存取出
?????? User user2 = userMapper.findUserById(1);
?????? System.out.println(user2);
//關閉session
?????? session.close();
?
?
?
6.2.3? 測試2
?
//獲取session
?????? SqlSession session = sqlSessionFactory.openSession();
?????? //獲限mapper接口實例
?????? UserMapper userMapper = session.getMapper(UserMapper.class);
?????? //第一次查詢
?????? User user1 = userMapper.findUserById(1);
?????? System.out.println(user1);
?????? //在同一個session執行更新
?????? User user_update =?new User();
?????? user_update.setId(1);
?????? user_update.setUsername("李奎");
?????? userMapper.updateUser(user_update);
?????? session.commit();
?????? //第二次查詢,雖然是同一個session但是由于執行了更新操作session的緩存被清空,這里重新發出sql操作
?????? User user2 = userMapper.findUserById(1);
?????? System.out.println(user2);
?
6.3???? 二級緩存
6.3.1? 原理
下圖是多個sqlSession請求UserMapper的二級緩存圖解。
?
二級緩存區域是根據mapper的namespace劃分的,相同namespace的mapper查詢數據放在同一個區域,如果使用mapper代理方法每個mapper的namespace都不同,此時可以理解為二級緩存區域是根據mapper劃分。
每次查詢會先從緩存區域找,如果找不到從數據庫查詢,查詢到數據將數據寫入緩存。
Mybatis內部存儲緩存使用一個HashMap,key為hashCode+sqlId+Sql語句。value為從查詢出來映射生成的java對象
sqlSession執行insert、update、delete等操作commit提交后會清空緩存區域。
?
?
6.3.2? 開啟二級緩存:
在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled"?value="true"/>
?
| ? | 描述 | 允許值 | 默認值 |
| cacheEnabled | 對在此配置文件下的所有cache 進行全局性開/關設置。 | true false | true |
?
?
要在你的Mapper映射文件中添加一行:??<cache?/>?,表示此mapper開啟二級緩存。
?
6.3.3? 實現序列化
???????? 二級緩存需要查詢結果映射的pojo對象實現java.io.Serializable接口實現序列化和反序列化操作,注意如果存在父類、成員pojo都需要實現序列化接口。
???????? public class Orders implements Serializable
???????? public class User implements Serializable
???????? ....
?
6.3.4? 測試
?
//獲取session1
?????? SqlSession session1 = sqlSessionFactory.openSession();
?????? UserMapper userMapper = session1.getMapper(UserMapper.class);
?????? //使用session1執行第一次查詢
?????? User user1 = userMapper.findUserById(1);
?????? System.out.println(user1);
?????? //關閉session1
?????? session1.close();
?????? //獲取session2
?????? SqlSession session2 = sqlSessionFactory.openSession();
?????? UserMapper userMapper2 = session2.getMapper(UserMapper.class);
?????? //使用session2執行第二次查詢,由于開啟了二級緩存這里從緩存中獲取數據不再向數據庫發出sql
?????? User user2 = userMapper2.findUserById(1);
?????? System.out.println(user2);
?????? //關閉session2
?????? session2.close();
?
6.3.5? 禁用二級緩存
在statement中設置useCache=false可以禁用當前select語句的二級緩存,即每次查詢都會發出sql去查詢,默認情況是true,即該sql使用二級緩存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
?
6.3.6? 刷新緩存
在mapper的同一個namespace中,如果有其它insert、update、delete操作數據后需要刷新緩存,如果不執行刷新緩存會出現臟讀。
?
?設置statement配置中的flushCache="true"?屬性,默認情況下為true即刷新緩存,如果改成false則不會刷新。使用緩存時如果手動修改數據庫表中的查詢數據會出現臟讀。
如下:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
?
6.3.7? Mybatis Cache參數
flushInterval(刷新間隔)可以被設置為任意的正整數,而且它們代表一個合理的毫秒形式的時間段。默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。
size(引用數目)可以被設置為任意正整數,要記住你緩存的對象數目和你運行環境的可用內存資源數目。默認值是1024。
readOnly(只讀)屬性可以被設置為true或false。只讀的緩存會給所有調用者返回緩存對象的相同實例。因此這些對象不能被修改。這提供了很重要的性能優勢。可讀寫的緩存會返回緩存對象的拷貝(通過序列化)。這會慢一些,但是安全,因此默認是false。
?
如下例子:
<cache ?eviction="FIFO"? flushInterval="60000"? size="512"? readOnly="true"/>
這個更高級的配置創建了一個?FIFO?緩存,并每隔?60?秒刷新,存數結果對象或列表的?512?個引用,而且返回的對象被認為是只讀的,因此在不同線程中的調用者之間修改它們會導致沖突。可用的收回策略有,?默認的是?LRU:
?
?
6.3.8? mybatis整合ehcache
???????? EhCache 是一個純Java的進程內緩存框架,是一種廣泛使用的開源Java分布式緩存,具有快速、精干等特點,是Hibernate中默認的CacheProvider。
?
6.3.8.1 mybatis整合ehcache原理
?
mybatis提供二級緩存Cache接口,如下:
?
?
它的默認實現類:
?
?
通過實現Cache接口可以實現mybatis緩存數據通過其它緩存數據庫整合,mybatis的特長是sql操作,緩存數據的管理不是mybatis的特長,為了提高緩存的性能將mybatis和第三方的緩存數據庫整合,比如ehcache、memcache、redis等。
????????
6.3.8.2 第一步:引入緩存的依賴包
?
?
maven坐標:
<dependency>
?????????? <groupId>org.mybatis.caches</groupId>
?????????? <artifactId>mybatis-ehcache</artifactId>
?????????? <version>1.0.2</version>
?????? </dependency>
?
6.3.8.3 第二步:引入緩存配置文件
classpath下添加:ehcache.xml
內容如下:
?
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
??? xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
??? <diskStore path="F:\develop\ehcache"?/>
??? <defaultCache
?????? maxElementsInMemory="1000"
?????? maxElementsOnDisk="10000000"
?????? eternal="false"
?????? overflowToDisk="false"
?????? timeToIdleSeconds="120"
?????? timeToLiveSeconds="120"
?????? diskExpiryThreadIntervalSeconds="120"
?????? memoryStoreEvictionPolicy="LRU">
??? </defaultCache>
</ehcache>
?
屬性說明:
? diskStore:指定數據在磁盤中的存儲位置。
? defaultCache:當借助CacheManager.add("demoCache")創建Cache時,EhCache便會采用<defalutCache/>指定的的管理策略
以下屬性是必須的:
? maxElementsInMemory - 在內存中緩存的element的最大數目
? maxElementsOnDisk - 在磁盤上緩存的element的最大數目,若是0表示無窮大
? eternal - 設定緩存的elements是否永遠不過期。如果為true,則緩存的數據始終有效,如果為false那么還要根據timeToIdleSeconds,timeToLiveSeconds判斷
? overflowToDisk - 設定當內存緩存溢出的時候是否將過期的element緩存到磁盤上
以下屬性是可選的:
? timeToIdleSeconds - 當緩存在EhCache中的數據前后兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數據便會刪除,默認值是0,也就是可閑置時間無窮大
? timeToLiveSeconds - 緩存element的有效生命期,默認是0.,也就是element存活時間無窮大
?????? diskSpoolBufferSizeMB 這個參數設置DiskStore(磁盤緩存)的緩存區大小.默認是30MB.每個Cache都應該有自己的一個緩沖區.
? diskPersistent - 在VM重啟的時候是否啟用磁盤保存EhCache中的數據,默認是false。
? diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運行間隔,默認是120秒。每個120s,相應的線程會進行一次EhCache中數據的清理工作
? memoryStoreEvictionPolicy - 當內存緩存達到最大,有新的element加入的時候, 移除緩存中element的策略。默認是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)
?
6.3.8.4 第三步:開啟ehcache緩存
EhcacheCache是ehcache對Cache接口的實現:
?
?
修改mapper.xml文件,在cache中指定EhcacheCache。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
?
根據需求調整緩存參數:
<cache type="org.mybatis.caches.ehcache.EhcacheCache"?>
??????? <property name="timeToIdleSeconds"?value="3600"/>
??????? <property name="timeToLiveSeconds"?value="3600"/>
??????? <!-- 同ehcache參數maxElementsInMemory -->
?????? <property name="maxEntriesLocalHeap"?value="1000"/>
?????? <!-- 同ehcache參數maxElementsOnDisk -->
??????? <property name="maxEntriesLocalDisk"?value="10000000"/>
??????? <property name="memoryStoreEvictionPolicy"?value="LRU"/>
??? </cache>
?
?
6.3.9? 應用場景
???????? 對于訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可采用mybatis二級緩存技術降低數據庫訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。
???????? 實現方法如下:通過設置刷新間隔時間,由mybatis每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔flushInterval,比如設置為30分鐘、60分鐘、24小時等,根據需求而定。
?
?
6.3.10????????????? 局限性
???????? mybatis二級緩存對細粒度的數據級別的緩存實現不好,比如如下需求:對商品信息進行緩存,由于商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用mybatis的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因為mybaits的二級緩存區域以mapper為單位劃分,當一個商品信息變化會將所有商品信息的緩存數據全部清空。解決此類問題需要在業務層根據需求對數據有針對性緩存。
?
?
?
?
7?????? 與spring整合
實現mybatis與spring進行整合,通過spring管理SqlSessionFactory、mapper接口。
?
7.1???? mybatis與spring整合jar
?
mybatis官方提供與mybatis與spring整合jar包:
?
?
還包括其它jar:
spring3.2.0
mybatis3.2.7
dbcp連接池
數據庫驅動
?
參考:
?
?
7.2???? Mybatis配置文件
在classpath下創建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>
?
<!—使用自動掃描器時,mapper.xml文件如果和mapper.java接口在一個目錄則此處不用定義mappers -->
<mappers>
<package name="cn.itcast.mybatis.mapper" />
</mappers>
</configuration>
?
?
7.3???? Spring配置文件:
???????? 在classpath下創建applicationContext.xml,定義數據庫鏈接池、SqlSessionFactory。
?
<beans xmlns="http://www.springframework.org/schema/beans"
?? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"?xmlns:mvc="http://www.springframework.org/schema/mvc"
?? xmlns:context="http://www.springframework.org/schema/context"
?? xmlns:aop="http://www.springframework.org/schema/aop"?xmlns:tx="http://www.springframework.org/schema/tx"
?? xsi:schemaLocation="http://www.springframework.org/schema/beans
????? http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
????? http://www.springframework.org/schema/mvc
????? http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
????? http://www.springframework.org/schema/context
????? http://www.springframework.org/schema/context/spring-context-3.2.xsd
????? http://www.springframework.org/schema/aop
????? http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
????? http://www.springframework.org/schema/tx
????? http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 加載配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 數據庫連接池 -->
<bean id="dataSource"?class="org.apache.commons.dbcp.BasicDataSource"?destroy-method="close">
?????? <property name="driverClassName"?value="${jdbc.driver}"/>
????? <property name="url"?value="${jdbc.url}"/>
????? <property name="username"?value="${jdbc.username}"/>
????? <property name="password"?value="${jdbc.password}"/>
????? <property name="maxActive"?value="10"/>
????? <property name="maxIdle"?value="5"/>
</bean>??
<!--?mapper配置 -->
?? <!-- 讓spring管理sqlsessionfactory?使用mybatis和spring整合包中的 -->
?? <bean id="sqlSessionFactory"?class="org.mybatis.spring.SqlSessionFactoryBean">
????? <!-- 數據庫連接池 -->
????? <property name="dataSource"?ref="dataSource"?/>
????? <!-- 加載mybatis的全局配置文件 -->
????? <property name="configLocation"?value="classpath:mybatis/SqlMapConfig.xml"?/>
?? </bean>
?
</beans>
?
?
注意:在定義sqlSessionFactory時指定數據源dataSource和mybatis的配置文件。
?
7.4???? Mapper編寫的三種方法
7.4.1? Dao接口實現類繼承SqlSessionDaoSupport
使用此種方法即原始dao開發方法,需要編寫dao接口,dao接口實現類、映射文件。
?
1、? 在sqlMapConfig.xml中配置映射文件的位置
<mappers>
? <mapper resource="mapper.xml文件的地址"?/>
<mapper resource="mapper.xml文件的地址"?/>
</mappers>
?
2、? 定義dao接口
?
3、? dao接口實現類集成SqlSessionDaoSupport
?
dao接口實現類方法中可以this.getSqlSession()進行數據增刪改查。
?
4、? spring 配置
?
<bean id=" "class="mapper接口的實現">
??? <property name="sqlSessionFactory"?ref="sqlSessionFactory"></property>
??? </bean>
?
?
7.4.2? 使用org.mybatis.spring.mapper.MapperFactoryBean
???????? 此方法即mapper接口開發方法,只需定義mapper接口,不用編寫mapper接口實現類。每個mapper接口都需要在spring配置文件中定義。
?
1、? 在sqlMapConfig.xml中配置mapper.xml的位置
如果mapper.xml和mappre接口的名稱相同且在同一個目錄,這里可以不用配置
<mappers>
? <mapper resource="mapper.xml文件的地址"?/>
<mapper resource="mapper.xml文件的地址"?/>
</mappers>
?
2、? 定義mapper接口
?
?
3、? Spring中定義
<bean id=""?class="org.mybatis.spring.mapper.MapperFactoryBean">
??? <property name="mapperInterface"?value="mapper接口地址"/>
<property name="sqlSessionFactory"?ref="sqlSessionFactory"/>
??? </bean>
?
7.4.3? 使用mapper掃描器
???????? 此方法即mapper接口開發方法,只需定義mapper接口,不用編寫mapper接口實現類。只需要在spring配置文件中定義一個mapper掃描器,自動掃描包中的mapper接口生成代代理對象。
?
1、? mapper.xml文件編寫,
?
2、? 定義mapper接口
?
注意mapper.xml的文件名和mapper的接口名稱保持一致,且放在同一個目錄
?
3、? 配置mapper掃描器
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
?<property name="basePackage"?value="mapper接口包地址"></property>
<property name="sqlSessionFactoryBeanName"?value="sqlSessionFactory"/>
??? </bean>
basePackage:掃描包路徑,中間可以用逗號或分號分隔定義多個包
4、? 使用掃描器后從spring容器中獲取mapper的實現對象
?
?????????如果將mapper.xml和mapper接口的名稱保持一致且放在一個目錄?則不用在sqlMapConfig.xml中進行配置
?
?
8?????? Mybatis逆向工程
使用官方網站的mapper自動生成工具mybatis-generator-core-1.3.2來生成po類和mapper映射文件。
?
8.1???? 第一步:mapper生成配置文件:
在generatorConfig.xml中配置mapper生成的詳細信息,注意改下幾點:
?
1、? 添加要生成的數據庫表
2、? po文件所在包路徑
3、? mapper文件所在包路徑
?
配置文件如下:
詳見generatorSqlmapCustom工程
?
8.2???? 第二步:使用java類生成mapper文件:
?
Public void?generator()?throws?Exception{
????? List<String> warnings =?new?ArrayList<String>();
??????boolean?overwrite =?true;
????? File configFile =?new?File("generatorConfig.xml");
????? ConfigurationParser cp =?new?ConfigurationParser(warnings);
????? Configuration config = cp.parseConfiguration(configFile);
????? DefaultShellCallback callback = new DefaultShellCallback(overwrite);
????? MyBatisGenerator myBatisGenerator =?new?MyBatisGenerator(config,
??????????? callback, warnings);
????? myBatisGenerator.generate(null);
?? }
???Public static void?main(String[] args)?throws?Exception {
??????try?{
???????? GeneratorSqlmap generatorSqlmap =?new?GeneratorSqlmap();
???????? generatorSqlmap.generator();
????? }?catch?(Exception e) {
???????? e.printStackTrace();
????? }
}
?
?
?
8.3???? 第三步:拷貝生成的mapper文件到工程中指定的目錄中
8.3.1? Mapper.xml
Mapper.xml的文件拷貝至mapper目錄內
8.3.2? Mapper.java
Mapper.java的文件拷貝至mapper 目錄內
?
?
注意:mapper xml文件和mapper.java文件在一個目錄內且文件名相同。
?
?
8.3.3? 第四步Mapper接口測試
?
學會使用mapper自動生成的增、刪、改、查方法。
?
//刪除符合條件的記錄
int?deleteByExample(UserExample example);
//根據主鍵刪除
int?deleteByPrimaryKey(String id);
//插入對象所有字段
int?insert(User record);
//插入對象不為空的字段
int?insertSelective(User record);
//自定義查詢條件查詢結果集
List<User> selectByExample(UserExample example);
//根據主鍵查詢
UserselectByPrimaryKey(String id);
//根據主鍵將對象中不為空的值更新至數據庫
int?updateByPrimaryKeySelective(User record);
//根據主鍵將對象中所有字段的值更新至數據庫
int?updateByPrimaryKey(User record);
?
?
8.4???? 逆向工程注意事項
8.4.1? Mapper文件內容不覆蓋而是追加
XXXMapper.xml文件已經存在時,如果進行重新生成則mapper.xml文件內容不被覆蓋而是進行內容追加,結果導致mybatis解析失敗。
解決方法:刪除原來已經生成的mapper xml文件再進行生成。
Mybatis自動生成的po及mapper.java文件不是內容而是直接覆蓋沒有此問題。
?
8.4.2? Table schema問題
下邊是關于針對oracle數據庫表生成代碼的schema問題:
?
Schma即數據庫模式,oracle中一個用戶對應一個schema,可以理解為用戶就是schema。
當Oralce數據庫存在多個schema可以訪問相同的表名時,使用mybatis生成該表的mapper.xml將會出現mapper.xml內容重復的問題,結果導致mybatis解析錯誤。
解決方法:在table中填寫schema,如下:
<table schema="XXXX" tableName=" " >
XXXX即為一個schema的名稱,生成后將mapper.xml的schema前綴批量去掉,如果不去掉當oracle用戶變更了sql語句將查詢失敗。
快捷操作方式:mapper.xml文件中批量替換:“from XXXX.”為空
?
Oracle查詢對象的schema可從dba_objects中查詢,如下:
select * from dba_objects
總結
以上是生活随笔為你收集整理的MyBatis学习随记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hdu-1422(简单dp)
- 下一篇: 抽屉原理~