本文提供了一種自動(dòng)生成sql語句的方法勺择,它針對的對象是有主鍵或唯一索引的單表创南,提供的操作有增、刪省核、改稿辙、查4種。理解本文和本文的提供的代碼需要有java注解的知識气忠,因?yàn)楸疚氖腔谧⒔馍蓅ql的邻储。
1 準(zhǔn)備#
1.1 為什么在StatementHandler攔截##
在SQL執(zhí)行流程分析(源碼篇)章節(jié)介紹了一次sqlsession的完整執(zhí)行過程,從中可以知道sql的解析是在StatementHandler里完成的旧噪,所以為了重寫sql需要攔截StatementHandler吨娜。
1.2 MetaObject簡介##
在實(shí)現(xiàn)里大量使用了MetaObject這個(gè)對象,因此有必要先介紹下它淘钟。MetaObject是Mybatis提供的一個(gè)的工具類宦赠,通過它包裝一個(gè)對象后可以獲取或設(shè)置該對象的原本不可訪問的屬性(比如那些私有屬性)。它有個(gè)三個(gè)重要方法經(jīng)常用到:
MetaObject forObject(...) 用于包裝對象米母;
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實(shí)現(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);
}
// 分離最后一個(gè)代理對象的目標(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]);
// 傳遞給下一個(gè)攔截器處理
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)實(shí)現(xiàn)類是RoutingStatementHandler精拟,因此攔截的實(shí)際對象是它燎斩。RoutingStatementHandler的主要功能是分發(fā),它根據(jù)配置Statement類型創(chuàng)建真正執(zhí)行數(shù)據(jù)庫操作的StatementHandler蜂绎,并將其保存到delegate屬性里栅表。由于delegate是一個(gè)私有屬性并且沒有提供訪問它的方法,因此需要借助MetaObject的幫忙师枣。通過MetaObject的封裝后我們可以輕易的獲得想要的屬性怪瓶。
在上面的方法里有個(gè)兩個(gè)循環(huán),通過他們可以分離出原始的RoutingStatementHandler(而不是代理對象)践美。
有了插件幫你生成sql語句后洗贰,mapper配置文件里單表的增刪改查部分就不需要再配置sql代碼了,但由于插件需要通過id來生成不同類型的sql語句陨倡,因此必要的配置還是需要的敛滋,而且相應(yīng)的id必須是下面的這幾個(gè)(區(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)方法接受一個(gè)dto對象作為參數(shù),它們根據(jù)這個(gè)對象的屬性值和配置的注解生成相應(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;
...
}
這個(gè)對象包含了兩種注解绎晃,一個(gè)是TableMapperAnnotation注解,它保存了表名杂曲、唯一鍵類型和構(gòu)成唯一鍵的字段庶艾;另一個(gè)是FieldMapperAnnotation注解,它保存了數(shù)據(jù)庫字段名和字段類型信息擎勘。這兩個(gè)注解都是必須的咱揍。SqlBuilder生成sql時(shí)會(huì)用到他們,下面以生成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實(shí)現(xiàn)#
public Object plugin(Object target) {
// 當(dāng)目標(biāo)類是StatementHandler類型時(shí)蟹地,才包裝目標(biāo)類积暖,否者直接返回目標(biāo)本身,減少目標(biāo)被代理的
// 次數(shù)
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}