在 MyBatis框架快速入門(一),搭建MyBatis開發環境 中已經把開發環境搭起來了,並測試了一個簡單的查詢操作,接下來就是對之前配置文件的繁瑣重複操作進行簡化,在此基礎上通過使用映射器接口代理對象的方式實現單表的增刪改查並學習其相關配置文件參數的配置與注意事項。
配置文件的簡化
MyBatis開發環境搭建參考: MyBatis框架快速入門(一),搭建MyBatis開發環境
1.typeAliases別名處理器,簡化映射配置文件中statement的parameterType,resultType屬性值的編寫
Mybatis的映射文件與映射器相對應,如果映射器中的方法有輸入參數及返回結果,那麼在Mybatis 的映射文件中也要配置與之對應的參數(parameterType)和返回結果(resultType),其屬性值寫全限定類名,當這個映射器有很多個方法時,我們在映射文件中配置都要以全限定類名來配置,這樣配置就變得繁瑣了。這時我們可以通過在覈心配置文件(SqlMapConfig.xml)中使用typeAlias標籤來給這些全限定類名起類型別名的方式來簡化操作。
typeAliases別名處理器:爲 Java 類型設置一個短的名字,可以方便我們引用某個類。
在核心配置文件(SqlMapConfig.xml)的configuration標籤中添加
<typeAliases>
<!--給指定包下的每一個類創建一個默認的別名,別名就是其不區分大小寫類名-->
<package name="com.mycode.domain"/>
</typeAliases>
配置後,在映射配置文件中就可以相對應的使用別名。
注意:SqlMapConfig.xml中配置順序
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 |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
2.在覈心配置文件mappers映射器中批量註冊指定包下所有的映射器接口
之前使用的方式是通過mapper逐個註冊映射配置文件(配置了映射配置文件的位置,Mybatis就可以加載statement信息,再根據namespace屬性找到映射器),而這種方式在有很多映射配置文件時,配置也會變得很繁瑣。
通過自動註冊指定包下所有的映射器接口方式簡化,在核心配置文件(SqlMapConfig.xml)的configuration標籤中修改mappers映射器,使用方式二:
<mappers>
<!--方式一:通過mapper逐個註冊映射配置文件
配置了映射配置文件的位置,Mybatis就可以加載statement信息,再根據namespace屬性找到映射器
-->
<!--<mapper resource="com/mycode/dao/UserMapper.xml"></mapper>-->
<!--方式二:自動註冊指定包下所有的映射器接口
這種方式對映射配置文件的要求:映射配置文件名必須和映射器接口名相同並且在同一目錄下。
Mybatis根據包名找到所有的映射器的類名和位置,符合以上兩點要求就可以根據映射器的類名和位置查找同名同路徑的映射配置文件。
-->
<package name="com.mycode.dao"/>
</mappers>
通過簡化後在配置映射配置文件時就可以通過起別名的方式減少代碼量,通過mappers映射器批量註冊映射器接口的方式來簡化對映射配置文件的引入註冊,在此基礎上通過Mybatis實現增刪改查就會更爲便捷。
增刪改查操作
1.OGNL表達式簡介
- OGNL:Object Graphic Navigator Language(對象導航圖語言),是應用於Java中的一個開源的表達式語言。
- 作用:對數據進行訪問,它擁有類型轉換、訪問對象方法、操作集合對象等功能。如從Java對象中獲取某一屬性的值(本質上使用的是JavaBean的getXxx()方法。如:user.getUsername()—>user.username,使用#{user.username}就可以獲取userame屬性的值)。
- 在#{}裏、${}使用OGNL表達式,可以從JavaBean中獲取指定屬性的值。
2.映射配置文件中相關配置屬性:parameterType與resultType說明
parameterType屬性:將會傳入這條語句的參數類的全限定類名或別名。
parameterType屬性屬性值可以是:
- 簡單類型(基本數據類型或者string)
如果只有一個參數且參數是簡單類型的,那麼#{ }
中{}裏的值可以隨意寫。 - JavaBean
在SQL語句中,可以在#{}
或者${}
中使用OGNL表達式來獲取JavaBean的屬性值。如parameterType是com.mycode.domain.User或其別名,則在SQL語句中可以使用#{username}
獲取username屬性值。 - JavaBean包裝類(一個JavaBean裏還有JavaBean)
在SQL語句中,可以在#{}
或者${}
中使用OGNL表達式來獲取JavaBean的屬性值。如parameterType是com.mycode.domain.MyWrapper 或其別名,則在SQL語句中可以使用#{user.username}
獲取username屬性值。
User類
public class User {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
User包裝類
public class MyWrapper {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
resultType屬性:結果集的數據要封裝成的對象的全限定類名或別名。
parameterType屬性屬性值可以是:
- 簡單類型(基本數據類型或者string)
- JavaBean(JavaBean的屬性名要和字段名保持一致)
當JavaBean中屬性名和字段名不一致時的解決方案:在映射配置文件中配置即可
方案①sql起別名的方式
<!--JavaBean中屬性名和字段名不一致的情況解決方案一-->
<select id="selectAllAliasUser" resultType="com.mycode.domain.AliasUser">
select id uid,username uname,password upassword,email uemail,birthday ubirthday from t_user;
</select>
方案②使用 resultMap標籤設置結果集中字段名和JavaBean屬性的對應關係
<!--JavaBean中屬性名和字段名不一致的情況解決方案二-->
<select id="selectAllAliasUser2" resultMap="aliasUserMap">
select * from t_user
</select>
<!--
resultMap標籤:設置結果集中字段名和JavaBean屬性的對應關係
id屬性:唯一標識
type屬性:結果集的數據要封裝成的對象的全限定類名或別名
-->
<resultMap id="aliasUserMap" type="AliasUser">
<!--id標籤:主鍵字段配置。 property屬性:JavaBean的屬性名 column屬性:字段名-->
<id property="uid" column="id"></id>
<!--result標籤:非主鍵字段配置。-->
<result property="uname" column="username"></result>
<result property="upassword" column="upassword"></result>
<result property="uemail" column="email"></result>
<result property="ubirthday" column="birthday"></result>
</resultMap>
數據庫User表各字段名
JavaBean
public class AliasUser {
private Integer uid;
private String uname;
private String upassword;
private String uemail;
private Date ubirthday;
...
...
}
3.簡單增刪改查代碼示例
①.在映射器UserMapper接口中編寫增刪改查方法
- UserMapper接口【映射器】(UserMapper)
public interface UserMapper {
//增加用戶
int insertUser(User user);
//根據id刪除指定用戶
void deleteUser(int id);
//更新指定用戶信息
void updateUser(User user);
//根據id查詢用戶信息(前面查詢了全部用戶,這裏查詢單個)
User selectUserByid(int id);
//查詢總記錄數
int getTotalCount();
}
②.在映射配置文件UserMapper.xml中配置映射器裏的每個方法 與 提供單元測試類實現增刪改查
單元測試類中使用@Before與@After註解使被@Test註解標註的public void方法執行之前執行先獲取映射器接口代理對象,執行之後關閉連接。
public class CRUDTest {
private InputStream inputStream;
private SqlSession sqlSession;
private UserMapper mapper;
@Before//在使用@Test註解標註的public void方法執行之前執行
public void init() throws IOException {
//讀取核心配置文件(SqlMapConfig.xml),得到輸入流
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//創建構造者對象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//構造者根據輸入流構造一個工廠,得到SqlSessionFactory工廠對象【構造者模式】
SqlSessionFactory factory = builder.build(inputStream);
//工廠對象生產一個SqlSession對象【工廠模式】
sqlSession = factory.openSession();
//使用SqlSession對象獲取映射器UserMapper接口的代理對象由代理對象完成功能【代理模式】
mapper = sqlSession.getMapper(UserMapper.class);
}
@After//在使用@Test註解標註的public void方法執行之後執行
public void destory() throws IOException {
sqlSession.close();
inputStream.close();
}
}
- 映射配置文件中添加 insertUser方法的配置(增)
<!--
parameterType屬性:將要傳入語句的參數的完全限定類名或別名
-->
<insert id="insertUser" parameterType="user">
<!--對於不支持自動生成主鍵列的數據庫和可能不支持自動生成主鍵的 JDBC 驅動,MyBatis提供了一種方法來生成主鍵:
selectKey標籤:用於在添加數據之後,查詢最新主鍵值
keyProperty屬性:查詢得到的最新主鍵值,放在參數user的哪個屬性裏
resultType屬性:查詢的最新主鍵值,是什麼類型。(MyBatis允許任何簡單類型用作主鍵的類型,包括字符串)
order屬性:是在添加之前查詢最新主鍵值,還是在添加之後查詢最新主鍵值。MySql設置爲AFTER Oracle設置爲BEFORE
標籤體:查詢最新主鍵值的SQL語句
【擴展】:當插入數據後就要在界面顯示該數據時,應主鍵(id)是自增的,在插入時並沒有賦值插入,所以這時如果直接輸出顯示的id就爲null,
這時可以通過配置映射配置文件(UserMapper.xml)selectKey標籤來查詢id的值,並返回顯示。具體查看測試類
-->
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into t_user (id,username,password,email,birthday) values (#{id},#{username},#{password},#{email},#{birthday});
<!-- SQL語句中使用了OGNL表達式,#{}代表佔位符,相當於預編譯對象的SQL中的?,具體的值由User類中的屬性確定
根據OGNL表達式這裏實際應寫爲#{user.id}...但是Mybatis簡化了操作。
-->
</insert>
- 在單元測試類中添加 增加操作方法
@Test
public void insertUserTest() throws IOException {
User user = new User();
user.setUsername("張三三");
user.setPassword("3");
user.setEmail("[email protected]");
user.setBirthday( new Date());
System.out.println("插入數據前"+user);
//插入數據前User{id=null, username='張三三', password='3', email='[email protected]', birthday=Thu Mar 12 21:19:36 CST 2020}
mapper.insertUser(user);
/*
當一個連接對象被創建時,默認情況下是自動提交事務的
從日誌信息可以知道,Mybatis在創建連接後會取消自動提交事務( Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fc2b765])
而DML語句執行完一定要提交事務,所以這裏需要自己手動提交事務
*/
sqlSession.commit();
/*
當插入數據後,要在界面顯示該數據時,應主鍵(id)是自增的,沒有賦值插入,所以這裏輸出的id就爲null,
這時可以通過配置映射配置文件(UserMapper.xml)selectKey 來查詢id的值,並返回顯示。
*/
System.out.println("插入數據後"+user);
//插入數據後User{id=58, username='張三三', password='3', email='[email protected]', birthday=Thu Mar 12 21:19:36 CST 2020}
}
- 映射配置文件中添加 deleteUser方法的配置(刪)
<!--如果只有一個參數且參數是簡單類型(基本數據類型和String),那麼#{}中{}裏的值可以隨意寫,一般規範見名知意,即這裏根據id查,就寫爲#{id} -->
<delete id="deleteUser" parameterType="int">
delete from t_user where id=#{id}
</delete>
- 在單元測試類中添加 刪除操作方法
@Test
public void deleteUserTest() throws IOException {
mapper.deleteUser(35);
sqlSession.commit();
}
- 映射配置文件中添加 updateUser方法的配置(改)
<update id="updateUser" parameterType="user">
update t_user set password=#{password},email=#{email} where id=#{id}
</update>
- 在單元測試類中添加 更改操作方法
@Test
public void updateUserTest() throws IOException {
User user = new User();
user.setId(58);
user.setPassword("1");
user.setEmail("[email protected]");
mapper.updateUser(user);
sqlSession.commit();
}
映射配置文件中添加 selectUserByid方法的配置(查)
<select id="selectUserByid" parameterType="int" resultType="user">
select * from t_user where id=#{id}
</select>
- 在單元測試類中添加 查詢操作方法
@Test
public void selectUserByIdTest() throws IOException {
User user = mapper.selectUserByid(31);
System.out.println(user);
}
- 映射配置文件中添加 getTotalCount方法的配置(查)
<select id="getTotalCount" resultType="int">
select count(*) from t_user
</select>
- 在單元測試類中添加 查詢數量方法
@Test
public void getTotalCountTest() throws IOException {
int count = mapper.getTotalCount();
System.out.println(count);
}
4.模糊查詢(探究#{}和${value}的區別)
①.在映射器UserMapper接口中編寫模糊查詢方法
- UserMapper接口【映射器】(UserMapper)
public interface UserMapper {
//根據用戶名模糊查詢(使用#{}方式)
List<User> selectUserByUserName(String username);
//根據用戶名模糊查詢(使用${}方式)
List<User> selectUserByUserName2(String username);
}
②.在映射配置文件UserMapper.xml中配置映射器裏的每個方法 與 提供單元測試類實現模糊查詢
- 映射配置文件中添加 selectUserByUserName方法的配置(使用#{}方式模糊查詢)
<select id="selectUserByUserName" parameterType="string" resultType="user">
<!--使用#{}方式進行模糊查詢-->
select * from t_user where username like #{username}
</select>
- 在單元測試類中添加 模糊查詢方法
//使用#{}方式進行模糊查詢
@Test
public void selectUserByUserNameTest() throws IOException {
//模糊查詢的條件值,需要有%
List<User> userList = mapper.selectUserByUserName("%上官%");
for (User user : userList) {
System.out.println(user);
}
}
運行結果:<××××> 爲註釋
DEBUG serMapper.selectUserByUserName - ==> Preparing: select * from t_user where username like ? <最終執行的SQL>
DEBUG serMapper.selectUserByUserName - ==> Parameters: %上官%(String)<參數值>
DEBUG serMapper.selectUserByUserName - <== Total: 4
User{id=4, username='上官婉兒', password='1', email='null', birthday=null}
User{id=5, username='上官鍋兒', password='1', email='null', birthday=null}
User{id=6, username='上官瓢兒', password='1', email='null', birthday=null}
User{id=21, username='上官蓋兒', password='1', email='null', birthday=null}
從日誌中可以看出#{}
表示一個佔位符,相當於預編譯SQL中的?
- 可以防止SQL注入
- 使用
#{}
方式,Mybatis會自動進行參數的Java類型和JDBC類型轉換(在日誌中Parameters: %上官%(String
) ,帶有參數類型,如是日期類型則會轉換 ) - 如果 parameterType傳輸的是單個簡單類型值,
#{}
括號中可以是 value 或其它名稱。
- 映射配置文件中添加 selectUserByUserName2方法的配置(使用${}方式模糊查詢)
<select id="selectUserByUserName2" parameterType="string" resultType="user">
<!--使用${}方式進行模糊查詢 ${}的{}裏必須是value-->
select * from t_user where username like '%${value}%'
</select>
- 在單元測試類中添加 模糊查詢方法
//使用${}方式進行模糊查詢
@Test
public void selectUserByUserNameTest2() throws IOException {
//在SQL語句中已經加了%,在測試代碼中,傳遞參數值時不需要再加%
List<User> userList = mapper.selectUserByUserName2("上官");
for (User user : userList) {
System.out.println(user);
}
}
運行結果:
DEBUG erMapper.selectUserByUserName2- ==> Preparing: select * from t_user where username like '%上官%'
DEBUG erMapper.selectUserByUserName2 - ==> Parameters:
DEBUG erMapper.selectUserByUserName2 - <== Total: 4
User{id=4, username='上官婉兒', password='1', email='null', birthday=null}
User{id=5, username='上官鍋兒', password='1', email='null', birthday=null}
User{id=6, username='上官瓢兒', password='1', email='null', birthday=null}
User{id=21, username='上官蓋兒', password='1', email='null', birthday=null}
從日誌中可以看出${value}
表示拼接SQL串,相當於把實際參數值,直接替換掉${value}
- 不能防止SQL注入
- 使用
${}
方式,Mybatis不進行參數的Java類型和JDBC類型轉換(拼接的方式是直接把參數“這個時間字符串”拼接上去了) - 如果 parameterType傳輸的是單個簡單類型值,
${}
括號中只能是 value。
基於#{}和${value}的區別與特點,在實際使用中,出於安全性考慮,一般都使用#{}的方式。但是有一種情況,當我們要對查詢結果按照一定的順序進行排序時,使用#{}的方式是達不到預期效果的。
如:根據指定字段名(id)降序查詢所有用戶
- UserMapper接口【映射器】(UserMapper)
public interface UserMapper {
//根據指定字段名(id)降序查詢所有用戶(使用#{}方式)
List<User> selectAllUserByIdSort(String columnName);
//根據指定字段名(id)降序查詢所有用戶(使用${}方式)
List<User> selectAllUserByIdSort2(String columnName);
}
- 映射配置文件中添加 selectAllUserByIdSort方法的配置(使用#{}方式)
<select id="selectAllUserByIdSort" parameterType="String" resultType="User">
select * from t_user order by #{value} desc
</select>
- 在單元測試類中添加 降序查詢方法
@Test
public void selectAllUserByIdSort() throws IOException {
List<User> userList = mapper.selectAllUserByIdSort("id");
for (User user : userList) {
System.out.println(user);
}
}
運行輸出:(無效)
[com.mysql.jdbc.JDBC4Connection@4cf4d528]
DEBUG erMapper.selectAllUserByIdSort - ==> Preparing: select * from t_user order by ? desc
DEBUG erMapper.selectAllUserByIdSort - ==> Parameters: id(String)
DEBUG erMapper.selectAllUserByIdSort - <== Total: 40
User{id=1, username='張三', password='1', email='null', birthday=null}
User{id=2, username='李四', password='1', email='null', birthday=null}
User{id=3, username='王五', password='1', email='null', birthday=null}
User{id=4, username='上官婉兒', password='1', email='null', birthday=Fri Mar 13 00:00:00 CST 2020}
User{id=5, username='上官鍋兒', password='1', email='null', birthday=null}
User{id=6, username='上官瓢兒', password='1', email='null', birthday=null}
......
.......
- 映射配置文件中添加 selectAllUserByIdSort2方法的配置(使用#{}方式)
<select id="selectAllUserByIdSort2" parameterType="String" resultType="User">
select * from t_user order by ${value} desc
</select>
- 在單元測試類中添加 降序查詢方法
@Test
public void selectAllUserByIdSort2() throws IOException {
List<User> userList = mapper.selectAllUserByIdSort2("id");
for (User user : userList) {
System.out.println(user);
}
}
運行輸出:
DEBUG rMapper.selectAllUserByIdSort2 - ==> Preparing: select * from t_user order by id desc
DEBUG rMapper.selectAllUserByIdSort2 - ==> Parameters:
DEBUG rMapper.selectAllUserByIdSort2 - <== Total: 36
User{id=43, username='百里', password='3', email='[email protected]', birthday=null}
User{id=39, username='迪迦', password='1', email='null', birthday=null}
User{id=38, username='伽羅', password='1', email='null', birthday=null}
User{id=37, username='小喬', password='1', email='null', birthday=null}
User{id=36, username='大喬', password='1', email='null', birthday=null}
...
...
源碼簡單分析通過代理對象的方式實現增刪改查的運行過程
功力不夠,簡單分析
以執行更新操作爲例
讀取核心配置文件SqlMapConfig.xml(通過類加載器讀取SqlMapConfig.xml,得到輸入流)
創建構造器
構造者根據輸入流讀取配置文件,解析xml,把所有配置信息封裝到Configuration對象,構造一個DefaultSqlSessionFactory工廠並把Configuration傳遞給工廠【構造者模式】
工廠生產一個DefaultSqlSession出來,並把Configuration傳遞給DefaultSqlSession【工廠模式】
使用DefaultSqlSession對象獲取映射器UserMapper接口的代理對象
單元測試類中讓代理對象去操作數據
代理對象的行爲
在Configuration封裝配置文件的信息中,映射器配置文件UserMapper.xml中namespace屬性值(com.mycode.dao.UserMapper)與每個statement的id屬性值(即每個方法名)作爲key,把sql語句與參數結果集(value)等保存在一個Map中。
在單元測試類中通過所測試的方法( 映射器全限定類名 + 方法名)作爲key值,得到對應sql語句與參數結果集(value值),即得到了要執行的sql語句與參數或結果集。
接下來的就是Mybatis底層封裝JDBC執行sql的操作了