MyBatis框架的學習(二)——MyBatis架構與入門
MyBatis框架的架構
MyBatis框架的架構如下圖:
下面作簡要概述:
- SqlMapConfig.xml,此檔案作為mybatis的全域性配置檔案,配置了mybatis的執行環境等資訊。mapper.xml檔案即sql對映檔案,檔案中配置了操作資料庫的sql語句,此檔案需要在SqlMapConfig.xml中載入。
- 通過mybatis環境等配置資訊構造SqlSessionFactory(即會話工廠)。
- 由會話工廠建立sqlSession即會話,操作資料庫需要通過sqlSession進行。
- mybatis底層自定義了Executor執行器介面操作資料庫,Executor介面有兩個實現,一個是基本執行器、一個是快取執行器。
- MappedStatement也是mybatis一個底層封裝物件,它包裝了mybatis配置資訊及sql對映資訊等。mapper.xml檔案中一個sql對應一個MappedStatement物件,sql的id即是MappedStatement的id。
- MappedStatement對sql執行輸入引數進行定義,包括HashMap、基本型別、pojo,Executor通過MappedStatement在執行sql前將輸入的java物件對映至sql中,輸入引數對映就是JDBC程式設計中對preparedStatement設定引數。
- MappedStatement對sql執行輸出結果進行定義,包括HashMap、基本型別、pojo,Executor通過MappedStatement在執行sql後將輸出結果對映至java物件中,輸出結果對映過程相當於JDBC程式設計中對結果的解析處理過程。
MyBatis入門
mybatis下載
mybaits的程式碼由github.com管理,地址:https://github.com/mybatis/mybatis-3/releases。大家可從該地址下載mybatis最新框架。
本人下載的是mybatis-3.2.7,其目錄結構為:
開發需求
現在我們明確需求,即實現以下功能:
- 根據使用者id查詢一個使用者資訊
- 根據使用者名稱稱模糊查詢使用者資訊列表
- 新增使用者
- 更新使用者
- 刪除使用者
MyBatis入門程式配置
【第一步】,使用Eclipse建立一個普通的Java工程,例如mybatis-day01。
【第二步】,加入Jar包。工程所需加入的Jar包有mybatis核心包、依賴包和mysql資料驅動包。如下:
【第三步】,在classpath下建立日誌記錄檔案——log4j.properties。我們可在mybatis-day01工程下新建一個config原始碼包,並在該原始碼包下建立一個日誌記錄檔案——log4j.properties
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作為輸出日誌資訊。
【第四步】,在classpath下建立SqlMapConfig.xml檔案。我們同樣可在config原始碼包下建立該檔案
其內容如下:
<?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="yezi" />
</dataSource>
</environment>
</environments>
</configuration>
SqlMapConfig.xml是mybatis的核心配置檔案,以上檔案的配置內容為資料來源、事務管理。
注意:等後面mybatis和Spring兩個框架整合之後,environments的配置將被廢除。
【第五步】,建立一個po類——User.java。我們可在src目錄下新建一個名為cn.itheima.mybatis.po的包,並在該包下建立一個po類——User.java
po類作為mybatis進行sql對映使用,po類通常與資料庫表對應,User.java檔案的內容如下:
public class User {
private int id;
private String username;// 使用者姓名
private String sex;// 性別
private Date birthday;// 生日
private String address;// 地址
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address="
+ address + "]";
}
}
【第六步】,在classpath下的sqlmap目錄下建立sql對映檔案user.xml
user.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語句(即不同sql對映檔案中的兩個相同id的sql語句如何來區分),這是當前的作用,後面會講另一層非常重要的作用。
【第七步】,載入對映檔案。mybatis框架需要載入對映檔案,將user.xml新增在SqlMapConfig.xml中,如下:
故須在SqlMapConfig.xml配置檔案中新增如下配置資訊:
<mappers>
<!-- resource是基於classpath來查詢的 -->
<mapper resource="sqlmap/user.xml"/>
</mappers>
MyBatis入門程式測試——根據id查詢使用者資訊
在user.xml對映檔案中新增如下配置:
<!-- 根據id獲取使用者資訊 -->
<select id="getUserById" parameterType="int" resultType="cn.itheima.mybatis.po.User">
select * from user where id=#{id};
</select>
- parameterType:查詢引數的資料型別,即定義輸入到sql中的對映型別。
- resultType:查詢結果的資料型別,如果是pojo則應該給出全路徑。
#{id}
表示使用PreparedStatement設定佔位符號並將輸入變數id傳到sql中。說白點,#{}
作用就是佔位符,相當於JDBC中的?
。
接著在src目錄下新建一個名為cn.itheima.mybatis.first的包,並在該包下建立一個MybatisTest單元測試類,緊接著在該類中編寫如下一個但單元測試方法:
public class MybatisTest {
@Test
public void getUserById() throws IOException {
// 第一步,建立SqlSessionFactoryBuilder物件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 第二步,載入配置檔案
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 第三步,建立SqlSessionFactory物件
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 第四步,建立SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 第五步,使用SqlSession物件執行查詢,得到User物件
// 第一個引數:執行查詢的StatementId
User user = sqlSession.selectOne("getUserById", 10);
// 第六步,列印結果
System.out.println(user);
// 第七步,釋放資源,每一個sqlSession就是一個連線
sqlSession.close();
}
}
以上完成的需求就是根據id查詢使用者資訊。一般來講工廠物件一般在實際開發是單例的,並不需要頻繁地建立,故getUserById()方法可優化為:
public class MybatisTest {
private SqlSessionFactory sqlSessionFactory = null; // 工廠物件一般在我們的系統中是單例的
@Before
public void init() throws IOException {
// 第一步,建立SqlSessionFactoryBuilder物件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 第二步,載入配置檔案
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 第三步,建立SqlSessionFactory物件
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}
@Test
public void getUserById() throws IOException {
// 第四步,建立SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 第五步,使用SqlSession物件執行查詢,得到User物件
// 第一個引數:執行查詢的StatementId
User user = sqlSession.selectOne("getUserById", 10);
// 第六步,列印結果
System.out.println(user);
// 第七步,釋放資源,每一個sqlSession就是一個連線
sqlSession.close();
}
}
根據使用者名稱稱模糊查詢使用者資訊列表
現在我就來完成第二個需求——根據使用者名稱稱模糊查詢使用者資訊列表。
一開始我在user.xml對映檔案中新增如下配置:
<select id="getUserByName" parameterType="string" resultType="cn.itheima.mybatis.po.User">
SELECT * FROM `user` WHERE username LIKE #{username}
</select>
如果查詢結果返回的是List集合,那麼resultType只需要設定為List集合中的一個元素的資料型別即可。
接著在MybatisTest單元測試類編寫如下單元測試方法:
@Test
public void getUserByName() {
// 建立一個SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 執行查詢
List<User> list = sqlSession.selectList("getUserByName", "%張%");
for (User user : list) {
System.out.println(user);
}
// 釋放資源
sqlSession.close();
}
執行以上方法,Eclipse控制檯列印如下:
實際開發中建議使用#{}
佔位符這種方式,因為這樣可以防止SQL注入。除了以上這種方式外,在此我還介紹第二種方式,即在user.xml對映檔案中新增如下配置:
<select id="getUserByName" parameterType="string" resultType="cn.itheima.mybatis.po.User">
SELECT * FROM `user` WHERE username LIKE '%${value}%'
</select>
${value}
表示使用引數將${value}
替換,做字串的拼接,${}
為字串拼接指令。注意:如果是取簡單資料型別的引數,括號中的值必須為value。
如此一來,MybatisTest單元測試類中的getUserByName()方法應修改為:
@Test
public void getUserByName() {
// 建立一個SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 執行查詢
List<User> list = sqlSession.selectList("getUserByName", "張");
for (User user : list) {
System.out.println(user);
}
// 釋放資源
sqlSession.close();
}
執行以上方法,Eclipse控制檯列印如下:
很明顯這種方式在實際開發中是不建議使用的,因為無法防止SQL注入。
總結
#{}和${}
#{}
:表示一個佔位符號,可以很好地去避免sql注入。其原理是將佔位符位置的整個引數和sql語句兩部分提交給資料庫,資料庫去執行sql語句,去表中匹配所有的記錄是否和整個引數是否一致。
#{}
要獲取輸入引數的值:
- 如果輸入引數是簡單資料型別,則
#{}
中可以寫value或其它名稱。 - 如果輸入引數是pojo物件型別,則
#{}
可通過OGNL方式去獲取,表示式就是屬性.屬性.屬性....
方式。
${}
表示一個sql拼接符號,其原理是在向資料庫發出sql之前去拼接好sql再提交給資料庫執行。
${}
要獲取輸入引數的值:
- 如果輸入引數是簡單資料型別,則
${}
中只能寫value。 - 如果輸入引數是pojo物件型別,則
${}
可通過OGNL方式去獲取,表示式就是屬性.屬性.屬性....
方式。
一般情況下建議使用#{}
,特殊情況下必須要用${}
,比如:
- 動態拼接sql中動態組成排序欄位,要通過
${}
將排序欄位傳入sql中。 - 動態拼接sql中動態組成表名,要通過
${}
將表名傳入sql中。
parameterType和resultType
parameterType:指定輸入引數型別,mybatis通過ognl從輸入物件中獲取引數值拼接在sql中。
resultType:指定輸出結果型別,mybatis將sql查詢結果的一行記錄資料對映為resultType指定型別的物件。
selectOne()和selectList()方法
selectOne查詢一條記錄,如果使用selectOne查詢多條記錄則丟擲異常:
selectList可以查詢一條或多條記錄。
新增使用者
現在我就來完成第三個需求——新增使用者。首先在user.xml對映檔案中新增如下配置:
<insert id="insertUser" parameterType="cn.itheima.mybatis.po.User">
INSERT INTO `user` (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>
如果輸入引數是pojo,那麼#{}
中的名稱就是pojo類中的屬性(用到了物件圖導航的思想),而不能隨便寫了。
然後試著在MybatisTest單元測試類編寫如下單元測試方法:
@Test
public void addUser() {
// 建立一個SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 建立User物件
User user = new User();
user.setUsername("小喬");
user.setBirthday(new Date());
user.setSex("2");
user.setAddress("上海");
// 插入使用者
sqlSession.insert("insertUser", user);
// 釋放資源
sqlSession.close();
}
執行以上方法,發現Eclipse控制檯中列印:
雖然發出了sql語句,但是事務並沒將其提交,而是回滾了。故User物件是無法插入到資料庫user表中的。所以addUser()單元測試方法應修改為:
@Test
public void addUser() {
// 建立一個SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 建立User物件
User user = new User();
user.setUsername("小喬");
user.setBirthday(new Date());
user.setSex("2");
user.setAddress("上海");
// 插入使用者
sqlSession.insert("insertUser", user);
// 提交事務
sqlSession.commit();
// 釋放資源
sqlSession.close();
}
執行以上方法,Eclipse控制檯會列印:
事務已提交,可發現數據庫user表中插入一條記錄。
MySQL自增主鍵返回
現在有這樣一個需求:想要得到MySQL資料庫給我們生成的主鍵id,即獲取主鍵。那如何實現這個需求呢?這裡要用到MySQL資料庫中的一個函式:
- LAST_INSERT_ID():返回auto_increment自增列新記錄id值。該函式是在當前事務下取到你最後生成的id值,而我們應知道查詢操作是沒有開啟事務的,增刪改操作是需要開啟事務的。
如此一來,需要將user.xml對映檔案中的如下配置:
<insert id="insertUser" parameterType="cn.itheima.mybatis.po.User">
INSERT INTO `user` (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>
修改為(即新增selectKey實現將主鍵返回):
<insert id="insertUser" parameterType="cn.itheima.mybatis.po.User">
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO `user` (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>
- keyProperty:返回的主鍵儲存在pojo中的哪個屬性(即其對應pojo的主鍵屬性)。獲取主鍵,實際上是將主鍵取出來之後封裝到了pojo的主鍵屬性當中。
- resultType:返回的主鍵是什麼型別(即其對應pojo的主鍵的資料型別)。
- order:selectKey的執行順序,是相對於insert語句來說的,由於mysql的自增原理,執行完insert語句之後才將主鍵生成,所以這裡selectKey的執行順序為AFTER。
最後將addUser()單元測試方法應修改為:
@Test
public void addUser() {
// 建立一個SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 建立User物件
User user = new User();
user.setUsername("大喬");
user.setBirthday(new Date());
user.setSex("2");
user.setAddress("上海");
// 插入使用者
sqlSession.insert("insertUser", user);
System.out.println(user.getId());
// 提交事務
sqlSession.commit();
// 釋放資源
sqlSession.close();
}
執行以上方法,Eclipse控制檯會列印:
MySql使用uuid實現主鍵
使用uuid實現主鍵,需要增加通過select uuid()
語句得到uuid值作為主鍵。所以需要將user.xml對映檔案中id為insertUser的Statement改置為:
<insert id="insertUser" parameterType="cn.itheima.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>
因為是使用uuid做主鍵,所以應該先生成主鍵然後再插入資料,此時order屬性的值應是BEFORE。
刪除使用者
現在我就來完成第四個需求——刪除使用者。首先在user.xml對映檔案中新增如下配置:
<!-- 刪除使用者 -->
<delete id="deleteUser" parameterType="int">
DELETE FROM `user` WHERE id=#{id1}
</delete>
然後在MybatisTest單元測試類編寫如下單元測試方法:
@Test
public void deleteUser() {
// 建立一個SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 刪除使用者
sqlSession.delete("deleteUser", 29);
// 提交事務
sqlSession.commit();
// 釋放資源
sqlSession.close();
}
更新使用者
現在我就來完成第五個需求——修改使用者資訊。首先在user.xml對映檔案中新增如下配置:
<!-- 修改使用者資訊 -->
<update id="updateUser" parameterType="cn.itheima.mybatis.po.User">
UPDATE `user` set username=#{username} WHERE id=#{id}
</update>
然後在MybatisTest單元測試類編寫如下單元測試方法:
@Test
public void updateUser() {
// 建立一個SqlSession物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 建立一個User物件
User user = new User();
user.setUsername("張角");
user.setId(10);
// 更新使用者
sqlSession.update("updateUser", user);
// 提交事務
sqlSession.commit();
sqlSession.close();
}
MyBatis解決了JDBC程式設計的問題
還記得MyBatis框架的學習(一)——MyBatis介紹一文中使用JDBC程式設計所帶來的問題嗎?現在使用MyBatis這個框架就可以解決JDBC程式設計所帶來的這些問題。
- 資料庫連線建立、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用資料庫連線池可解決此問題。
解決:在SqlMapConfig.xml中配置資料連線池,使用連線池管理資料庫連線。 - Sql語句寫在程式碼中造成程式碼不易維護,實際應用sql變化的可能較大,sql變動需要改變java程式碼。
解決:將Sql語句配置在XXXXmapper.xml檔案中與java程式碼分離。 - 向sql語句傳引數麻煩,因為sql語句的where條件不一定,可能多也可能少,佔位符需要和引數一一對應。
解決: MyBatis自動將java物件對映至sql語句,通過statement中的parameterType定義輸入引數的型別。 - 對結果集解析麻煩,sql變化導致解析程式碼變化,且解析前需要遍歷,如果能將資料庫記錄封裝成pojo物件解析比較方便。
解決: MyBatis自動將sql執行結果對映至java物件,通過statement中的resultType定義輸出結果的型別。
MyBatis與Hibernate的不同之處
MyBatis和Hibernate不同,它不完全是一個ORM框架,因為MyBatis需要程式設計師自己編寫Sql語句,不過MyBatis可以通過XML或註解方式靈活配置要執行的sql語句,並將java物件和sql語句對映生成最終執行的sql,最後將sql執行的結果再對映生成java物件。
MyBatis學習門檻低,簡單易學,程式設計師直接編寫原生態sql,可嚴格控制sql執行效能,靈活度高,非常適合對關係資料模型要求不高的軟體開發,例如網際網路軟體、企業運營類軟體等,因為這類軟體需求變化頻繁,一但需求變化要求成果輸出迅速。但是靈活的前提是MyBatis無法做到資料庫無關性,如果需要實現支援多種資料庫的軟體則需要自定義多套sql對映檔案,工作量大。
Hibernate物件/關係對映能力強,資料庫無關性好,對於關係模型要求高的軟體(例如需求固定的定製化軟體)如果用Hibernate開發可以節省很多程式碼,提高效率。但是Hibernate的學習門檻高,要精通門檻更高,而且怎麼設計O/R對映,在效能和物件模型之間如何權衡,以及怎樣用好Hibernate需要具有很強的經驗和能力才行。
總之,按照使用者的需求在有限的資源環境下只要能做出維護性、擴充套件性良好的軟體架構都是好架構,所以框架只有適合才是最好。
我個人總結的MyBatis與Hibernate這兩個框架的不同之處:
- MyBatis學習成本低,入門門檻低。MyBatis需要程式設計師自己寫sql,對sql修改和優化就比較靈活。MyBatis是不完全的ORM框架,MyBatis需要程式設計師編寫sql,但是MyBatis也存在對映(輸入對映、輸出對映)適用場景:需求變化較快的專案開發,比如網際網路專案、電商。
- Hibernate學習成本高,入門門檻高,Hibernate是ORM框架,不需要程式設計師編寫sql,自動根據物件對映生成sql。適用場景:需求固定的中小型專案,如OA系統、ERP系統。
企業在技術選型上應考慮各個技術框架的特點去進行選型,企業要根據人力、物力等資源去衡量,以節省成本利潤最大化為目標進行選型。
至此,我們就算入門MyBatis了。讀者如需檢視原始碼,可參考!