近來有空,鑒于工作經常會使用到mybatis。是以想将這個架構研究的更加徹底一些!
【MyBatis源碼分析】整個文章結構會在每一部分源碼分析的開頭列出要分析的源碼的執行個體,比如:
- 分析加載解析XML配置流程,就會先寫相關節點的xml配置及解析的源碼展示。
- 分析mybatis四大對象流程,就會先寫針對單個對象展示源碼。
整個系列文章,在本文中會一次性地将所有的代碼示例寫完,之後就針對這些代碼一部分一部分進行分析,探究Mybatis原理。
一、 建表
這裡準備一段SQL:
drop table if exists mail;
create table mail
(
id int auto_increment not null comment '主鍵id',
create_time datetime not null comment '建立時間',
modify_time timestamp not null comment '修改時間',
web_id int not null comment '站點id,1表示新浪,2表示QQ,3表示搜狐,4表示火狐',
mail varchar(50) not null comment '郵箱名',
use_for varchar(30) comment '郵箱用途',
primary key(id),
index use_for(use_for),
unique index web_id_mail(web_id, mail)
)charset=utf8 engine=innodb comment='郵箱表';
很多人可能有不止一個郵箱,新浪的、騰訊的、搜狐的,每個郵箱可能又有不一樣的用途,這裡就拿郵箱做一個例子。
建立每張表的時候應當注意唯一限制,像這裡,一個網站下的郵箱一定是唯一的,不可能在新浪下同時存在兩個名為"[email protected]"的郵箱名,是以對web_id+mail做唯一索引。
二、 建實體類
在SQL層面不同的詞語使用"_"分割,在Java層面不同的詞語則使用駝峰命名法:
- 對于類名/接口名/枚舉類,使用首字母大寫的駝峰命名法
- 對于字段,使用首字母小寫的駝峰命名法
mail表建立實體類:
public class Mail {
/**
* 主鍵id
*/
private long id;
/**
* 建立時間
*/
private Date createTime;
/**
* 修改時間
*/
private Date modifyTime;
/**
* 網站id,1表示新浪,2表示QQ,3表示搜狐,4表示火狐
*/
private int webId;
/**
* 郵箱
*/
private String mail;
/**
* 用途
*/
private String useFor;
public Mail() {
}
public Mail(int webId, String mail, String useFor) {
this.webId = webId;
this.mail = mail;
this.useFor = useFor;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getModifyTime() {
return modifyTime;
}
public void setModifyTime(Date modifyTime) {
this.modifyTime = modifyTime;
}
public int getWebId() {
return webId;
}
public void setWebId(int webId) {
this.webId = webId;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public String getUseFor() {
return useFor;
}
public void setUseFor(String useFor) {
this.useFor = useFor;
}
@Override
public String toString() {
return "MailDO [id=" + id + ", createTime=" + createTime + ", modifyTime=" + modifyTime + ", webId=" + webId + ", mail=" + mail + ", useFor="
+ useFor + "]";
}
}
注意實體類一定要重寫toStirng()方法,便于定位問題。
三、 建資料通路層
對于資料通路層通常有如下約定:
- 資料通路層使用Dao命名,它定義了對表的基本增删改查操作
- 資料通路層之上使用Service命名,它的作用是對于資料庫的多操作進行組合,比如先查再删、先删再增、先改再查再删等等,這些操作不會放在Dao層面去操作,而會放在Service層面去進行組合
那麼,首先定義一個MailDao,我定義增删改查五個方法,其中查詢兩個方法,一個查單個,一個查清單:
public interface MailDao {
/**
* 插入一條郵箱資訊
*/
public long insertMail(Mail mail);
/**
* 删除一條郵箱資訊
*/
public int deleteMail(long id);
/**
* 更新一條郵箱資訊
*/
public int updateMail(Mail mail);
/**
* 查詢郵箱清單
*/
public List<Mail> selectMailList();
/**
* 根據主鍵id查詢一條郵箱資訊
*/
public Mail selectMailById(long id);
}
接着是Dao的實作類,通常以"Impl"結尾,"Impl"是關鍵字"Implements"的縮寫,表示接口實作類的意思。MailDao的實作類就命名為MailDaoImpl了,代碼為:
public class MailDaoImpl implements MailDao {
private static final String NAME_SPACE = "MailMapper.";
private static SqlSessionFactory ssf;
private static Reader reader;
static {
try {
reader = Resources.getResourceAsReader("mybatis/config.xml");
ssf = new SqlSessionFactoryBuilder().build(reader);
}
catch (IOException e) {
e.printStackTrace();
}
}
@Override
public long insertMail(Mail mail) {
SqlSession ss = ssf.openSession();
try {
int rows = ss.insert(NAME_SPACE + "insertMail", mail);
ss.commit();
if (rows > 0) {
return mail.getId();
}
return 0;
} catch (Exception e) {
ss.rollback();
return 0;
} finally {
ss.close();
}
}
@Override
public int deleteMail(long id) {
SqlSession ss = ssf.openSession();
try {
int rows = ss.delete(NAME_SPACE + "deleteMail", id);
ss.commit();
return rows;
} catch (Exception e) {
ss.rollback();
return 0;
} finally {
ss.close();
}
}
@Override
public int updateMail(Mail mail) {
SqlSession ss = ssf.openSession();
try {
int rows = ss.update(NAME_SPACE + "updateMail", mail);
ss.commit();
return rows;
} catch (Exception e) {
ss.rollback();
return 0;
} finally {
ss.close();
}
}
@Override
public List<Mail> selectMailList() {
SqlSession ss = ssf.openSession();
try {
return ss.selectList(NAME_SPACE + "selectMailList");
} finally {
ss.close();
}
}
@Override
public Mail selectMailById(long id) {
SqlSession ss = ssf.openSession();
try {
return ss.selectOne(NAME_SPACE + "selectMailById", id);
} finally {
ss.close();
}
}
}
四、 配置相關環境及SQL的XML檔案
MyBatis的配置檔案有兩個,一個是環境的配置config.xml,一個是具體SQL的編寫mail.xml。首先看一下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>
<properties resource="properties/db.properties" />
<settings>
<setting name="logImpl" value="LOG4J" />
<!-- 自定義配置LOG4J為輸出日志 預設沒有設定-->
<setting name="cacheEnabled" value="true" />
<!-- 對在此配置檔案下的所有cache 進行全局性開/關設定。 預設true -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 全局性設定懶加載。如果設為‘false’,則所有相關聯的都會被初始化加載。 預設true -->
<setting name="multipleResultSetsEnabled" value="true" />
<!-- 允許和不允許單條語句傳回多個資料集(取決于驅動需求)。 預設true -->
<setting name="useColumnLabel" value="true" />
<!-- 使用列标簽代替列名稱。不同的驅動器有不同的作法。參考一下驅動器文檔,或者用這兩個不同的選項進行測試一下。 預設true-->
<setting name="useGeneratedKeys" value="true" />
<!-- 允許JDBC 生成主鍵。需要驅動器支援。如果設為了true,這個設定将強制使用被生成的主鍵,有一些驅動器不相容不過仍然可以執行。 預設true-->
<setting name="autoMappingBehavior" value="PARTIAL" />
<!-- 指定MyBatis 是否并且如何來自動映射資料表字段與對象的屬性。PARTIAL将隻自動映射簡單的,沒有嵌套的結果。FULL 将自動映射所有複雜的結果。預設PARTIAL -->
<setting name="defaultExecutorType" value="SIMPLE" />
<!-- 配置和設定執行器,SIMPLE 執行器執行其它語句。REUSE 執行器可能重複使用prepared statements 語句,BATCH執行器可以重複執行語句和批量更新。 預設SIMPLE-->
<setting name="defaultStatementTimeout" value="25000" />
<!-- 設定一個時限,以決定讓驅動器等待資料庫回應的多長時間為逾時 預設沒有設定 -->
<setting name="safeRowBoundsEnabled" value="false" />
<!-- 允許在嵌套語句中使用RowBounds。如果允許,設定為false。 預設false-->
<setting name="mapUnderscoreToCamelCase" value="false" />
<!-- 使資料庫列名稱A_COLUMN自動映射到駱駝類型Java屬性名稱aColumn。 -->
<setting name="localCacheScope" value="SESSION" />
<!-- MyBatis使用本地緩存來防止循環引用,并加速重複的嵌套查詢。預設情況下(SESSION)會話中執行的所有查詢都被緩存。如果localCacheScope = STATEMENT本地會話将僅用于語句執行,則不會在對同一個SqlSession的兩個不同調用之間共享資料。 預設SESSION -->
<setting name="jdbcTypeForNull" value="OTHER" />
<!-- 當沒有為參數提供特定的JDBC類型時,指定空值的JDBC類型。一些驅動程式需要指定列JDBC類型,但其他驅動程式則使用NULL,VARCHAR或OTHER等通用值。 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode ,toString" />
<!-- 指定哪個對象的方法觸發延遲加載 用逗号分隔的方法名稱清單 -->
</settings>
<typeAliases>
<typeAlias type="model.Mail"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driveClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${userName}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mail.xml"/>
</mappers>
</configuration>
接着是編寫SQL語句的mail.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="MailMapper">
<resultMap type="Mail" id="MailResultMap">
<result column="id" property="id" />
<result column="create_time" property="createTime" />
<result column="modify_time" property="modifyTime" />
<result column="web_id" property="webId" />
<result column="mail" property="mail" />
<result column="use_for" property="useFor" />
</resultMap>
<sql id="fields">
id, create_time, modify_time, web_id, mail, use_for
</sql>
<sql id="fields_value">
null, now(), now(), #{webId}, #{mail}, #{useFor}
</sql>
<insert id="insertMail" parameterType="Mail" useGeneratedKeys="true" keyProperty="id">
insert into mail(
<include refid="fields" />
) values(
<include refid="fields_value" />
);
</insert>
<delete id="deleteMail" parameterType="java.lang.Long">
delete from mail where id = #{id};
</delete>
<update id="updateMail" parameterType="Mail">
update mail
<set>
<if test="web_id != 0">
web_id = #{webId}
</if>
<if test="mail != null">
mail = #{mail}
</if>
<if test="use_for != null">
use_for = #{useFor}
</if>
</set>
where id = #{id};
</update>
<select id="selectMailList" resultMap="MailResultMap">
select <include refid="fields" /> from mail;
</select>
<select id="selectMailById" resultMap="MailResultMap" parameterType="java.lang.Long">
select <include refid="fields" /> from mail where id = #{id};
</select>
</mapper>
這個mail.xml我盡量寫得全一點,這樣後面分析的時候都會有代碼示例,mail.xml中包括:
- resultMap
- <sql>标簽
- 插入主鍵傳回主鍵id
- 動态sql
五、 建立單元測試類
軟體的正确性離不開良好的測試,通常測試有兩種方式:
- 寫main函數,這種方式我基本不使用,除非是測試一個很小的功能點比如Math.round這種,這種代碼寫完我也會直接删除的,不會留着送出到代碼庫上
- 使用單元測試工具比如junit,這是我常用的方式
接着看一下單元測試代碼:
public class TestMyBatis {
private static MailDao mailDao;
static {
mailDao = new MailDaoImpl();
}
@Test
public void testInsert() {
Mail mail1 = new Mail(1, "[email protected]", "個人使用");
Mail mail2 = new Mail(2, "[email protected]", "企業使用");
Mail mail3 = new Mail(3, "[email protected]", "新增賬號使用");
System.out.println(mailDao.insertMail(mail1));
System.out.println(mailDao.insertMail(mail2));
System.out.println(mailDao.insertMail(mail3));
}
@Test
public void testDelete() {
System.out.println(mailDao.deleteMail(1));
}
@Test
public void testUpdate() {
Mail mail = new Mail(2, "[email protected]", "個人使用");
mail.setId(2);
System.out.println(mailDao.updateMail(mail));
System.out.println(mailDao.selectMailById(2));
}
@Test
public void testSelectOne() {
System.out.println(mailDao.selectMailById(2));
}
@Test
public void testSelectList() {
List<Mail> mailList = mailDao.selectMailList();
if (mailList != null && mailList.size() != 0) {
for (Mail mail : mailList) {
System.out.println(mail);
}
}
}
}
正确的情況下,應當五個方法跑出來全部是綠色的進度條。
當然,單元測試也可以單獨跑每一個,Eclipse/MyEclipse/idea都支援的!
現在簡單的一個通路資料源的demo就出來,接下來我們就通過這個demo去debug相關mybatis的源碼!