tkmybatis是在mybatis框架的基础上提供了很多工具,让开发更加高效,下面来看看这个框架的基本使用,后面会对相关源码进行分析,感兴趣的同学可以看一下,挺不错的一个工具
实现对员工表的增删改查的代码
java的dao层接口public interfaceWorkerMapperextendsMapper{
}
xml映射文件<?xml version="1.0" encoding="UTF-8"?>
实体对象@Table(name = "worker")
public classWorker{
@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 ...
}
以上就是实现对Worker进行增删改查的所有代码,包括选择性更新、插入、删除等,所有的方法列表如下
以后对表字段的添加或修改只需要更改实体对象的注解,不需要修改xml映射文件,如将worker_id改成worker_no@Column(name = "worker_no")
private String workerNo;
数据源的配置,只需要将org.mybatis.spring.mapper.MapperScannerConfigurer改成tk.mybatis.spring.mapper.MapperScannerConfigurer,然后加一个属性
,也可不加,因为框架提供了默认实现
mappers=tk.mybatis.mapper.common.Mapper
用这个库之后写代码感觉在飞…….如果只是简单的了解此框架到这里就可以了,下面是对框架实现原理的分析
原理的简单分析
此框架为我们实现这些功能所有的改动都在Mapper层面,所有的Mapper都继承了tk.mybatis.mapper.common.Mapperpublic interfaceWorkerMapperextendsMapper{}
Mapper接口的声明如下,可以看到Mapper接口实现了所有常用的方法public interfaceMapperextends
BaseMapper,
ExampleMapper,
RowBoundsMapper,
Marker{
}
看一下完整的UML图,太大了,可以用新窗口打开,放大之后再看
这里选择一个接口:SelectOneMapper接口,对于源码进行简单分析,此接口声明如下:public interface SelectOneMapper{
/**
* 根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
*
*@paramrecord
*@return
*/
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
T selectOne(T record);
}
@SelectProvider是mybatis3之后提供的,用于灵活的设置sql来源,这里设置了服务提供类和方法,但这个库并没有直接用method指定的方法来返回sql,而是在运行时进行解析的,代码如下public classBaseSelectProviderextendsMapperTemplate{
publicStringselectOne(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();
}
}
到这里我们就大概知道了这个库为我们提供便利的原理了,总的来说就是这个库帮我们提供了对表的基本操作的sql,帮我们省了很多工作量,而且维护起来也很方便,否则我们的xml文件动不动就几百行甚至上千行
对源码的探索不能到这里停止,最起码要分析到与另一个框架的整合点
我们知道,mybatis的mapper接口是在启动的时候被框架以JdkProxy的形式封装了的,具体对应的类是MapperFactoryBean,这个类中有一个checkDaoConfig()方法,是从父类继承并重写了该方法,继承结构如下MapperFactoryBean -> SqlSessionDaoSupport -> DaoSupport
这里的DaoSupport就是spring提供的Dao的抽象,代码如下public abstract classDaoSupportimplementsInitializingBean{
// spring 完成属性设置后会调用此方法
@Override
publicfinalvoidafterPropertiesSet()throwsIllegalArgumentException, BeanInitializationException{
// 这里提供了接口供子类去实现
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
protectedabstractvoidcheckDaoConfig()throwsIllegalArgumentException;
protectedvoidinitDao()throwsException{
}
}
框架自定义的MapperFactoryBean重写了checkDaoConfig()方法,完成对所有sql语句的设置,代码如下@Override
protectedvoidcheckDaoConfig(){
super.checkDaoConfig();
//通用Mapper
if (mapperHelper.isExtendCommonMapper(getObjectType())) {
//这里去处理该类所对应的MappedStatement,封装在helper类中处理
mapperHelper.processConfiguration(getSqlSession().getConfiguration(), getObjectType());
}
}
MapperHelper的processConfiguration方法如下publicvoidprocessConfiguration(Configuration configuration, Class> mapperInterface){
String prefix;
if (mapperInterface != null) {
prefix = mapperInterface.getCanonicalName();
} else {
prefix = "";
}
for (Object object : new ArrayList(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);
}
}
}
}
}
设置sql的逻辑,提供了几种不同类型的sqlsourcepublic 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);
到这里整个sql的获取流程就分析完了,本人用这个库写过一个小项目,确实节省了开发的工作量,而且DAO层的结构更加清晰简洁了
关于mybatis新特性
从3.4.0开始,mybatis提供对外部表的alias引用方法,多表联合查询就方便多了,我们先看原始的方式是怎样做的select a.id,a.name,b.bid,b.bname .....
from user a
left join room b
原始的方式是将所有的表字段列出来,再来看用新特性怎样做
select id="selectUsers" resultType="map">
select
,
from user t1
left join room t2
这里主要就是对基本的sql进行了复用,如果对表进行了修改只要在原始的sql节点修改就可以了,就算是5个表的联合查询,sql也是清晰易懂,维护起来会更轻松
新版本的mybatis对于对象映射也提供了更友好的方式,直接使用外部的ResultMap再加上查询语句中的别名就映射完成了
更进一步
敏锐的程序员可能会提出问题,如当多表查询的时候可能会存在字段名称相同的情况,这里的解决方案是给include添加另一个属性
包含prefix的sqlNode如下
${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
如果说觉得手动写包含alias和prefix的字段麻烦,可以用,mybatis代码生成器的插件的方式实现,我自己写了一个生成器的插件,可以代码再这里,仅供参考
通用Service类/**
* Created by Kaiwen
*/
@Service
public abstract classCommonServiceImplimplementsCommonService{
/**
* 泛型注入
*/
@Autowired
private Mapper mapper;
publicT selectByPrimaryKey(PK entityId){
return mapper.selectByPrimaryKey(entityId);
}
publicintdeleteByPrimaryKey(PK entityId){
return mapper.deleteByPrimaryKey(entityId);
}
publicintinsert(T record){
return mapper.insert(record);
}
publicintinsertSelective(T record){
return mapper.insertSelective(record);
}
publicintupdateByPrimaryKeySelective(T record){
return mapper.updateByPrimaryKeySelective(record);
}
publicintupdateByPrimaryKey(T record){
return mapper.updateByPrimaryKey(record);
}
publicList selectByExample(Example example){
return mapper.selectByExample(example);
}
}
注入方式区别
mappers=tk.mybatis.mapper.common.Mapper
<
实体类package com.jjshome.esf.common.entity.school;
import java.util.Date;
import javax.persistence.*;
@Table(name = "XQ_SCHOOL_AREA")
public classSchoolArea{
/**
* 主键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;//高中数量
TK mybatis Mapper文件内容<?xml version="1.0" encoding="UTF-8" ?>
extends="com.jjshome.esf.core.dao.school.ISchoolInfoDAO.SchoolInfo">
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
A.NAME LIKE CONCAT('%',#{NAME},'%')
A.CITY_CODE = #{areaCityCode}
( A.NAME LIKE CONCAT('%',#{keywords},'%')
)
SELECT
*
FROM
XQ_SCHOOL_AREA
WHERE
1=1
AND CITY_CODE=#{cityId}
AND (NAME like CONCAT(#{key},'%' ) or NAME_SPELL like CONCAT(#{key},'%' ))
AND
STATUS=1
limit #{pageSize}
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
AND A.AREA_ID = #{areaId}
AND
A.TYPE IN
#{item}
AND A.NAME LIKE CONCAT('%',#{name},'%')
普通mybatisMapper文件<?xml version="1.0" encoding="UTF-8" ?>
ID,
NAME,
NAME_SPELL,
ALIAS,
ALIAS_SPELL,
TYPE,
AREA_ID,
CITY_CODE,
AREA_CODE,
ADDR,
START_TIME,
MOTTO,
WEB_SITE,
PHONE,
FEATURE,
LNG,
LAT,
UNIT_PRICE,
SALE_PRICE,
NATURE_TYPE,
NATURE_CITY,
SCHOOL_DEGREE,
ENROL_DEGREE,
IMG_DEGREE,
STATUS,
CREATE_ID,
CREATE_DATE,
UPDATE_ID,
UPDATE_DATE,
SALE_COUNT,
SALE_COUNT
SELECT
i.*,
yc.NAME as 'CITY_NAME',
ya.NAME as 'AREA_NAME',
xq.NAME as 'SCHOOL_DISTRICT_NAME'
FROM
XQ_SCHOOL_INFO i
LEFT JOIN YW_CITY_SETTING yc ON i.CITY_CODE = yc.CODE
LEFT JOIN YW_CITY_SETTING ya ON i.AREA_CODE = ya.CODE
LEFT JOIN XQ_SCHOOL_AREA xq ON i.AREA_ID = xq.ID
WHERE
i.ID = #{id,jdbcType=INTEGER}
UPDATE
XQ_SCHOOL_INFO
SET
STATUS = 0,
UPDATE_ID = #{updateId},
UPDATE_DATE = NOW()
WHERE
ID = #{id,jdbcType=INTEGER}
UPDATE
XQ_SCHOOL_INFO
SET
STATUS = 0,
UPDATE_ID = #{updateId},
UPDATE_DATE = NOW()
WHERE
ID IN (${ids})
update XQ_SCHOOL_INFO
SET AREA_ID = NULL,
UPDATE_DATE = NOW()
WHERE
ID = #{id}
SELECT LAST_INSERT_ID()
INSERT INTO XQ_SCHOOL_INFO
(NAME,
NAME_SPELL,
ALIAS,
ALIAS_SPELL,
TYPE,
AREA_ID,
CITY_CODE,
AREA_CODE,
ADDR,
START_TIME,
MOTTO,
WEB_SITE,
PHONE,
FEATURE,
LNG,
LAT,
UNIT_PRICE,
SALE_PRICE,
NATURE_TYPE,
NATURE_CITY,
SCHOOL_DEGREE,
ENROL_DEGREE,
IMG_DEGREE,
STATUS,
CREATE_ID,
CREATE_DATE,
UPDATE_ID,
UPDATE_DATE)
VALUES
(#{name,jdbcType=VARCHAR},
#{nameSpell,jdbcType=VARCHAR},
#{alias,jdbcType=VARCHAR},
#{aliasSpell,jdbcType=VARCHAR},
#{type,jdbcType=INTEGER},
#{areaId,jdbcType=INTEGER},
#{cityCode,jdbcType=VARCHAR},
#{areaCode,jdbcType=VARCHAR},
#{addr,jdbcType=VARCHAR},
#{startTime,jdbcType=DATE},
#{motto,jdbcType=VARCHAR},
#{webSite,jdbcType=VARCHAR},
#{phone,jdbcType=VARCHAR},
#{feature,jdbcType=VARCHAR},
#{lng,jdbcType=DECIMAL},
#{lat,jdbcType=DECIMAL},
#{unitPrice},
#{salePrice},
#{natureType,jdbcType=INTEGER},
#{natureCity,jdbcType=INTEGER},
#{schoolDegree,jdbcType=INTEGER},
#{enrolDegree,jdbcType=INTEGER},
#{imgDegree,jdbcType=INTEGER},
#{status,jdbcType=TINYINT},
#{createId,jdbcType=VARCHAR},
#{createDate,jdbcType=DATE},
#{updateId,jdbcType=VARCHAR},
#{updateDate,jdbcType=DATE})
SELECT LAST_INSERT_ID()
INSERT INTO XQ_SCHOOL_INFO
NAME,
NAME_SPELL,
ALIAS,
ALIAS_SPELL,
TYPE,
AREA_ID,
CITY_CODE,
AREA_CODE,
ADDR,
START_TIME,
MOTTO,
WEB_SITE,
PHONE,
FEATURE,
LNG,
LAT,
UNIT_PRICE,
SALE_PRICE,
NATURE_TYPE,
NATURE_CITY,
SCHOOL_DEGREE,
ENROL_DEGREE,
IMG_DEGREE,
STATUS,
CREATE_ID,
CREATE_DATE,
UPDATE_ID,
UPDATE_DATE,
#{name,jdbcType=VARCHAR},
#{nameSpell,jdbcType=VARCHAR},
#{alias,jdbcType=VARCHAR},
#{aliasSpell,jdbcType=VARCHAR},
#{type,jdbcType=INTEGER},
#{areaId,jdbcType=INTEGER},
#{cityCode,jdbcType=VARCHAR},
#{areaCode,jdbcType=VARCHAR},
#{addr,jdbcType=VARCHAR},
#{startTime,jdbcType=DATE},
#{motto,jdbcType=VARCHAR},
#{webSite,jdbcType=VARCHAR},
#{phone,jdbcType=VARCHAR},
#{feature,jdbcType=VARCHAR},
#{lng,jdbcType=DECIMAL},
#{lat,jdbcType=DECIMAL},
#{unitPrice},
#{salePrice},
#{natureType,jdbcType=INTEGER},
#{natureCity,jdbcType=INTEGER},
#{schoolDegree,jdbcType=INTEGER},
#{enrolDegree,jdbcType=INTEGER},
#{imgDegree,jdbcType=INTEGER},
#{status,jdbcType=TINYINT},
#{createId,jdbcType=VARCHAR},
#{createDate,jdbcType=DATE},
#{updateId,jdbcType=VARCHAR},
#{updateDate,jdbcType=DATE},
UPDATE XQ_SCHOOL_INFO
NAME=#{name,jdbcType=VARCHAR},
NAME_SPELL=#{nameSpell,jdbcType=VARCHAR},
ALIAS=#{alias,jdbcType=VARCHAR},
ALIAS_SPELL=#{aliasSpell,jdbcType=VARCHAR},
TYPE=#{type,jdbcType=INTEGER},
AREA_ID=#{areaId,jdbcType=INTEGER},
CITY_CODE=#{cityCode,jdbcType=VARCHAR},
AREA_CODE=#{areaCode,jdbcType=VARCHAR},
ADDR=#{addr,jdbcType=VARCHAR},
START_TIME=#{startTime,jdbcType=DATE},
MOTTO=#{motto,jdbcType=VARCHAR},
WEB_SITE=#{webSite,jdbcType=VARCHAR},
PHONE=#{phone,jdbcType=VARCHAR},
FEATURE=#{feature,jdbcType=VARCHAR},
LNG=#{lng,jdbcType=DECIMAL},
LAT=#{lat,jdbcType=DECIMAL},
UNIT_PRICE=#{unitPrice},
SALE_PRICE=#{salePrice},
NATURE_TYPE=#{natureType,jdbcType=INTEGER},
NATURE_CITY=#{natureCity,jdbcType=INTEGER},
SCHOOL_DEGREE=#{schoolDegree,jdbcType=INTEGER},
ENROL_DEGREE=#{enrolDegree,jdbcType=INTEGER},
IMG_DEGREE=#{imgDegree,jdbcType=INTEGER},
STATUS=#{status,jdbcType=TINYINT},
CREATE_ID=#{createId,jdbcType=VARCHAR},
CREATE_DATE=#{createDate,jdbcType=DATE},
UPDATE_ID=#{updateId,jdbcType=VARCHAR},
UPDATE_DATE=#{updateDate,jdbcType=DATE},
SALE_COUNT=#{saleCount},
WHERE
ID = #{id,jdbcType=INTEGER}
SELECT
FROM
XQ_SCHOOL_INFO
WHERE
STATUS = 1
AND AREA_ID = #{areaId}
SELECT
FROM
XQ_SCHOOL_INFO
WHERE
STATUS = 1
ORDER BY ID DESC
SELECT
i.*,
yc.NAME as 'CITY_NAME',
ya.NAME as 'AREA_NAME'
FROM
XQ_SCHOOL_INFO i
LEFT JOIN YW_CITY_SETTING yc ON i.CITY_CODE = yc.CODE
LEFT JOIN YW_CITY_SETTING ya ON i.AREA_CODE = ya.CODE
WHERE
i.STATUS = 1
AND i.CITY_CODE=#{city}
AND i.AREA_CODE=#{area}
AND i.ID=#{schoolId}
AND i.NAME LIKE concat('%',#{schoolName},'%')
AND i.AREA_ID=#{schoolDistrictId}
AND i.TYPE=#{schoolType}
AND (i.NAME LIKE CONCAT('%',#{key},'%') OR i.ALIAS LIKE CONCAT('%', #{key}, '%'))
/*priceType == 1:起售价 2:房源售均价*/
AND SALE_PRICE >= #{salePriceStart}
AND SALE_PRICE <= #{salePriceEnd}
AND UNIT_PRICE >= #{salePriceStart}
AND UNIT_PRICE <= #{salePriceEnd}
AND SCHOOL_DEGREE = 100
AND SCHOOL_DEGREE < 100
ORDER BY ID DESC
SELECT
FROM
XQ_SCHOOL_INFO
WHERE
STATUS = 1
AND NAME = #{name}
AND CITY_CODE=#{city}
AND AREA_CODE=#{area}
AND TYPE = #{type}
SELECT
XSI.*,CYCS.NAME AS 'CITY_NAME',AYCS.NAME AS 'AREA_NAME'
FROM
XQ_SCHOOL_INFO XSI
LEFT JOIN YW_CITY_SETTING CYCS ON XSI.CITY_CODE = CYCS.CODE
LEFT JOIN YW_CITY_SETTING AYCS ON XSI.AREA_CODE = AYCS. CODE
WHERE
1=1
AND XSI.AREA_CODE=#{areaId}
AND (XSI.NAME like CONCAT(#{key},'%' ) or XSI.NAME_SPELL like CONCAT(#{key},'%' ))
AND
XSI.STATUS=1
limit #{pageSize}
SELECT DISTINCT AREA_ID FROM XQ_SCHOOL_INFO WHERE NAME LIKE CONCAT('%',#{schoolName},'%')
SELECT
FROM
XQ_SCHOOL_INFO
WHERE
STATUS = 1
AND ID IN
#{item}
AND AREA_ID = #{areaId}