基于spring JdbcTemplate改进版通用dao的使用及实现三:SuperDao类的设计和实现

分类: Spring 5人评论 1年前发布

注意:本组件已重构并全新发布,更加方便易于使用。代码全部开源,详情访问 Github仓库码云

前面讲了如何使用,该讲讲如何实现了。

其实技术上并没有什么难度,主要就是一个设计思路。

先来讲解一下superDao,大体上跟前面实现的原理没有什么变化,主要就是添加了根据class对象查询及组合使用。

先来看一下superDao的使用声明:

<bean id="superDao" class="com.mincoder.speed.persistence.SuperDaoImpl">
    <!--必须-->
    <property name="jdbcTemplate" ref="jdbcTemplate"/>
    <!--可选,默认使用com.mincoder.speed.persistence.NameHandler-->
    <property name="nameHandler" ref="defaultNameHandler"/>
    <!--可选,默认使用spring提供的org.springframework.jdbc.core.BeanPropertyRowMapper-->
    <property name="rowMapperClass" value="com.mincoder.test.SpeedRowMapper"/>
    <!--可选,默认true-->
    <property name="isNativeId" value="true"/>
</bean>

如果你的操作都是默认的,并且spring配置了扫描superDao的包路径,就无须在xml配置文件中声明了。

jdbcTemplate,属性是必须的,不多说了。

nameHandler,不配置默认处理以下划线分隔的方式,例如 USER_ID* 对应 userId,*PARENT_USER_ID 对应 parentUserId。

rowMapperClass,不配置默认使用spring提供的BeanPropertyRowMapper.newInstance(clazz)方法,同样可以处理以下划线分隔的方式。

isNativeId,不配置默认数据库主键是自增类型,适用于mysql、sql server等,oracle序列类型的不适用。

superDaoImpl需要有对应以上配置的变量,并且还要保存一些操作信息:

/**
 * 通用dao实现
 *
 * Created by liyd on 6/26/14.
 */
@Repository
public class SuperDaoImpl implements SuperDao {
    /** 排序 */
    private static final ThreadLocal<String>          LOCAL_ORDER_BY = new ThreadLocal<String>();
    /** 白名单 */
    private static final ThreadLocal<List<String>>    INCLUDE_FIELDS = new ThreadLocal<List<String>>();
    /** 黑名单 */
    private static final ThreadLocal<List<String>>    EXCLUDE_FIELDS = new ThreadLocal<List<String>>();
    /** 操作的字段 */
    private static final ThreadLocal<List<AutoField>> AUTO_FIELDS    = new ThreadLocal<List<AutoField>>();
    /** spring jdbcTemplate 对象 */
    @Autowired
    protected JdbcOperations                          jdbcTemplate;
    /** 名称处理器,为空按默认执行 */
    @Autowired(required = false)
    protected NameHandler                             nameHandler;
    /** rowMapper,为空按默认执行 */
    @Autowired(required = false)
    protected String                                  rowMapperClass;
    @Autowired(required = false)
    protected Boolean                                 isNativeId     = true;
    ......
}

一些操作的信息肯定需要先保存起来,那么保存在哪里呢?dao肯定会有多个线程同时调用并发的情况,最佳答案当然是ThreadLocal了。

下面是每个变量的说明:

LOCAL*ORDER*BY:保存查询排序信息

INCLUDE_FIELDS:保存白名单信息

EXCLUDE_FIELDS:保存黑名单信息

AUTO_FIELDS:保存操作的字段信息

jdbcTemplate:不多说了,spring JdbcTemplate对象

nameHandler:名称处理类,方便扩展,可以在配置文件中替换默认

rowMapperClass:rowMapper处理类,方便扩展,可以在配置文件中替换默认

isNativeId:主键生成策略,是否自增,可以在配置文件中指定,默认true

看了上面的变量,对于superDao的实现流程相信已经心里有数了吧!

nameHandler和rowMapperClass这里不细说了,将在稍后做具体说明,其实前面的文章已经有说明了,主要说下改动的地方。

以下列举几个典型的方法实现。

insert方法

/**
 * 插入数据
 *
 * @param entity the entity
 * @param clazz the clazz
 * @return long
 */
private Long insert(Object entity, Class<?> clazz) {
    NameHandler handler = this.getNameHandler();
    Long id = null;
    if (!this.isNativeId) {
        String className = (entity == null ? clazz.getSimpleName() : entity.getClass()
            .getSimpleName());
        String primaryValueFrag = handler.getPrimaryValueFrag(className, this.isNativeId);
        id = jdbcTemplate.queryForLong(primaryValueFrag);
        String primaryName = handler.getPrimaryName(className);
        this.set(NameUtils.getCamelName(primaryName), id);
    }
    final BoundSql boundSql = SqlUtils.buildInsertSql(entity, clazz, this.getFieldMap(),
        this.getNameHandler());
    if (this.isNativeId) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            public PreparedStatement createPreparedStatement(Connection con)
                                                                            throws SQLException {
                PreparedStatement ps = con.prepareStatement(boundSql.getSql(),
                    new String[] { boundSql.getPrimaryKey() });
                int index = 0;
                for (Object param : boundSql.getParams()) {
                    index++;
                    ps.setObject(index, param);
                }
                return ps;
            }
        }, keyHolder);
        id = keyHolder.getKey().longValue();
    } else {
        jdbcTemplate.update(boundSql.getSql(), boundSql.getParams().toArray());
    }
    return id;
}
@Override
public Long insert(Object entity) {
    return this.insert(entity, null);
}
@Override
public Long insert(Class<?> clazz) {
    return this.insert(null, clazz);
}

insert方法的实现,比较复杂一点的地方就是主键的生成策略,是数据库自增型还是序列等方式指定型。

主要就是根据isNativeId判断在插入数据时是否生成主键返回,如果是oracle等序列方式的,就在AUTO_FIELDS字段中set一个主键属性。

set方法:

@Override
public SuperDao set(String fieldName, Object value) {
    this.addAutoFields(fieldName, null, "=", AutoField.UPDATE_FIELD, value);
    return this;
}

其它就是sql语句的拼装了。

BoundSql buildInsertSql方法:

/**
 * 构建insert语句
 *
 * @param entity 实体映射对象
 * @param clazz the clazz
 * @param fieldMap the field map
 * @param nameHandler 名称转换处理器
 * @return bound sql
 */
public static BoundSql buildInsertSql(Object entity, Class<?> clazz,
                                      Map<String, Object> fieldMap, NameHandler nameHandler) {
    Class<?> entityClass = (clazz == null ? entity.getClass() : clazz);
    String tableName = nameHandler.getTableName(entityClass.getSimpleName());
    String primaryName = nameHandler.getPrimaryName(entityClass.getSimpleName());
    List<AutoField> autoFields = (List<AutoField>) fieldMap.get(AUTO_FIELDS);
    List<AutoField> allAutoField = getAllAutoField(entity, autoFields, INSERT);
    StringBuilder sql = new StringBuilder("INSERT INTO ");
    List<Object> params = new ArrayList<Object>();
    sql.append(tableName);
    sql.append("(");
    StringBuilder args = new StringBuilder();
    args.append("(");
    for (AutoField autoField : allAutoField) {
        sql.append(nameHandler.getColumnName(autoField.getName()));
        args.append("?");
        params.add(autoField.getValues()[0]);
        sql.append(",");
        args.append(",");
    }
    sql.deleteCharAt(sql.length() - 1);
    args.deleteCharAt(args.length() - 1);
    args.append(")");
    sql.append(")");
    sql.append(" VALUES ");
    sql.append(args);
    return new BoundSql(sql.toString(), primaryName, params);
}

queryList方法:

@Override
public <T> List<T> queryList(Class<T> clazz) {
    BoundSql boundSql = SqlUtils.buildListSql(null, clazz, this.getFieldMap(),
            this.getNameHandler());
    List<?> list = jdbcTemplate.query(boundSql.getSql(), boundSql.getParams().toArray(),
            this.getRowMapper(clazz));
    return (List<T>) list;
}
@Override
public <T> List<T> queryList(T entity) {
    BoundSql boundSql = SqlUtils.buildListSql(entity, null, this.getFieldMap(),
        this.getNameHandler());
    List<?> list = jdbcTemplate.query(boundSql.getSql(), boundSql.getParams().toArray(),
        this.getRowMapper(entity.getClass()));
    return (List<T>) list;
}

queryList的查询逻辑比较简单,但是拼装sql会复杂一点,因为要处理白名单、黑名单及排序等。

SqlUtils.buildListSql方法:

/**
 * 构建列表查询sql
 *
 * @param entity the entity
 * @param clazz the clazz
 * @param fieldMap the field map
 * @param nameHandler the name handler
 * @return bound sql
 */
public static BoundSql buildListSql(Object entity, Class<?> clazz,
                                    Map<String, Object> fieldMap, NameHandler nameHandler) {
    BoundSql boundSql = SqlUtils.buildQuerySql(entity, clazz, fieldMap, nameHandler);
    String orderBy = (String) fieldMap.get(ORDER_BY);
    if (StringUtils.isNotBlank(orderBy)) {
        boundSql.setSql(boundSql.getSql() + " ORDER BY " + orderBy);
    } else {
        boundSql.setSql(boundSql.getSql() + " ORDER BY " + boundSql.getPrimaryKey() + " DESC");
    }
    return boundSql;
}

查询的sql跟单个查询等是一样的,列表查询只不过最后多了一个排序处理,如果没有指定排序,则按主键倒序排序。

SqlUtils.buildQuerySql方法:

/**
 * 按设置的条件构建查询sql
 *
 * @param entity the entity
 * @param clazz the clazz
 * @param fieldMap the field map
 * @param nameHandler the name handler
 * @return bound sql
 */
public static BoundSql buildQuerySql(Object entity, Class<?> clazz,
                                     Map<String, Object> fieldMap, NameHandler nameHandler) {
    if (clazz == null) {
        clazz = entity.getClass();
    }
    List<AutoField> autoFields = (List<AutoField>) fieldMap.get(AUTO_FIELDS);
    List<String> includeFields = (List<String>) fieldMap.get(INCLUDE_FIELDS);
    List<String> excludeFields = (List<String>) fieldMap.get(EXCLUDE_FIELDS);
    String tableName = nameHandler.getTableName(clazz.getSimpleName());
    String primaryName = nameHandler.getPrimaryName(clazz.getSimpleName());
    List<AutoField> allAutoField = getAllAutoField(entity, autoFields, QUERY);
    String columns = SqlUtils.buildColumnSql(clazz, nameHandler, includeFields, excludeFields);
    StringBuilder querySql = new StringBuilder("SELECT " + columns + " FROM ");
    querySql.append(tableName);
    List<Object> params = Collections.EMPTY_LIST;
    if (!CollectionUtils.isEmpty(allAutoField)) {
        querySql.append(" WHERE ");
        BoundSql boundSql = SqlUtils.builderWhereSql(allAutoField, nameHandler);
        params = boundSql.getParams();
        querySql.append(boundSql.getSql());
    }
    return new BoundSql(querySql.toString(), primaryName, params);
}

这里,先把操作字段,实体类的属性等合并成一个总的AutoField列表,然后再根据这个列表进行sql的拼装。

首先拼装处理拥有黑白名单的列:

/**
 * 构建查询的列sql
 *
 * @param clazz
 * @param nameHandler
 * @param includeField
 * @param excludeField
 * @return
 */
public static String buildColumnSql(Class<?> clazz, NameHandler nameHandler,
                                    List<String> includeField, List<String> excludeField) {
    StringBuilder columns = new StringBuilder();
    //有白名单,直接按白名单走
    if (!CollectionUtils.isEmpty(includeField)) {
        for (String fieldName : includeField) {
            String columnName = nameHandler.getColumnName(fieldName);
            columns.append(columnName);
            columns.append(",");
        }
    } else {
        //获取属性信息
        BeanInfo beanInfo = ClassUtils.getSelfBeanInfo(clazz);
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor pd : pds) {
            String fieldName = pd.getName();
            //忽略黑名单field
            if (!CollectionUtils.isEmpty(excludeField) && excludeField.contains(fieldName)) {
                continue;
            }
            String columnName = nameHandler.getColumnName(fieldName);
            columns.append(columnName);
            columns.append(",");
        }
    }
    columns.deleteCharAt(columns.length() - 1);
    return columns.toString();
}

然后是拼装where条件,拼装时需要注意处理多个值、in、not in、null、or等情况:

/**
 * 构建where条件sql
 *
 * @param autoFields the auto fields
 * @param nameHandler the name handler
 * @return bound sql
 */
private static BoundSql builderWhereSql(List<AutoField> autoFields, NameHandler nameHandler) {
    StringBuilder sql = new StringBuilder();
    List<Object> params = new ArrayList<Object>();
    for (int i = 0; i < autoFields.size(); i++) {
        AutoField autoField = autoFields.get(i);
        if (AutoField.WHERE_FILED != autoField.getType()) {
            continue;
        }
        if (sql.length() > 0) {
            sql.append(" ").append(autoField.getSqlOperator()).append(" ");
        }
        String columnName = nameHandler.getColumnName(autoField.getName());
        Object[] values = autoField.getValues();
        if (StringUtils.equalsIgnoreCase(IN, autoField.getFieldOperator())
            || StringUtils.equalsIgnoreCase(NOT_IN, autoField.getFieldOperator())) {
            //in,not in的情况
            sql.append(columnName).append(" ").append(autoField.getFieldOperator()).append(" ");
            sql.append("(");
            for (int j = 0; j < values.length; j++) {
                sql.append(" ?");
                params.add(values[j]);
                if (j != values.length - 1) {
                    sql.append(",");
                }
            }
            sql.append(")");
        } else if (values == null) {
            //null 值
            sql.append(columnName).append(" ").append(autoField.getFieldOperator())
                .append(" NULL");
        } else if (values.length == 1) {
            //一个值 =
            sql.append(columnName).append(" ").append(autoField.getFieldOperator())
                .append(" ?");
            params.add(values[0]);
        } else {
            //多个值,or的情况
            sql.append("(");
            for (int j = 0; j < values.length; j++) {
                sql.append(columnName).append(" ").append(autoField.getFieldOperator())
                    .append(" ?");
                params.add(values[j]);
                if (j != values.length - 1) {
                    sql.append(" OR ");
                }
            }
            sql.append(")");
        }
    }
    return new BoundSql(sql.toString(), null, params);
}

参数fieldMap就是保存在ThreadLocal中的一些操作信息,代码:

/**
 * 获取操作的属性信息,并回收资源
 *
 * @return
 */
protected Map<String, Object> getFieldMap() {
    List<AutoField> autoFields = AUTO_FIELDS.get();
    List<String> includeFields = INCLUDE_FIELDS.get();
    List<String> excludeFields = EXCLUDE_FIELDS.get();
    String orderBy = LOCAL_ORDER_BY.get();
    Map<String, Object> fieldMap = new HashMap<String, Object>();
    fieldMap.put(SqlUtils.AUTO_FIELDS, autoFields);
    fieldMap.put(SqlUtils.INCLUDE_FIELDS, includeFields);
    fieldMap.put(SqlUtils.EXCLUDE_FIELDS, excludeFields);
    fieldMap.put(SqlUtils.ORDER_BY, orderBy);
    //资源回收
    LOCAL_ORDER_BY.remove();
    INCLUDE_FIELDS.remove();
    EXCLUDE_FIELDS.remove();
    AUTO_FIELDS.remove();
    return fieldMap;
}

在获取之即将它们移除,这是一个好的习惯。

nameHandler如果为空就实例化一个默认的:

/**
 * 获取名称处理器
 *
 * @return
 */
protected NameHandler getNameHandler() {
    if (this.nameHandler == null) {
        this.nameHandler = new DefaultNameHandler();
    }
    return this.nameHandler;
}
上一篇:
下一篇:
5 条评论
akwolf · 1年前
:mrgreen:

回复

ls2005nba · 1年前
思路跟不上,能否提供源码参考下呀呀呀呀呀呀呀呀呀

回复

dding_ch · 1年前
很强啊,求代码学习

回复

zgch1234 · 1年前
请问这个通用superDao能支持多表关联的复杂查询吗?

回复

yunfeiyang0514 · 1年前

楼主请问可以共享下完整的源码,让我们学习下吗?谢谢

回复