MyBatis實戰之初步
關於MyBatis與Hibernate及其JDBC的比較,大家可以參考我的這篇文章:MyBatis+Hibernate+JDBC對比分析
如果覺得這個還不夠系統全面,可以自行Google或者百度。
用了MyBatis有兩年了,後來覺得不好用有一定的侷限性換成了MyBatis-Plus,關於MyBatis-Plus實戰系列,可以參考這個連結:https://www.cnblogs.com/youcong/category/1213059.html
另外話說回來了,為什麼我又要回到MyBatis?
原因有這麼幾個方面?
第一、MyBatis-Plus是MyBatis的升級版,MyBatis有的,MyBatis-Plus都有,MyBatis沒有的,MyBatis-Plus也有,從這個角度來看,MyBatis-Plus是從MyBatis中衍生出來的,那麼它們的原始碼可以說有一大半是一致的,只是有少部分不同,所以如果以後MyBatis-Plus不能滿足我的需求,我可以自行改造,雖說碼雲或者Github上有不少現成的輪子可以用,但是我個人覺得,如果進入一家比較大的公司,不僅僅要知道如何投機取巧(善用現有的輪子,同時也要具備製造輪子的能力,你可以理解為深入瞭解原始碼,並熟悉其中的設計模式);
第二、如第一條所說的那樣,我看過很多開源專案,有的理解透徹MyBatis,知道MyBatis生成的程式碼有很多侷限性,就例如它的逆向工程可以避免重複編寫CRUD之類的程式碼,但是生成的XML太繁瑣,於是就有了MyBatis-Plus,但是MyBatis-Plus也並不是沒有缺點的,比如它只能針對單表,而不能多表連線,也正是因為這個侷限性,Jeesite的創造者才決定自行改造MyBatis,讓其符合自身的需要,關於Jeesite簡化MyBatis的博文,大家可以參考這個連結:https://my.oschina.net/thinkgem/blog/1503611;
第三、目前主流還是MyBatis,網際網路專案大多都是MyBatis,有的公司也有自己封裝的ORM框架,但是本質上基本要麼是對Hibernate進行改造,要麼就是對MyBatis改造,下面我貼一下關於MyBatis的圖給大家看看:
從這張圖,大家可以知道MyBatis和MyBatis-Plus在開源專案都彼此佔用;
有人說,現在部落格上有很多關於MyBatis的,你寫這個是不是有點多餘的,我對此的回答是,不多餘。就好比讀書,每個人讀一本書一遍或者兩遍及其以上,感觸肯定是不一樣的。很多東西當初的時候不知道是什麼意思,回過頭來,你會發現,你可以發現一些不一樣的東西,這個東西,你可以理解為舉一反三或是融會貫通。
下面進入正題,關於MyBatis的學習,我當初是參考孤傲蒼狼的部落格。今天在寫這篇文章時,我查了官網,也搜尋相關的中文教程,在w3cschool發現了關於MyBatis,但是我覺得寫的不好,對於初學者和進階者而言,簡直就是亂七八糟,我知道也許這樣評價會引起一些人諷刺,但是這是事實,大家可以自己看,我覺得初學者或者進階者,特別是進階者可以回過頭再看看官網。
準備環境:Window10/8/7+JDK8/7/6或以上+MySQL
專案結構如下:
SQL指令碼:
REATE TABLE `user` (
`user_id` int(8) NOT NULL AUTO_INCREMENT COMMENT '使用者主鍵',
`login_code` varchar(20) NOT NULL COMMENT '使用者編碼(登入賬戶) 手機號 郵箱號',
`user_name` varchar(20) NOT NULL COMMENT '使用者名稱',
`password` varchar(40) NOT NULL COMMENT '密碼',
`sex` int(2) NOT NULL COMMENT '性別',
`identity_card` varchar(20) DEFAULT NULL COMMENT '身份證',
`create_time` datetime NOT NULL COMMENT '建立時間',
`create_by` varchar(10) NOT NULL COMMENT '建立人',
`update_time` datetime NOT NULL COMMENT '更新時間',
`update_by` varchar(10) NOT NULL COMMENT '更新人',
`status` int(2) NOT NULL DEFAULT '0' COMMENT '狀態:0註冊新使用者 1郵件認證使用者 2管理員 3黑名單',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
一、匯入依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.youcong.mybatis</groupId> <artifactId>mybatis</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
二、編寫實體類
package com.blog.entity;
import java.io.Serializable;
import java.util.Date;
import java.io.Serializable;
public class User{
private static final long serialVersionUID = 1L;
/**
* 使用者主鍵
*/
private Integer userId;
/**
* 使用者編碼(登入賬戶) 手機號 郵箱號
*/
private String loginCode;
/**
* 使用者名稱
*/
private String userName;
/**
* 密碼
*/
private String password;
/**
* 性別
*/
private Integer sex;
/**
* 身份證
*/
private String identityCard;
/**
* 建立時間
*/
private Date createTime;
/**
* 建立人
*/
private String createBy;
/**
* 更新時間
*/
private Date updateTime;
/**
* 更新人
*/
private String updateBy;
/**
* 狀態:0註冊新使用者 1郵件認證使用者 2管理員 3黑名單
*/
private Integer status;
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getLoginCode() {
return loginCode;
}
public void setLoginCode(String loginCode) {
this.loginCode = loginCode;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public String getIdentityCard() {
return identityCard;
}
public void setIdentityCard(String identityCard) {
this.identityCard = identityCard;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
三、編寫資料訪問層及其對應的XML檔案
package com.blog.dao;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.blog.entity.User;
public interface UserDao {
@Select("select * from `user` where user_id=#{userId}")
@Results(@Result(property="userName",column="user_name"))
User selectOne(int userId);
}
@Select是MyBatis的註解寫法,當欄位名和屬性名一致時,可以直接@Select不需要@Results,@Results的作用就是因為實體屬性與欄位不一致,為了獲取查詢結果,必須要這樣。
當然了你也可以不寫註解,直接在對應的UserDao.xml寫,如果是UserDao.xml裡面寫的話,就會變成這樣
package com.blog.dao; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import com.blog.entity.User; public interface UserDao { User selectOne(int userId); }
需要在UserDao.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="com.blog.dao.UserDao"> <!-- 通用查詢對映結果 --> <resultMap id="BaseResultMap" type="com.blog.entity.User"> <id column="user_id" property="userId" /> <result column="login_code" property="loginCode" /> <result column="user_name" property="userName" /> <result column="password" property="password" /> <result column="sex" property="sex" /> <result column="identity_card" property="identityCard" /> <result column="create_time" property="createTime" /> <result column="create_by" property="createBy" /> <result column="update_time" property="updateTime" /> <result column="update_by" property="updateBy" /> <result column="status" property="status" /> </resultMap> <!-- 通用查詢結果列 -->
<sql id="Base_Column_List">
user_id, login_code, user_name, password, sex, identity_card, create_time, create_by, update_time, update_by, status
</sql>
<select id="selectOne" resultMap="BaseResultMap"> select * from `user` where user_id=#{userId} </select> </mapper>
除了<select>標籤之外,還有<insert>、<update>、<delete>,它們的作用分別是查、增、更、刪。當然了,還有這裡面可以<sql>,這個<sql>的作用就是為了複用重複列,省的重複編寫程式碼冗餘。如果要引用,寫成這樣既可
<!-- 通用查詢結果列 --> <sql id="Base_Column_List"> user_id, login_code, user_name, password, sex, identity_card, create_time, create_by, update_time, update_by, status </sql> <select id="selectOne" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from `user` where user_id=#{userId} </select>
MyBatis常用的兩種寫法,XML方式和註解方式,但是無論你採用哪種,對應的XML檔案必須要存在。
其實<resultMap id="BaseResultMap" type="com.blog.entity.User"> 可以變成 <resultMap id="BaseResultMap" type="User">
前提必須要在mybatis-config.xml配置
<typeAliases> <typeAlias type="com.blog.entity.User" alias="User"/> </typeAliases>
但是有人覺得,如果我的類有很多,豈不是要配置很多,太麻煩了,別怕,MyBatis已經為你想到了
<typeAliases> <package name="com.blog.entity"/> </typeAliases>
這樣配置就解決了重複配置的麻煩
其實除了這樣,如果你想標新立異點,還可以再實體最上面加上@Alias註解
這就是MyBatis的別名三種方式/策略。
四、編寫mybatis-config.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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/blog_test"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mybatis/mapping/UserDao.xml"/> </mappers> </configuration>
mybatis-config.xml這個配置沒多大用,坦白說。
因為後期與Spring整合由Spring來管理資料庫連線池物件。
不過還是要稍微講講,以讓讀者達到開卷有益的目的或者是讓沒有學過的或者已經學過但是不知道的漲漲見識。
<environment id="development">表示我們預設使用development配置環境
<transactionManager type="JDBC"/> 採用JDBC的事務管理模式
<dataSource type="POOLED"> 資料庫連線資訊
<mappers> 對映器 通常一般對映XML檔案
五、編寫工具類
package com.blog.config; import java.io.IOException; import java.io.InputStream; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class SqlSessionFactoryUtils { private static SqlSessionFactory sqlSessionFactory = null; public static SqlSessionFactory getSqlSessionFactory() { InputStream is = null; if(sqlSessionFactory==null) { String resource = "mybatis/mybatis-config.xml"; try { sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource)); return sqlSessionFactory; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return sqlSessionFactory; } }
說到上述程式碼,順便談談MyBatis的核心元件。它的核心元件主要有這麼幾個,如下所示:
SqlSessionFactoryBuilder:它會根據配置資訊或者程式碼來生成SqlSessionFactory;
SqlSessionFactory:依賴工廠生成SqlSession;
SqlSession:是一個即可傳送SQL執行返回結果,也可以獲取Mapper介面;
Sql Mapper:它是MyBatis的新設計元件,它是由一個Java介面和XML檔案(或註解)構建,需要給出對應的SQL和對映規則。它負責傳送SQL去執行並返回結果。
用一張圖來表示它們之間的關係,如圖:
它們的生命週期在此稍微說一下:
(1)SqlSessionFacotoryBuilder
SqlSessionFacotoryBuilder是利用XML和Java程式碼獲得資源來構建SqlSessionFactory的,通過它可以構建多個SqlSessionFactory,它的作用就是一個構建器,一旦我們構建了SqlSessionFactory,它的作用就完結,它就失去存在的意義。這時我們應該毫不猶豫的廢棄它,將它回收。所以它的生命週期只存在方法區域性,它的作用就是生成SqlSessionFactory。
(2)SqlSessionFactory的作用是建立SqlSession,而SqlSession就是一個會話,相當於JDBC中的Connection物件。每次應用程式需要訪問資料庫,我們就要通過SqlSessionFactory建立SqlSession,所以SqlSessionFactory應該在MyBatis應用的整個生命週期中。而如果我們多次建立同一個資料庫的SqlSessionFactory,則每次建立SqlSessionFactory會開啟更多的資料庫連線資源,那麼連線資源就很快會被耗盡。因此SqlSessionFactory的責任是唯一的,它的責任就是建立SqlSession,所以我們果斷採用單例模式。如果採用多例,那麼它對資料庫連線的消耗是很大的,不利於我們統一管理。所以正確的做法是使得每個資料庫只對應一個SqlSessionFactory,管理好資料庫資源分配,避免過多的Connection被消耗。
(3)SqlSession
SqlSession是一個會話,相當於JDBC的一個Connection物件,它的生命週期應該是在請求資料庫處理事務的過程中。它是一個執行緒不安全的物件,在涉及多執行緒的時候我們需要特別當心,操作資料庫需要注意其隔離級別,資料庫鎖等高階特性。此外,每次建立的SqlSession都必須及時關閉它,它長期存在就會使資料庫連線池的活動資源減少,對系統性能的影響很大。
(4)Mapper
Mapper是一個介面,而沒有任何實現類,它的作用是傳送SQL,然後返回我們需要的結果,或者執行SQL從而修改資料庫的資料,因此它應該在一個SqlSession事務方法之內,是一個方法級別的東西。它就如同JDBC中的一條SQL語句的執行,它的最大範圍和SqlSession是相同的。儘管我們想一直儲存著Mapper,但是你會發現它很難控制,所以儘量在一個SqlSession事務的方法中使用它們,然後廢棄掉。
MyBatis元件生命週期,如圖:
六、測試
package mybatis; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import com.blog.config.SqlSessionFactoryUtils; import com.blog.dao.UserDao; import com.blog.entity.User; public class MyBatisTest { @Test public void testName() throws Exception { SqlSession sqlSession = null; sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.selectOne(1); System.out.println(user.getUserName()); } }
小結:
通過這篇文章,你可以達到能夠知道怎麼使用MyBatis和了解熟悉它的相關執行原理和對應的生命週期,希望能給大家帶來有益的啟發和收穫。
本文參考:
《深入淺出MyBatis技術原理和實戰》
MyBatis官網:http://www.mybatis.org/mybatis-3/