文檔版本 | 開發工具 | 測試平臺 | 工程名字 | 日期 | 作者 | 備註 |
---|---|---|---|---|---|---|
V1.0 | 2016.06.26 | lutianfei | none |
mybatis開發dao的方法
SqlSession使用範圍
SqlSessionFactoryBuilder
- 通過SqlSessionFactoryBuilder創建會話工廠SqlSessionFactory
- 將SqlSessionFactoryBuilder當成一個工具類使用即可,不需要使用單例管理SqlSessionFactoryBuilder。
- 在需要創建SqlSessionFactory時候,只需要new一次SqlSessionFactoryBuilder即可。
SqlSessionFactory
- 通過SqlSessionFactory創建SqlSession,使用單例模式管理sqlSessionFactory(工廠一旦創建,使用一個實例)。
- 將來mybatis和spring整合後,使用單例模式管理sqlSessionFactory。
SqlSession
- SqlSession是一個面向用戶(程序員)的接口。
- SqlSession中提供了很多操作數據庫的方法:如:selectOne(返回單個對象)、selectList(返回單個或多個對象)、。
- SqlSession是線程不安全的,在SqlSesion實現類中除了有接口中的方法(操作數據庫的方法)還有數據域屬性。
- SqlSession最佳應用場合在方法體內,定義成局部變量使用。
SqlSession執行過程如下:
- 1、 加載數據源等配置信息
- Environment environment = configuration.getEnvironment();
- 2、 創建數據庫鏈接
- 3、 創建事務對象
- 4、 創建Executor,SqlSession所有操作都是通過Executor完成,mybatis源碼如下:
- 1、 加載數據源等配置信息
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();
}
原始dao開發方法
- 程序員需要寫dao接口和dao實現類
思路
- 程序員需要寫dao接口和dao實現類。
- 需要向dao實現類中注入SqlSessionFactory,在方法體內通過SqlSessionFactory創建SqlSession
dao接口
- dao接口實現類
public class UserDaoImpl implements UserDao {
// 需要向dao實現類中注入SqlSessionFactory
// 這裏通過構造方法注入
private SqlSessionFactory sqlSessionFactory;
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User findUserById(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("test.findUserById", id);
// 釋放資源
sqlSession.close();
return user;
}
@Override
public List<User> findUserByName(String name) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> list = sqlSession.selectList("test.findUserByName", name);
// 釋放資源
sqlSession.close();
return list;
}
@Override
public void insertUser(User user) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//執行插入操作
sqlSession.insert("test.insertUser", user);
// 提交事務
sqlSession.commit();
// 釋放資源
sqlSession.close();
}
@Override
public void deleteUser(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//執行插入操作
sqlSession.delete("test.deleteUser", id);
// 提交事務
sqlSession.commit();
// 釋放資源
sqlSession.close();
}
- 測試代碼:
public class UserDaoImplTest {
private SqlSessionFactory sqlSessionFactory;
// 此方法是在執行testFindUserById之前執行
@Before
public void setUp() throws Exception {
// 創建sqlSessionFactory
// mybatis配置文件
String resource = "SqlMapConfig.xml";
// 得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 創建會話工廠,傳入mybatis的配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
}
@Test
public void testFindUserById() throws Exception {
// 創建UserDao的對象
UserDao userDao = new UserDaoImpl(sqlSessionFactory);
// 調用UserDao的方法
User user = userDao.findUserById(1);
System.out.println(user);
}
}
原始dao開發問題
- dao接口實現類方法中存在大量模板方法,設想能否將這些代碼提取出來,大大減輕程序員的工作量。
- 調用sqlsession方法時將statement的id硬編碼了
- 調用sqlsession方法時傳入的變量,由於sqlsession方法使用泛型,即使變量類型傳入錯誤,在編譯階段也不報錯,不利於程序員開發。
mapper代理方法
- 程序員只需要mapper接口(相當 於dao接口)
mapper代理開發規範
- 程序員還需要編寫mapper.xml映射文件
程序員編寫mapper接口需要遵循一些開發規範,這樣Mybatis可以自動生成mapper接口實現類代理對象。
開發規範:
- 1、在mapper.xml中namespace等
於mapper接口地址
- 2、mapper.java接口中的方法名和mapper.xml中statement的id一致
- 3、mapper.java接口中的方法輸入參數類型和mapper.xml中statement的parameterType指定的類型一致。
- 4、mapper.java接口中的方法返回值類型和mapper.xml中statement的resultType指定的類型一致。
- 1、在mapper.xml中namespace等
- 總結:
- 以上開發規範主要是對下邊的代碼進行統一生成:
User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.insert("test.insertUser", user);
- 編寫XXXmapper.java
public interface UserMapper {
//用戶信息綜合查詢
public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception;
//用戶信息綜合查詢總數
public int findUserCount(UserQueryVo userQueryVo) throws Exception;
//根據id查詢用戶信息
public User findUserById(int id) throws Exception;
//根據id查詢用戶信息,使用resultMap輸出
public User findUserByIdResultMap(int id) throws Exception;
//根據用戶名列查詢用戶列表
public List<User> findUserByName(String name)throws Exception;
//插入用戶
public void insertUser(User user)throws Exception;
//刪除用戶
public void deleteUser(int id)throws Exception;
}
- XXXmapper.xml
- 在SqlMapConfig.xml中加載mapper.xml
<!-- 加載 映射文件 -->
<mappers>
<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
<!-- 批量加載mapper
指定mapper接口的包名,mybatis自動掃描包下邊所有mapper接口進行加載,遵循一些規範:需要將mapper接口類名和mapper.xml映射文件名稱保持一致,且在一個目錄中
上邊規範的前提是:使用的是mapper代理方法
-->
<package name="cn.itcast.mybatis.mapper"/>
</mappers>
- 測試
@Test
public void testFindUserById() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//創建UserMapper對象,mybatis自動生成mapper代理對象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//調用userMapper的方法
User user = userMapper.findUserById(1);
System.out.println(user);
}
- 一些問題總結
- 代理對象內部調用selectOne或selectList ?
- 如果mapper方法返回單個pojo對象(非集合對象),代理對象內部通過selectOne查詢數據庫。
- 如果mapper方法返回集合對象,代理對象內部通過selectList查詢數據庫。
- mapper接口方法參數只能有一個是否影響系統擴展維護?
- 系統框架中,dao層的代碼是被業務層公用的。即使mapper接口只有一個參數,可以使用包裝類型的pojo滿足不同的業務方法的需求。
- 注意:持久層方法的參數可以包裝類型、map、list等等;
- 但service方法中建議不要使用包裝類型(不利於業務層的可擴展)。
- 代理對象內部調用selectOne或selectList ?
SqlMapConfig.xml配置文件
- mybatis的全局配置文件SqlMapConfig.xml,配置內容如下:
- properties(屬性)
- settings(全局配置參數)
- typeAliases(類型別名)
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
- plugins(插件)
- environments(環境集合屬性對象)
- environment(環境子屬性對象)
- transactionManager(事務管理)
- dataSource(數據源)
- mappers(映射器)
properties屬性
需求:將數據庫連接參數單獨配置在db.properties中,只需要在SqlMapConfig.xml中加載db.properties的屬性值。
在SqlMapConfig.xml中就不需要對數據庫連接參數硬編碼。
- 將數據庫連接參數只配置在db.properties中:方便對參數進行統一管理,其它xml可以引用該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">
<!--properties中還可以配置一些屬性名和屬性值 -->
<!-- <property name="jdbc.driver" value=""/> -->
</properties>
<!-- 和spring整合後 environments配置將廢除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事務管理,事務控制由mybatis-->
<transactionManager type="JDBC" />
<!-- 數據庫連接池,由mybatis管理-->
<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>
- properties特性:
- 注意: MyBatis 將按照下面的順序來加載屬性:
- 在 properties 元素體內定義的屬性首先被讀取。
- 然後會讀取properties 元素中resource或 url 加載的屬性,它會覆蓋已讀取的同名屬性。
- 最後讀取parameterType傳遞的屬性,它會覆蓋已讀取的同名屬性。
因此,通過
parameterType
傳遞的屬性具有最高優先級,resource
或url
加載的屬性次之,最低優先級的是properties
元素體內定義的屬性。建議:
- 不要在properties元素體內添加任何屬性值,只將屬性值定義在properties文件中。
- 在properties文件中定義屬性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
settings全局參數配置
- mybatis框架在運行時可以調整一些運行參數。
- 比如:開啓二級緩存、開啓延遲加載。。
- 全局參數將會影響mybatis的運行行爲。
- 詳細參見“學習資料/mybatis-settings.xlsx”文件
typeAliases(別名)重點
需求
- 在mapper.xml中,定義很多的statement,statement需要parameterType指定輸入參數的類型、需要resultType指定輸出結果的映射類型。
- 如果在指定類型時輸入類型全路徑,不方便進行開發,可以針對parameterType或resultType 指定的類型定義一些別名,在mapper.xml中通過別名定義,方便開發。
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 |
- 自定義別名
單個別名定義
引用別名:
批量定義別名(常用)
<!-- 別名定義 -->
<typeAliases>
<!-- 針對單個別名定義
type:類型的路徑
alias:別名
-->
<!-- <typeAlias type="cn.itcast.mybatis.po.User" alias="user"/> -->
<!-- 批量別名定義
指定包名,mybatis自動掃描包中的po類,自動定義別名,別名就是類名(首字母大寫或小寫都可以)
-->
<package name="cn.itcast.mybatis.po"/>
</typeAliases>
typeHandlers(類型處理器)
- mybatis中通過typeHandlers完成jdbc類型和java類型的轉換。
- 通常情況下,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-任何兼容的字符串類型,作爲代碼存儲(而不是索引)。 |
mappers(映射配置)
- 通過resource加載單個映射文件,
<mapper resource=" " />
使用相對於類路徑的資源
- 如:
<mapper resource="sqlmap/User.xml" />
- 如:
使用完全限定路徑
<mapper url=" " />
- 如:
<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
- 如:
通過mapper接口加載單個mapper
- 規範:mapper**接口類名和mapper.xml映射文件名稱必須保持一致,且在一個package目錄中**;並且要使用mapper代理方法
<!-- 通過mapper接口加載單個映射文件需要遵循一些規範:mapper接口類名和mapper.xml映射文件名稱必須保持一致,且在一個package目錄中;並且要使用mapper代理方法-->
<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
- 批量加載mapper(推薦使用)
- 當指定mapper接口的包名時,mybatis會自動掃描包下邊所有mapper接口進行加載
- 應遵循的一些規範:需要將mapper**接口類名和mapper.xml映射文件名稱保持一致,且在一個package目錄中;並且使用的是**mapper代理方法
<package name="cn.itcast.mybatis.mapper"/>
輸入映射
- 通過parameterType指定輸入參數的類型,類型可以是簡單類型、hashmap、pojo的包裝類型。
傳遞pojo的包裝對象
需求:
- 完成用戶信息的綜合查詢,需要傳入查詢條件很複雜(可能包括用戶信息、其它信息,比如商品、訂單的)
定義包裝類型pojo
- 針對上邊需求,建議使用自定義的包裝類型的pojo。在包裝類型的pojo中將複雜的查詢條件包裝進去。
- 映射文件XXXmapper.xml設置
- 在UserMapper.xml中定義用戶信息綜合查詢(查詢條件複雜,通過高級查詢進行復雜關聯查詢)。
mapper.java
測試代碼
傳遞hashmap
- Sql映射文件定義如下:
#
中 id 和 username是hashmap的key。
<!-- 傳遞<u>hashmap</u>綜合查詢用戶信息 -->
<select id="findUserByHashmap" parameterType="hashmap" resultType="user">
select * from user where id=#{id} and username like '%${username}%'
</select>
- 測試:
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獲取值爲空。
輸出映射
resultType
使用resultType進行輸出映射,只有查詢出來的列名和pojo中的屬性名一致,該列纔可以映射成功。
- 如果查詢出來的列名和pojo中的屬性名全部不一致,沒有創建pojo對象。
- 只要查詢出來的列名和pojo中的屬性有一個一致,就會創建pojo對象。
輸出簡單類型
需求
- 用戶信息的綜合查詢列表總數,通過查詢總數和上邊用戶綜合查詢列表纔可以實現分頁。
Usermapper.xml
Usermapper.java
測試代碼
小結
- 查詢出來的結果集只有一行且一列,可以使用簡單類型進行輸出映射。
輸出pojo對象和pojo列表
- 不管是輸出的pojo單個對象還是一個列表(list中包括pojo),在mapper.xml中resultType指定的類型是一樣的。
在mapper.java指定的方法返回值類型不一樣:
- 1、輸出單個pojo對象,方法返回值是單個對象類型
- 2、輸出pojo對象list,方法返回值是
List<Pojo>
生成的動態代理對象中是根據mapper方法的返回值類型確定是調用selectOne(返回單個對象調用)還是selectList (返回集合對象調用)
resultMap
mybatis中使用resultMap完成高級輸出結果映射。
resultMap使用方法
- 如果查詢出來的列名和pojo的屬性名不一致,通過定義一個resultMap對列名和pojo屬性名之間作一個映射關係。
案例: 將下邊的sql使用User完成映射
- User類中屬性名和上邊查詢列名不一致。
SELECT id id_,username username_ FROM USER WHERE id=#{value}
- 1、在UserMapper.xml中定義resultMap
<!-- 定義resultMap
將SELECT id id_,username username_ FROM USER 和User類中的屬性作一個映射關係
type:resultMap最終映射的java對象類型,可以使用別名
id:對resultMap的唯一標識
-->
<resultMap type="user" id="userResultMap">
<!-- id表示查詢結果集中唯一標識
column:查詢出來的列名
property:type指定的pojo類型中的屬性名
最終resultMap對column和property作一個映射關係 (對應關係)
-->
<id column="id_" property="id"/>
<!--
result:對普通名映射定義
column:查詢出來的列名
property:type指定的pojo類型中的屬性名
最終resultMap對column和property作一個映射關係 (對應關係)
-->
<result column="username_" property="username"/>
</resultMap>
- 2、使用resultMap作爲statement的輸出映射類型
<!-- 使用resultMap進行輸出映射
resultMap:指定定義的resultMap的id,如果這個resultMap在其它的mapper文件,前邊需要加namespace
-->
<select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap">
SELECT id id_,username username_ FROM USER WHERE id=#{value}
</select>
mapper.java
測試
@Test
public void testFindUserByIdResultMap() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//創建UserMapper對象,mybatis自動生成mapper代理對象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//調用userMapper的方法
User user = userMapper.findUserByIdResultMap(1);
System.out.println(user);
}
- 小結
- 使用resultType進行輸出映射,只有查詢出來的列名和pojo中的屬性名一致,該列纔可以映射成功。
- 如果查詢出來的列名和pojo的屬性名不一致,通過定義一個resultMap對列名和pojo屬性名之間作一個映射關係。
動態sql
什麼是動態sql
- mybatis核心 對sql語句進行靈活操作,通過表達式進行判斷,對sql進行靈活拼接、組裝。
需求
- 用戶信息綜合查詢列表和用戶信息查詢列表總數這兩個statement的定義使用動態sql。
- 對查詢條件進行判斷,如果輸入參數不爲空才進行查詢條件拼接。
mapper.xml
- 測試代碼
sql片段
需求
- 將上邊實現的動態sql判斷代碼塊抽取出來,組成一個sql片段。其它的statement中就可以引用sql片段。方便程序員進行開發。
定義sql片段,在UserMapper.xml頂部
- 引用sql片段
- 在mapper.xml中定義的statement中引用sql片段:
foreach
向sql傳遞數組或List,mybatis使用foreach解析
需求
- 在用戶查詢列表和查詢總數的statement中增加多個id輸入查詢。sql語句如下:
//兩種方法:
SELECT * FROM USER WHERE id=1 OR id=10 OR id=16
SELECT * FROM USER WHERE id IN(1,10,16)
- 在輸入參數類型中添加
List<Integer>
ids傳入多個id
- 修改mapper.xml
- 對於此類型Sql語句:WHERE id=1 OR id=10 OR id=16
- 在查詢條件中,查詢條件定義成一個sql片段,需要修改sql片段。
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.sex!=null and userCustom.sex!=''">
and user.sex = #{userCustom.sex}
</if>
<if test="userCustom.username!=null and userCustom.username!=''">
and user.username LIKE '%${userCustom.username}%'
</if>
<if test="ids!=null">
<!-- 使用 foreach遍歷傳入ids
collection:指定輸入 對象中集合屬性
item:每個遍歷生成對象中
open:開始遍歷時拼接的串
close:結束遍歷時拼接的串
separator:遍歷的兩個對象中需要拼接的串
-->
<!-- 使用實現下邊的sql拼接:
AND (id=1 OR id=10 OR id=16)
-->
<foreach collection="ids" item="user_id" open="AND (" close=")" separator="or">
<!-- 每個遍歷需要拼接的串 -->
id=#{user_id}
</foreach>
<!-- 實現 “ and id IN(1,10,16)”拼接 -->
<!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=",">
每個遍歷需要拼接的串
#{user_id}
</foreach> -->
</if>
</if>
</sql>
測試代碼
在UserQueryVo中:
public class UserQueryVo {
//傳入多個id
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
- 對於另外一個sql的實現:
<!-- 實現 “ and id IN(1,10,16)”拼接 -->
<!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=",">
每個遍歷需要拼接的串
#{user_id}
</foreach> -->