陶邦仁 發(fā)布于 2015/12/25 09:56
系列目錄
- 深入淺出MyBatis系列
- 【深入淺出MyBatis系列一】MyBatis入門
- 【深入淺出MyBatis系列二】配置簡介(MyBatis源碼篇)
- 【深入淺出MyBatis系列三】Mapper映射文件配置
- 【深入淺出MyBatis系列四】強(qiáng)大的動態(tài)SQL
- 【深入淺出MyBatis系列五】SQL執(zhí)行流程分析(源碼篇)
- 【深入淺出MyBatis系列六】插件原理
- 【深入淺出MyBatis系列七】分頁插件
- 【深入淺出MyBatis系列八】SQL自動生成插件
- 【深入淺出MyBatis系列九】改造Cache插件
- 【深入淺出MyBatis系列十】與Spring集成
- 【深入淺出MyBatis系列十一】緩存源碼分析
- 【深入淺出MyBatis系列十二】終結(jié)篇:MyBatis原理深入解析
本文提供了一種自動生成sql語句的方法,它針對的對象是有主鍵或唯一索引的單表晓猛,提供的操作有增凿滤、刪、改杠纵、查4種
。理解本文和本文的提供的代碼需要有java注解的知識,因為本文是基于注解生成sql的暴匠。
1 準(zhǔn)備
1.1 為什么在StatementHandler攔截## 在SQL執(zhí)行流程分析(源碼篇)章節(jié)介紹了一次sqlsession的完整執(zhí)行過程竭缝,從中可以知道sql的解析是在StatementHandler里完成的房维,所以為了重寫sql需要攔截StatementHandler。
1.2 MetaObject簡介
在實現(xiàn)里大量使用了MetaObject這個對象抬纸,因此有必要先介紹下它咙俩。MetaObject是Mybatis提供的一個的工具類,通過它包裝一個對象后可以獲取或設(shè)置該對象的原本不可訪問的屬性(比如那些私有屬性)
湿故。它有個三個重要方法經(jīng)常用到:
MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) 用于包裝對象阿趁;
Object getValue(String name) 用于獲取屬性的值(支持OGNL的方法);
void setValue(String name, Object value) 用于設(shè)置屬性的值(支持OGNL的方法)坛猪;
2 攔截器簽名
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class AutoMapperInterceptor implements Interceptor {
...
}
從簽名里可以看出脖阵,要攔截的目標(biāo)類型是StatementHandler(注意:type只能配置成接口類型
),攔截的方法是名稱為prepare參數(shù)為Connection類型的方法墅茉。
3 intercept實現(xiàn)
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class AutoMapperInterceptor implements Interceptor {
private static final Log logger = LogFactory.getLog(AutoMapperInterceptor.class);
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
DEFAULT_OBJECT_WRAPPER_FACTORY);
// 分離代理對象鏈
while (metaStatementHandler.hasGetter("h")) {
Object object = metaStatementHandler.getValue("h");
metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
}
// 分離最后一個代理對象的目標(biāo)類
while (metaStatementHandler.hasGetter("target")) {
Object object = metaStatementHandler.getValue("target");
metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
}
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");
Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
if (null == originalSql || "".equals(originalSql)) {
String newSql = "";
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
.getValue("delegate.mappedStatement");
// 根據(jù)ID生成相應(yīng)類型的sql語句(id需剔除namespace信息)
String id = mappedStatement.getId();
id = id.substring(id.lastIndexOf(".") + 1);
if ("insert".equals(id)) {
newSql = SqlBuilder.buildInsertSql(parameterObject);
} else if ("update".equals(id)) {
newSql = SqlBuilder.buildUpdateSql(parameterObject);
} else if ("delete".equals(id)) {
newSql = SqlBuilder.buildDeleteSql(parameterObject);
} else if ("select".equals(id)) {
newSql = SqlBuilder.buildSelectSql(parameterObject);
}
logger.debug("Auto generated sql:" + newSql);
//
SqlSource sqlSource = buildSqlSource(configuration, newSql, parameterObject.getClass());
List<ParameterMapping> parameterMappings = sqlSource.getBoundSql(parameterObject).getParameterMappings();
metaStatementHandler.setValue("delegate.boundSql.sql", sqlSource.getBoundSql(parameterObject).getSql());
metaStatementHandler.setValue("delegate.boundSql.parameterMappings", parameterMappings);
}
// 調(diào)用原始statementHandler的prepare方法
statementHandler = (StatementHandler) metaStatementHandler.getOriginalObject();
statementHandler.prepare((Connection) invocation.getArgs()[0]);
// 傳遞給下一個攔截器處理
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
private SqlSource buildSqlSource(Configuration configuration, String originalSql,
Class<?> parameterType) {
SqlSourceBuilder builder = new SqlSourceBuilder(configuration);
return builder.parse(originalSql, parameterType, null);
}
}
StatementHandler的默認(rèn)實現(xiàn)類是RoutingStatementHandler命黔,因此攔截的實際對象是它。RoutingStatementHandler的主要功能是分發(fā)就斤,它根據(jù)配置Statement類型創(chuàng)建真正執(zhí)行數(shù)據(jù)庫操作的StatementHandler悍募,并將其保存到delegate屬性里。由于delegate是一個私有屬性并且沒有提供訪問它的方法战转,因此需要借助MetaObject的幫忙搜立。通過MetaObject的封裝后我們可以輕易的獲得想要的屬性。
在上面的方法里有個兩個循環(huán)槐秧,通過他們可以分離出原始的RoutingStatementHandler(而不是代理對象)啄踊。
有了插件幫你生成sql語句后,mapper配置文件里單表的增刪改查部分就不需要再配置sql代碼了刁标,但由于插件需要通過id來生成不同類型的sql語句颠通,因此必要的配置還是需要的,而且相應(yīng)的id必須是下面的這幾個(區(qū)分大小寫):
<update id="update" parameterType="UserDto"></update>
<insert id="insert" parameterType="UserDto"></insert>
<delete id="delete" parameterType="UserDto"></delete>
<select id="select" parameterType="UserDto" resultType="UserDto""></select>
3 SqlBuilder
SqlBuilder的相應(yīng)方法接受一個dto對象作為參數(shù)膀懈,它們根據(jù)這個對象的屬性值和配置的注解生成相應(yīng)的sql顿锰。
@TableMapperAnnotation(tableName = "t_user", uniqueKeyType = UniqueKeyType.Single, uniqueKey = " userid ")
public class UserDto {
@FieldMapperAnnotation(dbFieldName = "userid", jdbcType = JdbcType.INTEGER)
private Integer userid;
@FieldMapperAnnotation(dbFieldName = "username", jdbcType = JdbcType.VARCHAR)
private String username;
...
}
這個對象包含了兩種注解,一個是TableMapperAnnotation注解,它保存了表名硼控、唯一鍵類型和構(gòu)成唯一鍵的字段刘陶;另一個是FieldMapperAnnotation注解,它保存了數(shù)據(jù)庫字段名和字段類型信息牢撼。這兩個注解都是必須的匙隔。SqlBuilder生成sql時會用到他們,下面以生成insert語句的方法為例熏版,其他方法類似:
public static String buildInsertSql(Object object) throws Exception {
if (null == object) {
throw new RuntimeException("Sorry,I refuse to build sql for a null object!");
}
Map dtoFieldMap = PropertyUtils.describe(object);
// 從參數(shù)對象里提取注解信息
TableMapper tableMapper = buildTableMapper(object.getClass());
// 從表注解里獲取表名等信息
TableMapperAnnotation tma = (TableMapperAnnotation) tableMapper.getTableMapperAnnotation();
String tableName = tma.tableName();
StringBuffer tableSql = new StringBuffer();
StringBuffer valueSql = new StringBuffer();
tableSql.append("insert into ").append(tableName).append("(");
valueSql.append("values(");
boolean allFieldNull = true;
// 根據(jù)字段注解和屬性值聯(lián)合生成sql語句
for (String dbFieldName : tableMapper.getFieldMapperCache().keySet()) {
FieldMapper fieldMapper = tableMapper.getFieldMapperCache().get(dbFieldName);
String fieldName = fieldMapper.getFieldName();
Object value = dtoFieldMap.get(fieldName);
// 由于要根據(jù)字段對象值是否為空來判斷是否將字段加入到sql語句中纷责,因此DTO對象的屬性不能是簡單類型,反而必須是封裝類型
if (value == null) {
continue;
}
allFieldNull = false;
tableSql.append(dbFieldName).append(",");
valueSql.append("#{").append(fieldName).append(",").append("jdbcType=")
.append(fieldMapper.getJdbcType().toString()).append("},");
}
if (allFieldNull) {
throw new RuntimeException("Are you joking? Object " + object.getClass().getName()
+ "'s all fields are null, how can i build sql for it?!");
}
tableSql.delete(tableSql.lastIndexOf(","), tableSql.lastIndexOf(",") + 1);
valueSql.delete(valueSql.lastIndexOf(","), valueSql.lastIndexOf(",") + 1);
return tableSql.append(") ").append(valueSql).append(")").toString();
}
4 plugin實現(xiàn)
public Object plugin(Object target) {
// 當(dāng)目標(biāo)類是StatementHandler類型時撼短,才包裝目標(biāo)類再膳,否者直接返回目標(biāo)本身,減少目標(biāo)被代理的
// 次數(shù)
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
? 著作權(quán)歸作者所有