TKmybatis的框架介紹和原理分析及Mybatis新特性
tkmybatis是在mybatis框架的基礎上提供了很多工具,讓開發更加高效,下面來看看這個框架的基本使用,後面會對相關原始碼進行分析,感興趣的同學可以看一下,挺不錯的一個工具
實現對員工表的增刪改查的程式碼
java的dao層介面
public interface WorkerMapper extends Mapper<Worker> {
}
- 1
- 2
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.jjs.kaiwen.dao.WorkerMapper">
<resultMap id="BaseResultMap" type="com.jjs.kaiwen.model.Worker">
<!--
WARNING - @mbggenerated
-->
<id column="id" jdbcType="INTEGER" property="id" />
<result column="worker_id" jdbcType="VARCHAR" property="workerId" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="org_id" jdbcType="INTEGER" property="orgId" />
<result column="status" jdbcType="VARCHAR" property="status" />
<result column="role_id" property="roleId" jdbcType="INTEGER" />
</resultMap>
</mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
實體物件
@Table(name = "worker")
public class Worker {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "worker_id")
private String workerId;
private String name;
@Column(name = "org_id")
private Integer orgId;
private String status;
@Column(name = "role_id")
private Integer roleId;
// getters and setters ...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
以上就是實現對Worker進行增刪改查的所有程式碼,包括選擇性更新、插入、刪除等,所有的方法列表如下
以後對錶欄位的新增或修改只需要更改實體物件的註解,不需要修改xml對映檔案,如將worker_id改成worker_no
@Column(name = "worker_no")
private String workerNo;
- 1
- 2
資料來源的配置,只需要將org.mybatis.spring.mapper.MapperScannerConfigurer改成tk.mybatis.spring.mapper.MapperScannerConfigurer,然後加一個屬性
,也可不加,因為框架提供了預設實現
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.jjs.zanbi.dao" />
<property name="properties">
<value>
mappers=tk.mybatis.mapper.common.Mapper
</value>
</property>
</bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
用這個庫之後寫程式碼感覺在飛…….如果只是簡單的瞭解此框架到這裡就可以了,下面是對框架實現原理的分析
原理的簡單分析
此框架為我們實現這些功能所有的改動都在Mapper層面,所有的Mapper都繼承了tk.mybatis.mapper.common.Mapper
public interface WorkerMapper extends Mapper<Worker> {}
- 1
Mapper介面的宣告如下,可以看到Mapper介面實現了所有常用的方法
public interface Mapper<T> extends
BaseMapper<T>,
ExampleMapper<T>,
RowBoundsMapper<T>,
Marker {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
看一下完整的UML圖,太大了,可以用新視窗開啟,放大之後再看
這裡選擇一個介面:SelectOneMapper介面,對於原始碼進行簡單分析,此介面宣告如下:
public interface SelectOneMapper<T> {
/**
* 根據實體中的屬性進行查詢,只能有一個返回值,有多個結果是丟擲異常,查詢條件使用等號
*
* @param record
* @return
*/
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
T selectOne(T record);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
@SelectProvider是mybatis3之後提供的,用於靈活的設定sql來源,這裡設定了服務提供類和方法,但這個庫並沒有直接用method指定的方法來返回sql,而是在執行時進行解析的,程式碼如下
public class BaseSelectProvider extends MapperTemplate {
public String selectOne(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
//修改返回值型別為實體型別
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
return sql.toString();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
到這裡我們就大概知道了這個庫為我們提供便利的原理了,總的來說就是這個庫幫我們提供了對錶的基本操作的sql,幫我們省了很多工作量,而且維護起來也很方便,否則我們的xml檔案動不動就幾百行甚至上千行
對原始碼的探索不能到這裡停止,最起碼要分析到與另一個框架的整合點
我們知道,mybatis的mapper介面是在啟動的時候被框架以JdkProxy的形式封裝了的,具體對應的類是MapperFactoryBean,這個類中有一個checkDaoConfig()方法,是從父類繼承並重寫了該方法,繼承結構如下
MapperFactoryBean -> SqlSessionDaoSupport -> DaoSupport
- 1
這裡的DaoSupport就是spring提供的Dao的抽象,程式碼如下
public abstract class DaoSupport implements InitializingBean {
// spring 完成屬性設定後會呼叫此方法
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// 這裡提供了介面供子類去實現
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
protected abstract void checkDaoConfig() throws IllegalArgumentException;
protected void initDao() throws Exception {
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
框架自定義的MapperFactoryBean重寫了checkDaoConfig()方法,完成對所有sql語句的設定,程式碼如下
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
//通用Mapper
if (mapperHelper.isExtendCommonMapper(getObjectType())) {
//這裡去處理該類所對應的MappedStatement,封裝在helper類中處理
mapperHelper.processConfiguration(getSqlSession().getConfiguration(), getObjectType());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
MapperHelper的processConfiguration方法如下
public void processConfiguration(Configuration configuration, Class<?> mapperInterface) {
String prefix;
if (mapperInterface != null) {
prefix = mapperInterface.getCanonicalName();
} else {
prefix = "";
}
for (Object object : new ArrayList<Object>(configuration.getMappedStatements())) {
if (object instanceof MappedStatement) {
MappedStatement ms = (MappedStatement) object;
//檢查這個MappedStatement是否屬於此對映物件
if (ms.getId().startsWith(prefix) && isMapperMethod(ms.getId())) {
if (ms.getSqlSource() instanceof ProviderSqlSource) {
//去設定該statement的sql語句
setSqlSource(ms);
}
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
設定sql的邏輯,提供了幾種不同型別的sqlsource
public void setSqlSource(MappedStatement ms) throws Exception {
if (this.mapperClass == getMapperClass(ms.getId())) {
throw new RuntimeException("請不要配置或掃描通用Mapper介面類:" + this.mapperClass);
}
Method method = methodMap.get(getMethodName(ms));
try {
//第一種,直接操作ms,不需要返回值
if (method.getReturnType() == Void.TYPE) {
method.invoke(this, ms);
}
//第二種,返回SqlNode
else if (SqlNode.class.isAssignableFrom(method.getReturnType())) {
SqlNode sqlNode = (SqlNode) method.invoke(this, ms);
DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode);
setSqlSource(ms, dynamicSqlSource);
}
//第三種,返回xml形式的sql字串
else if (String.class.equals(method.getReturnType())) {
String xmlSql = (String) method.invoke(this, ms);
SqlSource sqlSource = createSqlSource(ms, xmlSql);
//替換原有的SqlSource
setSqlSource(ms, sqlSource);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
到這裡整個sql的獲取流程就分析完了,本人用這個庫寫過一個小專案,確實節省了開發的工作量,而且DAO層的結構更加清晰簡潔了
關於mybatis新特性
從3.4.0開始,mybatis提供對外部表的alias引用方法,多表聯合查詢就方便多了,我們先看原始的方式是怎樣做的
select a.id,a.name,b.bid,b.bname .....
from user a
left join room b
- 1
- 2
- 3
原始的方式是將所有的表字段列出來,再來看用新特性怎樣做
select id="selectUsers" resultType="map">
select
<include refid="user_col_sql_id"><property name="alias" value="t1"/>,
<include refid="room_col_sql_id"><property name="alias" value="t2"/>
from user t1
left join room t2
</select>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這裡主要就是對基本的sql進行了複用,如果對錶進行了修改只要在原始的sql節點修改就可以了,就算是5個表的聯合查詢,sql也是清晰易懂,維護起來會更輕鬆
新版本的mybatis對於物件對映也提供了更友好的方式,直接使用外部的ResultMap再加上查詢語句中的別名就對映完成了
<resultMap id="workerResultMap" type="com.jjs.kaiwen.model.Worker" extends="BaseResultMap">
<association property="room" columnPrefix="b_" resultMap="com.jjs.kaiwen.dao.OrgMapper.BaseResultMap"/>
</resultMap>
- 1
- 2
- 3
更進一步
敏銳的程式設計師可能會提出問題,如當多表查詢的時候可能會存在欄位名稱相同的情況,這裡的解決方案是給include新增另一個屬性
<include refid="user_col_sql_id_with_alias">
<property name="alias" value="t"/>
<property name="prefix" value="t_"/>
</include>
- 1
- 2
- 3
- 4
包含prefix的sqlNode如下
<sql id="base_column_with_alias">
${alias}.ID as ${prefix}ID,
${alias}.WORKER_ID as ${prefix}WORKER_ID,
${alias}.NAME as ${prefix}NAME,
${alias}.ZB_ROLE_ID as ${prefix}ZB_ROLE_ID,
${alias}.ORG_ID as ${prefix}ORG_ID,
${alias}.STATUS as ${prefix}STATUS
</sql>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
如果說覺得手動寫包含alias和prefix的欄位麻煩,可以用,mybatis程式碼生成器的外掛的方式實現,我自己寫了一個生成器的外掛,可以程式碼再這裡,僅供參考
通用Service類
/**
* Created by Kaiwen
*/
@Service
public abstract class CommonServiceImpl<T,PK extends Serializable> implements CommonService<T,PK> {
/**
* 泛型注入
*/
@Autowired
private Mapper<T> mapper;
public T selectByPrimaryKey(PK entityId) {
return mapper.selectByPrimaryKey(entityId);
}
public int deleteByPrimaryKey(PK entityId) {
return mapper.deleteByPrimaryKey(entityId);
}
public int insert(T record) {
return mapper.insert(record);
}
public int insertSelective(T record) {
return mapper.insertSelective(record);
}
public int updateByPrimaryKeySelective(T record) {
return mapper.updateByPrimaryKeySelective(record);
}
public int updateByPrimaryKey(T record) {
return mapper.updateByPrimaryKey(record);
}
public List<T> selectByExample(Example example) {
return mapper.selectByExample(example);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
注入方式區別
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.jjshome.esf.core.dao.school" />
<property name="properties">
<value>
mappers=tk.mybatis.mapper.common.Mapper
</value>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.jjshome.esf.core.dao.community,com.jjshome.esf.core.dao.hsl"/>
</bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
實體類
package com.jjshome.esf.common.entity.school;
import java.util.Date;
import javax.persistence.*;
@Table(name = "XQ_SCHOOL_AREA")
public class SchoolArea {
/**
* 主鍵ID
*/
@Id
@Column(name = "ID")
private Integer id;
/**
* 城市編碼
*/
@Column(name = "CITY_CODE")
private String cityCode;
/**
* 學區名稱
*/
@Column(name = "NAME")
private String name;
/**
* 學區名稱拼音
*/
@Column(name = "NAME_SPELL")
private String nameSpell;
/**
* 狀態,1:正常,0:刪除
*/
@Column(name = "STATUS")
private Byte status;
/**
* 新增人
*/
@Column(name = "CREATE_ID")
private String createId;
@Transient
private Integer primaryCount; //小學數量
@Transient
private Integer middleCount; //初中數量
@Transient
private Integer highCount;//高中數量
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
TK mybatis Mapper檔案內容
<?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.jjshome.esf.core.dao.school.ISchoolAreaDAO" >
<resultMap id="BaseResultMap" type="com.jjshome.esf.common.entity.school.SchoolArea" >
<!--
WARNING - @mbggenerated
-->
<id column="ID" property="id" jdbcType="INTEGER" />
<result column="CITY_CODE" property="cityCode" jdbcType="VARCHAR" />
<result column="NAME" property="name" jdbcType="VARCHAR" />
<result column="NAME_SPELL" property="nameSpell" jdbcType="VARCHAR" />
<result column="STATUS" property="status" jdbcType="TINYINT" />
<result column="CREATE_ID" property="createId" jdbcType="VARCHAR" />
<result column="CREATE_DATE" property="createDate" jdbcType="TIMESTAMP" />
<result column="UPDATE_ID" property="updateId" jdbcType="VARCHAR" />
<result column="UPDATE_DATE" property="updateDate" jdbcType="TIMESTAMP" />
<result column="CITY_NAME" property="cityName"/>
<result column="PRIMARY_COUNT" property="primaryCount"/>
<result column="MIDDLE_COUNT" property="middleCount"/>
<result column="HIGH_COUNT" property="highCount"/>
</resultMap>
<resultMap id="SchoolDetailArea" type="com.jjshome.esf.common.entity.school.SchoolAreaDetail"
extends="com.jjshome.esf.core.dao.school.ISchoolInfoDAO.SchoolInfo">
<result column="SCHOOL_AREA_NAME" property="schoolAreaName"/>
</resultMap>
<select id="selectByPage" parameterType="map" resultMap="BaseResultMap">
SELECT A.*, C.NAME AS CITY_NAME,
(SELECT COUNT(*) FROM XQ_SCHOOL_INFO B WHERE A.ID=B.AREA_ID AND B.TYPE='553' AND B.STATUS = 1 ) AS PRIMARY_COUNT,
(SELECT COUNT(*) FROM XQ_SCHOOL_INFO B WHERE A.ID=B.AREA_ID AND B.TYPE='554' AND B.STATUS = 1 ) AS MIDDLE_COUNT,
(SELECT COUNT(*) FROM XQ_SCHOOL_INFO B WHERE A.ID=B.AREA_ID AND B.TYPE='555' AND B.STATUS = 1 ) AS HIGH_COUNT
FROM XQ_SCHOOL_AREA A
LEFT JOIN YW_CITY_SETTING C ON A.CITY_CODE = C.CODE
<where>
<if test="name != null and name != '' "> A.NAME LIKE CONCAT('%',#{NAME},'%') </if>
<if test="areaCityCode != null and areaCityCode != '' "> A.CITY_CODE = #{areaCityCode} </if>
<if test="keywords != null and keywords != '' ">
( A.NAME LIKE CONCAT('%',#{keywords},'%')
)
</if>
</where>
</select>
<select id="selectAreaIdAndKeyWord" parameterType="java.util.Map" resultMap="BaseResultMap">
SELECT
*
FROM
XQ_SCHOOL_AREA
WHERE
1=1
<if test="cityId != null">
AND CITY_CODE=#{cityId}
</if>
<if test="key != null and key!=''">
AND (NAME like CONCAT(#{key},'%' ) or NAME_SPELL like CONCAT(#{key},'%' ))
</if>
AND
STATUS=1
<if test="pageSize != null">
limit #{pageSize}
</if>
</select>
<!--查詢學區詳情列表-->
<select id="selectAreaDetailByPage" parameterType="map" resultMap="SchoolDetailArea">
SELECT A.* ,B.NAME AS SCHOOL_AREA_NAME ,C.NAME AS CITY_NAME,D.NAME AS AREA_NAME FROM XQ_SCHOOL_INFO A
LEFT JOIN XQ_SCHOOL_AREA B ON A.AREA_ID = B.ID
LEFT JOIN YW_CITY_SETTING C ON A.CITY_CODE = C.CODE
LEFT JOIN YW_CITY_SETTING D ON A.AREA_CODE = D.CODE
WHERE A.STATUS = 1 AND B.STATUS =1
<if test="areaId != null and areaId.length() > 0"> AND A.AREA_ID = #{areaId} </if>
<if test="typeList != null and typeList.size > 0">
AND
A.TYPE IN
<foreach collection="typeList" item="item" index="index" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
<if test="name != null and name != '' "> AND A.NAME LIKE CONCAT('%',#{name},'%') </if>
</select>
</mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
普通mybatisMapper檔案
<?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.jjshome.esf.core.dao.school.ISchoolInfoDAO">
<resultMap id="SchoolInfo" type="com.jjshome.esf.common.entity.school.SchoolInfo">
<id column="ID" property="id"/>
<result column="NAME" property="name"/>
<result column="NAME_SPELL" property="nameSpell"/>
<result column="ALIAS" property="alias"/>
<result column="ALIAS_SPELL" property="aliasSpell"/>
<result column="TYPE" property="type" typeHandler="com.jjshome.esf.core.component.handler.DictValueTypeHandler"/>
<result column="AREA_ID" property="areaId"/>
<result column="CITY_CODE" property="cityCode"/>
<result column="AREA_CODE" property="areaCode"/>
<result column="ADDR" property="addr"/>
<result column="START_TIME" property="startTime"/>
<result column="MOTTO" property="motto"/>
<result column="WEB_SITE" property="webSite"/>
<result column="PHONE" property="phone"/>
<result column="FEATURE" property="feature" typeHandler="com.jjshome.esf.core.component.handler.DictValueListTypeHandler"/>
<result column="LNG" property="lng"/>
<result column="LAT" property="lat"/>
<result column="UNIT_PRICE" property="unitPrice"/>
<result column="SALE_PRICE" property="salePrice"/>
<result column="NATURE_TYPE" property="natureType" typeHandler="com.jjshome.esf.core.component.handler.DictValueTypeHandler"/>
<result column="NATURE_CITY" property="natureCity" typeHandler="com.jjshome.esf.core.component.handler.DictValueTypeHandler"/>
<result column="SCHOOL_DEGREE" property="schoolDegree"/>
<result column="ENROL_DEGREE" property="enrolDegree"/>
<result column="IMG_DEGREE" property="imgDegree"/>
<result column="STATUS" property="status"/>
<result column="CREATE_ID" property="createId"/>
<result column="CREATE_DATE" property="createDate"/>
<result column="UPDATE_ID" property="updateId"/>
<result column="UPDATE_DATE" property="updateDate"/>
<result column="CITY_NAME" property="cityName" />
<result