文章目錄
- Mybatis第二階端
- 第一章 Mabatis第二階端知識點
- 第二章 SqlMapConfig.xml配置檔案
-
- 2.1 properties(屬性)
- 2.2 typeAliases(類型别名)
- 2.3 mappers(映射器)
- 第三章 Mybatis 連接配接池與事務深入(了解)
-
- 3.1 Mybatis的連接配接池技術
-
- 3.1.1 Mybatis連接配接池的分類
- 3.1.2 Mybatis中資料源的配置
- 3.1.3 Mybatis的事務控制
-
- 3.1.3.1 JDBC中事務的回顧
- 3.1.3.2 Mybatis中的事務送出方式
- 第四章 Mybatis映射檔案的SQL深入
-
- 4.1 動态SQL之 if 标簽
- 4.2 動态SQL之 where 标簽
- 4.3 動态SQL标簽之 foreach 标簽
- 4.4 Mybatis中簡化編寫的SQL片段
- 第5章 Mybatis 的多表關聯查詢
-
- 5.1 回顧
- 5.2 使用者和賬号
- 5.3 Mybatis維護一對多關系
-
- 5.3.1 查詢所有賬戶
Mybatis第二階端
第一章 Mabatis第二階端知識點
1、mybatis中的連接配接池以及事務控制
mybatis中連接配接池使用及分析
mybatis事務控制的分析
2、mybatis基于XML配置的動态SQL語句使用
mappers配置檔案中的幾個标簽:
<if>
<where>
<foreach>
<sql>
3、mybatis中的多表操作 掌握應用(聯合查詢)
一對多,多對一
多對多
今日學習目标:
1:掌握SqlMapConfig.xml配置檔案(第2章)
2:了解Mybatis連接配接池與事務操作(第3章)
3:掌握Mybatis動态SQL(第4章)
4:掌握Mybatis多表關聯查詢(第5章5.3)
5:掌握Mybatis多對多關系(第5章5.4)
第二章 SqlMapConfig.xml配置檔案
在SqlMapConfigx.xml中可以配置很多屬性(本章一個個講解),如下:
SqlMapConfig.xml中配置的内容和順序如下:
properties(屬性)
settings(全局配置參數)
typeAliases (類型别名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環境集合屬性對象)
environment(環境子屬性對象)
transactionManager(事務管理)
dataSource(資料源)
mappers(映射器)
2.1 properties(屬性)
SqlMapConfig.xml可以引用java屬性檔案中的配置資訊如下:
在classpath下定義jdbcConfig.properties檔案, (即resources檔案夾下)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/itcastmybatis
jdbc.username=root
jdbc.password=root
在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>
<!-- 配置properties
可以在标簽内部配置連接配接資料庫的資訊。(可見我的部落格中Mybatis(一)的講解)
也可以通過屬性引用外部配置檔案資訊
-->
<!--加載外部配置檔案資訊-->
<properties resource="jdbc.properties"/>
<!--也可以這麼寫-->
<!--<properties url="file:///D:/ideaProjects/mybatis/mybatis_day02_crud/src/main/resources/jdbcConfig.properties">
</properties>-->
<!--配置mybatis環境-->
<environments default="mysql">
<environment id="mysql">
<!--配置事務管理-->
<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>
<!--配置映射檔案資訊-->
<mappers>
<mapper resource="cn/iyhome/dao/IUerDao.xml"/>
<!--如果是使用注解的話,此處應該是用class屬性指定被注解的dao全限定類名-->
<!--<mapper class="cn.iyhome.dao.IUserDao"/>-->
</mappers>
</configuration>
Properties屬性講解
标簽的屬性講解:
-
resource屬性: (常用)
用于指定配置檔案的位置,是按照類路徑的寫法來寫,并且必須存在于類路徑下。
-
url屬性:
是要求按照Url的寫法來寫位址
URL:Uniform Resource Locator 統一資源定位符。它是可以唯一定位一個資源的位置。 它的寫法: http://localhost:8080/mybatisserver/demo1Servlet 協定 主機 端口 URI URI:Uniform Resource Identifier 統一資源辨別符。它是在應用中可以唯一辨別一個資源的。
是以 xml中加載jdbc.properties也可以這麼寫
<properties url="file:///D:/ideaProjects/mybatis/mybatis_day02_crud/src/main/resources/jdbcConfig.properties">
[題外話]HTTP 協定中 URI 和 URL 有什麼差別?
ps:轉自知乎daixinye的解釋
2.2 typeAliases(類型别名)
mybatis内置别名映射
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 |
map | Map |
使用者自定義别名
需求:IUserDao.xml中方法的傳回值類型,全限定類名過于繁瑣,是否可以簡化
方案:使用别名,替換全限定類名
(1) 在SqlMapConfig.xml中配置
<!--在configuration标簽下配置-->
<!--使用typeAliases配置别名,它隻能配置domain中類的别名-->
<!--配置别名-->
<typeAliases>
<!--使用typeAlias給指定的實體類注冊别名,配置的别名是不區分大小寫的-->
<!--配置一個類-->
<!--<typeAlias type="cn.iyhome.domain.User" alias="user"/>-->
<!--但是當類比較多的時候,一個類一個類配置顯得十分麻煩-->
<!--pageage用于指定要配置别名的包,該包下的實體類都會注冊别名,并且類名就是别名,同樣不區分大小寫-->
<!--批量配置-->
<package name="cn.iyhome.domain"/>
</typeAliases>
(2)在配置了SqlMapConfig.xml後,在IUserDao.xml中就可以把權限定類名替換為别名
<!--可以看到下面3個方法的傳回值都是用了别名,且不區分大小寫-->
<!--查找所有-->
<select id="findAll" resultType="USER">
select * from user
</select>
<!--修改使用者-->
<update id="updateUser" parameterType="UsER">
update user set sex=#{sex} where id=#{id}
</update>
<!--查找使用者byQueryVo-->
<select id="findUserByQueryVO" parameterType="cn.iyhome.domain.QueryVo" resultType="uSER">
select * from user where username=#{user.username}
</select>
2.3 mappers(映射器)
問題現象:在SqlMapConfig.xml中,配置映射檔案時,如果我們使用是是xml配置的方法,使用的是resource屬性,如果是使用注解的方式的話(在第一階段,入門案例有一點涉及,在第三階段會詳細講解),使用的事是class屬性.當Dao層的類特别多的時候,使用這兩種方式配置起來,工作量特别大.
解決方案:使用package統一處理
<!--配置映射檔案資訊-->
<mappers>
<!--指定xml配置檔案映射-->
<!--<mapper resource="cn/iyhome/dao/IUserDao.xml"/>-->
<!--指定使用注解,此處應該是用class屬性指定被注解的dao全限定類名-->
<!--<mapper class="cn.iyhome.dao.IUserDao"/>-->
<!--兩種方式通用的寫法-->
<!--package标簽用于指定dao接口所在的包,當指定了之後,就不需要再寫mapper->resource或者mapper->class 了-->
<package name="cn.iyhome.dao"/>
</mappers>
注意:
使用<mapper class>和<package name>必須滿足以下條件:
- 此種方法要求mapper接口名稱和mapper映射檔案名稱相同,且放在同一個目錄中
快捷的檢查方式:
第三章 Mybatis 連接配接池與事務深入(了解)
3.1 Mybatis的連接配接池技術
我們在實際開發中都會使用連接配接池。因為它可以減少我們擷取連接配接所消耗的時間。
在Mybatis中有連接配接池技術,但是它采用的是自己的連接配接池技術。在Mybatis的SqlMapConfig.xml配置檔案中,通過<dataSource type=”pooled”>來實作Mybatis中連接配接池的配置。
什麼是連接配接池?
連接配接池的特點:
1:連接配接池就是用于存儲連接配接的一個容器
2:連接配接池其實就是一個集合對象,該集合必須是線程安全的,不能兩個線程拿到同一個連接配接。
3:連接配接池必須實作隊列的特性,先進先出
3.1.1 Mybatis連接配接池的分類
在Mybatis中我們将它的資料源dataSource分為以下幾類:
可以看出Mybatis将它自己的資料源分為三類:
- UNPOOLED 不使用連接配接池的資料源,采用傳統的擷取連接配接的方式,雖然也實作Javax.sql.DataSource接口,但是并沒有使用池的思想。
- POOLED 使用連接配接池的資料源,采用傳統的javax.sql.DataSource規範中的連接配接池,mybatis中有針對規範的實作。
-
JNDI 使用JNDI實作的資料源(不了解),采用伺服器提供的JNDI技術實作,來擷取DataSource對象,不同的伺服器所能拿到DataSource是不一樣。
注意:如果不是web或者maven的war工程,是不能使用的。
tomcat伺服器,采用連接配接池就是dbcp連接配接池。
具體結構如下:
相應地,MyBatis内部分别定義了實作了java.sql.DataSource接口的UnpooledDataSource,PooledDataSource類來表示UNPOOLED、POOLED類型的資料源。
在這三種資料源中,我們一般采用的是POOLED資料源(很多時候我們所說的資料源就是為了更好的管理資料庫連接配接,也就是我們所說的連接配接池技術)。
3.1.2 Mybatis中資料源的配置
同樣還是以上述demo為案例,執行測試類中的調用findAll()的方法.此時資料源使用的是POOLED
查詢結果:
此截圖可以看到,他是從連接配接池中擷取的連接配接,在使用完畢後歸還給連接配接池.
底層代碼跟蹤
1.Ctrl+N :查找類:PooledDataSource.java
2.定位到方法 getConnection()
tips: Alt+7 可以再左下側展示出所有方法和變量
3.跟蹤進入popConnection()方法
圖解處理流程
問:為什麼MyBatis連接配接池要設計為有一個空閑連接配接清單和一個活動連接配接清單?
1:從連接配接池的設計看,這兩個連接配接清單都是必需的,加起來等于是連接配接池能用的最大連接配接數。
2:當有新的連接配接請求時,有從空閑連接配接清單中選擇一個可用的連接配接,如果這個連接配接可以正常執行,轉移到活動連接配接清單。
3:建立連接配接比較耗時,是以一開始就建立好一堆連接配接,這些連接配接沒有被使用的時候就在空閑清單裡。
4:當要使用的時候,就從空閑清單裡拿一個,放到活動清單裡。
ps: 資料源使用的是UNPOOLED時,底層實作原理和我們之前接觸的一樣.使用反射加載驅動,然後使用DriverManager擷取連接配接,再進行一系列的操作,此處不再闡述
總結:最後我們可以發現,真正連接配接打開的時間點,隻是在我們執行SQL語句時,才會進行。其實這樣做我們也可以進一步發現,資料庫連接配接是我們最為寶貴的資源,隻有在要用到的時候,才去擷取并打開連接配接,當我們用完了就再立即将資料庫連接配接歸還到連接配接池中。 是以我們使用連接配接池的技術會節省連接配接資源。
3.1.3 Mybatis的事務控制
3.1.3.1 JDBC中事務的回顧
一:什麼是事務?
二:事務的四大特性ACID?
三:不考慮隔離性會産生的3個問題?
髒讀
(針對未送出資料)如果一個事務中對資料進行了更新,但事務還沒有送出,另一個事務可以“看到”該事務沒有送出的更新結果,這樣造成的問題就是,如果第一個事務復原,那麼,第二個事務在此之前所“看到”的資料就是一筆髒資料。
不可重複讀
(針對其他送出前後,讀取資料本身的對比)不可重複讀取是指同一個事務在整個事務過程中對同一筆資料進行讀取,每次讀取結果都不同。如果事務1在事務2的更新操作之前讀取一次資料,在事務2的更新操作之後再讀取同一筆資料一次,兩次結果是不同的
幻讀
(針對其他送出前後,讀取資料條數的對比) 幻讀是指同樣一筆查詢在整個事務過程中多次執行後,查詢所得的結果集是不一樣的。幻讀針對的是多筆記錄。如果事務1在事務2的新增操作之前讀取一次資料,在事務2的新增操作之後再讀取同一筆資料,取得的結果集是不同的,幻讀發生。
不可重複讀和幻讀比較:
兩者有些相似,但是前者針對的是update或delete,後者針對的insert。
四:解決辦法:四種隔離級别?
1、Serializable (串行化):最嚴格的級别,事務串行執行,資源消耗最大,避免了“髒讀”和“不可重複讀”,“幻讀”的情況;
2、REPEATABLE READ(重複讀):保證了一個事務不會修改已經由另一個事務讀取但未送出(復原)的資料。避免了“髒讀”和“不可重複讀”的情況,但不能避免“幻讀”,但是帶來了更多的性能損失。(mysql的預設隔離級别)
3、READ COMMITTED (讀已送出):大多數主流資料庫的預設事務等級,保證了一個事務不會讀到另一個并行事務已修改但未送出的資料,避免了“髒讀”,但不能避免“幻讀”和“不可重複讀”。該級别适用于大多數系統。(oracle的預設隔離級别)
4、Read Uncommitted(讀未送出):事務中的修改,即使沒有送出,其他事務也可以看得到,會導緻“髒讀”、“幻讀”和“不可重複讀取”。
3.1.3.2 Mybatis中的事務送出方式
Mybatis中事務的送出方式,本質上就是調用JDBC的setAutoCommit()來實作事務控制。
源碼分析
- ctrl+滑鼠點選 openSession(),跳轉到接口SqlSessionFactory
- 點選SqlSessionFactory,按ctrl+alt+b ,選擇DefaultSqlSessionFactory
- 定位到方法openSession() 再來檢視api,autoCommit:為true表示啟動自動送出事務,false表示禁用自動送出事務
問題
看完上述源碼可能有的同學不明白我們要講的點在哪.我們再來觀察我們的測試代碼
還記得我們在Mybatis第一階段單表CURD時做插入操作時,控制台沒有報錯,但是檢視資料庫表資料又沒有插入成功的案例麼.原因在于沒有送出事務.是以我們再添加一行代碼
此時我們再想上述的源碼分析中,
autoCommit:false
,你是不是就已經明白了些什麼.因為自動送出事務的開關預設是關着的.是以我們如果隻要打開這個開關後,
SqlSession.commit()
就不要再寫了.
總結
如果設定此時事務就設定為自動送出了,同樣可以實作CUD操作時記錄的儲存。雖然這也是一種方式,但就程式設計而言,設定為自動送出方式為false再根據情況決定是否進行送出,這種方式更常用。因為我們可以根據業務情況來決定業務是否進行送出。如果設定自動送出,如果目前操作有誤,事務很難設定復原了。 隻有設定為手動送出,我們才能更好的控制事務。
第四章 Mybatis映射檔案的SQL深入
Mybatis的映射檔案中,前面我們的SQL都是比較簡單的,有些時候業務邏輯複雜時,我們的SQL是動态變化的,此時在前面的學習中我們的SQL就不能滿足要求了。
4.1 動态SQL之 if 标簽
我們根據實體類的不同取值,使用不同的SQL語句來進行查詢。比如在id如果不為空時可以根據id查詢,如果username不同空時還要加入使用者名作為條件。這種情況在我們的多條件組合查詢中經常會碰到。
需求:增加一個方法,根據實體類的不同取值,使用不同的SQL語句來進行查詢
1.在IUserDao.java中添加方法
/**
* 根據條件查詢
*/
List<User> findUserByCondition(User user);
2.在IUserDao.xml中添加配置資訊
<!--根據條件查詢-->
<select id="findUserByCondition" parameterType="user" resultType="user">
select * from user where 1=1
<!--if标簽的test屬性中寫的是對象的屬性名,如果是包裝類的對象要使用OGNL表達式的寫法-->
<if test="username!=null">
and username like #{username}
</if>
<if test="sex!=null">
and sex=#{sex}
</if>
</select>
注意:<if>标簽的test屬性中寫的是對象的屬性名,如果是包裝類的對象要使用OGNL表達式的寫法。
另外要注意where 1=1 的作用,如果兩個if條件都為假,那麼sql語句隻有where關鍵字沒有條件,文法錯誤.
3.在測試類中測試方法
@Test
public void finUserByCondition() {
//使用SqlSession建構Dao的代理對象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
//
User user = new User();
user.setUsername("%王%");
user.setSex("男");
//執行Dao的findUserByCondition方法
List<User> users = userDao.findUserByCondition(user);
//周遊
for (User userInfo : users) {
System.out.println(userInfo);
}
}
結果
如果把
user.setUsername("%王%");
注釋,隻保留
user.setSex("男");
,查詢結果為:
4.2 動态SQL之 where 标簽
有了這個标簽後就可以省略
where 1=1
<!--where标簽的使用-->
<select id="findByCondition" parameterType="user" resultMap="userMap">
select * from user
<where>
<if test="userName != null">
and username = #{userName}
</if>
<if test="userSex != null">
and sex = #{Sex}
</if>
</where>
</select>
<where />可以自動處理第一個and
4.3 動态SQL标簽之 foreach 标簽
需求
傳入多個id查詢使用者資訊,用下邊兩個sql實作:
SELECT * FROM USERS WHERE username LIKE '%王%' AND (id =41 OR id =43 OR id=45)
SELECT * FROM USERS WHERE username LIKE '%王%' AND id IN (41,43,45)
這樣我們在進行範圍查詢時,就要将一個集合中的值,作為參數動态添加進來。
這樣我們将如何進行參數的傳遞?
1.為QueryVo類添加成員變量,變量類型為List,存儲Id
public class QueryVo {
private User user;
//存儲使用者id
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
2.編寫映射檔案IUseDao.xml
<!--根據id範圍查找-->
<select id="findUserByIds" parameterType="QueryVo" resultType="user">
select * from user
<where>
<if test="ids != null and ids.size()>0">
<foreach collection="ids" open="and id in (" item="id" close=")" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
sql語句:select * from user where id in(?,?,?)
參數說明:
<foreach>标簽用于周遊集合,它的屬性:
collection:代表要周遊的集合元素,注意編寫時不要寫#{}
open:代表語句的開始部分
close:代表結束部分
item:代表周遊集合的每個元素,生成的變量名
sperator:代表分隔符
3.編寫測試類
@Test
public void findUserByIds() {
//使用SqlSession建構Dao的代理對象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<Integer> list = new ArrayList<Integer>();
Collections.addAll(list, 41,43,45,100);
QueryVo qv = new QueryVo();
qv.setIds(list);
List<User> users = userDao.findUserByIds(qv);
for (User user : users) {
System.out.println(user);
}
}
檢視結果:
4.4 Mybatis中簡化編寫的SQL片段
Sql中可将重複的sql提取出來,使用時用include引用即可,最終達到sql重用的目的。
我們先到UserDao.xml檔案中使用
<sql>
标簽,定義出公共部分,如下:
<sql id="defaultUser">
select * from user
</sql>
使用
<include refid="defaultUser"></include>
<!--foreach标簽的使用-->
<select id="findInIds" parameterType="queryVo" resultMap="userMap">
<!--select * from user-->
<!--使用include引入公共代碼塊-->
<include refid="defaultUser"></include>
<where>
<if test="ids != null and ids.size()>0">
<foreach collection="ids" open=" and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
第5章 Mybatis 的多表關聯查詢
5.1 回顧
表之間的關系有幾種:
一對一,一對多,多對一,多對多
在多對一中:特例:
如果拿出每一個訂單,他都隻能屬于一個使用者。
是以Mybatis就把多對一看成了一對一。
5.2 使用者和賬号
本次案例主要以最為簡單的使用者和賬戶的模型來分析Mybatis 多表關系。使用者為User 表,賬戶為Account 表。一個使用者(User)可以有多個賬戶(Account)。具體關系如下:
維護資料庫示例資料
-- user表在之前的案例中已經建立過,不再建立
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`ID` INT(11) NOT NULL COMMENT '編号',
`UID` INT(11) DEFAULT NULL COMMENT '使用者編号',
`MONEY` DOUBLE DEFAULT NULL COMMENT '金額',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `account`(`ID`,`UID`,`MONEY`) VALUES (1,46,1000),(2,45,1000),(3,46,2000);
5.3 Mybatis維護一對多關系
5.3.1 查詢所有賬戶
第一步:建立工程
第二步:編寫java檔案、資源檔案、測試檔案、pom.xml
在上述案例中,再添加以下檔案
cn.iyhome.domain.Account.java
cn.iyhome.dao.IAccountDao.java
cn/iyhome/dao/IAccountDao.xml
Account.java
public class Account {
private int id;
private int uid;
private float money;
//getter
//setter
@Override
public String toString() {
//...略
}
}
IAccountDao.java
public interface IAccountDao {
/**
* 查詢所有使用者
* @return
*/
List<Account> findAll();
/**
* 根據id查詢使用者資訊
* @param id
* @return
*/
Account findById(Integer id);
}
IAccountDao.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="cn.iyhome.dao.IAccountDao">
<!--查找所有-->
<select id="findAll" resultType="account">
select * from account
</select>
<!--根據id查找賬戶資訊-->
<select id="findById" parameterType="int" resultType="account">
select * from account where id = #{id}
</select>
</mapper>
未完待續…