本文隶属于专题系列: Spring JdbcTemplate实现通用的泛型dao

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

在项目中一直使用Mybaits,最近想自己搞个小项目,通过对比之后发现mybatis的优势并不是十分明显了,个人小项目倾向于更加简洁的DBUtils。Spring jdbc也实现了DBUtils基本一样的功能,项目中又刚好用到了spring,这当然是不二之选了。 Mybaits采用xml的方式,想要实现通用的dao比较的麻烦,spring jdbc没有类似xml的配置文件,实现一个通用的dao反而简单,参考网上的一些介绍结合自己项目的需求,写了一个算是比较规范的通用dao,这里作下整理。 

先来说一下我的通用dao实现的主要几点:

  1. 小项目使用的是mysql,所以是基于mysql数据库,但是差别不大,换数据库的话改几行代码就搞定。
  2. 可以自定义数据库表名、列名到Java类、属性名称的转换。主要是我设计的数据库表中,列名为了防止关键字等各类冲突,都是以下划线”_”开头和分隔的,无法和Java映射实体类的属性名保持一致。
  3. 实现基本的增、删、改、查、分页查询等功能。
  4. 因为没有xml等映射配置文件,所以基于编码和命名的规范性(当然不规范也能支持,但是实现自定义名称处理器将会十分复杂)。所有的数据库列名也好,Java属性名也好,映射都是有一定的规范的,不可能说我数据库列名为user_name,java实体类中映射为tomcat。
  5. 我数据库表的主键名是基于表名再加上“_id”,例如user表主键即为_user_id(所有列以下划线”_”开头)。
  6. 映射的实体类中不能添加数据库表中列不存在的无用字段,这样会使组装的sql语句错误。当然可以用添加注解或其它的方式来判断,但那样会破坏实体类的简洁度。任何情况下不在映射的实体类中添加数据库没有的字段(某些框架的主外键映射除外),如果需要在转换后的VO类中添加,个人认为这是一个好的编码习惯。
  7. 基于spring JdbcTemplate。

下面一步一步的来实现我们的通用dao(在最后有给出完整的代码),接口在这里就不晒了,定义了几个方法的名字而已,先来看一下通用dao实现的定义:

public abstract class BaseDaoImpl<T> implements BaseDao<T> {
    /** 具体操作的实体类对象 */
    private Class<T>       entityClass;

    /** 名称加工处理器 */
    private NameHandler    nameHandler;

    /** spring jdbcTemplate 对象 */
    @Autowired
    protected JdbcTemplate jdbcTemplate;
}

把类声明为了abstract类型,因为这个类我们是不需要直接实例化的,我们使用实例化的是继承它的子类。 另外在声明类的时候添加了一个泛型参数,这个T即是需要操作的实体类对象。 entityClass用来保存运行时T的具体类型。 nameHandler即自定义名称处理器。 JdbcTemplate spring的JdbcTemplate 对象,注意这里加了注解@Autowired,并声明为protected作用域,这样继承它的子类在使用spring注解声明bean初始化的时候会帮你自动注入jdbcTemplate。当然你也可以使用xml。 我们先来举一个使用的例子,需要实现一个用户表对应的dao,可以像下面声明:

public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao {
}

声明的这个UserDaoImpl不用写任何代码就拥有了对user表操作基本的增删改查功能,可以省略大量的重复工作。回到正题,声明了User这个泛型对象,要对User表进行操作,这就要求在运行时能够动态的得到User这个对象,因为这个User对象根据实现dao的不同是会变的,怎么在运行时得到它呢?我们来看一下public Type getGenericSuperclass()这个方法,doc上说它用来返回表示当前Class 所表示的实体(类、接口、基本类型或 void)的直接超类的Type,好,有了这个,就能拿到我们想要的了,在UserDaoImpl的构造方法中写入如下代码,在类实例化的时候就拿到具体的实体类型:

/**
 * 构造方法,获取运行时的具体实体对象
 */
public BaseDaoImpl() {
    Type superclass = getClass().getGenericSuperclass();
    ParameterizedType type = (ParameterizedType) superclass;
    entityClass = (Class<T>) type.getActualTypeArguments()[0];
}

基本的工作都做完了,接下来实现业务方法,先来实现相对复杂点的insert,说insert复杂是因为它比其它操作多一了个主键的生成,mysql主键的生成由数据库负责比较简单,主要是保存记录后要返回这个主键以供后面可能需要的其它操作使用,所以不能用简单的调用update或execute方法传入sql完事了,spring提供了KeyHolder的实现,看下面代码:

/**
 * 插入一条记录
 *
 * @param entity
 */
@Override
public Long insert(T entity) {
    final SqlContext sqlContext = SqlUtils.buildInsertSql(entity, this.getActualNameHandler());
    KeyHolder keyHolder = new GeneratedKeyHolder();
    jdbcTemplate.update(new PreparedStatementCreator() {
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            PreparedStatement ps = con.prepareStatement(sqlContext.getSql().toString(),
                new String[] { sqlContext.getPrimaryKey() });
            int index = 0;
            for (Object param : sqlContext.getParams()) {
                index++;
                ps.setObject(index, param);
            }
            return ps;
        }
    }, keyHolder);
    return keyHolder.getKey().longValue();
}

代码很简单,主键将在保存后返回。实现了insert方法,其它的就简单了,下面是整个类的源码:

/**
 * 泛型通用dao实现 依赖于spring jdbc
 *
 * User: liyd
 * Date: 2/12/14
 * Time: 2:34 PM
 */
public abstract class BaseDaoImpl<T> implements BaseDao<T> {

    /** 具体操作的实体类对象 */
    private Class<T>       entityClass;

    /** 名称加工处理器 */
    private NameHandler    nameHandler;

    /** spring jdbcTemplate 对象 */
    @Autowired
    protected JdbcTemplate jdbcTemplate;

    /**
     * 构造方法,获取运行时的具体实体对象
     */
    public BaseDaoImpl() {
        Type superclass = getClass().getGenericSuperclass();
        ParameterizedType type = (ParameterizedType) superclass;
        entityClass = (Class<T>) type.getActualTypeArguments()[0];
    }

    /**
     * 获取实际运行时的名称处理器
     *
     * @return
     */
    private NameHandler getActualNameHandler() {
        if (nameHandler == null) {
            synchronized (this) {
                if (nameHandler == null) {
                    nameHandler = this.getNameHandler();
                }
            }
        }
        return nameHandler;
    }

    /**
     * 得到名称处理器,子类覆盖此方法实现自己的名称转换处理器
     *
     * @return
     */
    protected NameHandler getNameHandler() {
        return new DefaultNameHandler();
    }

    /**
     * 插入一条记录
     *
     * @param entity
     */
    @Override
    public Long insert(T entity) {
        final SqlContext sqlContext = SqlUtils.buildInsertSql(entity, this.getActualNameHandler());
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                PreparedStatement ps = con.prepareStatement(sqlContext.getSql().toString(),
                    new String[] { sqlContext.getPrimaryKey() });
                int index = 0;
                for (Object param : sqlContext.getParams()) {
                    index++;
                    ps.setObject(index, param);
                }
                return ps;
            }
        }, keyHolder);
        return keyHolder.getKey().longValue();
    }

    /**
     * 更新记录
     *
     * @param entity
     */
    @Override
    public void update(T entity) {
        SqlContext sqlContext = SqlUtils.buildUpdateSql(entity, this.getActualNameHandler());
        jdbcTemplate.update(sqlContext.getSql().toString(), sqlContext.getParams().toArray());
    }

    /**
     * 删除记录
     *
     * @param id
     */
    @Override
    public void delete(Serializable id) {
        String tableName = this.getActualNameHandler().getTableName(entityClass.getSimpleName());
        String primaryName = this.getNameHandler().getPrimaryName(entityClass.getSimpleName());
        String sql = "DELETE FROM " + tableName + " WHERE " + primaryName + " = ?";
        jdbcTemplate.update(sql, id);
    }

    /**
     * 删除所有记录
     */
    @Override
    public void deleteAll() {
        String tableName = this.getActualNameHandler().getTableName(entityClass.getSimpleName());
        String sql = " TRUNCATE TABLE " + tableName;
        jdbcTemplate.execute(sql);
    }

    /**
     * 得到记录
     *
     * @param id
     * @return
     */
    @Override
    public T getById(Serializable id) {
        String tableName = this.getNameHandler().getTableName(entityClass.getSimpleName());
        String primaryName = this.getNameHandler().getPrimaryName(entityClass.getSimpleName());
        String sql = "SELECT * FROM " + tableName + " WHERE " + primaryName + " = ?";
        return (T) jdbcTemplate.query(sql,
            new DefaultRowMapper(entityClass, this.getActualNameHandler()), id).get(0);
    }

    /**
     * 查询所有记录
     *
     * @return
     */
    @Override
    public List<T> findAll() {
        String sql = "SELECT * FROM "
                     + this.getActualNameHandler().getTableName(entityClass.getSimpleName());
        return (List<T>) jdbcTemplate.query(sql,
            new DefaultRowMapper(entityClass, this.getActualNameHandler()));
    }

    /**
     * 查询记录数
     *
     * @param entity
     * @return
     */
    public int queryCount(T entity) {
        String tableName = this.getActualNameHandler().getTableName(entityClass.getSimpleName());
        StringBuilder countSql = new StringBuilder("select count(*) from ");
        countSql.append(tableName);
        SqlContext sqlContext = SqlUtils.buildQueryCondition(entity, this.getActualNameHandler());
        if (sqlContext.getSql().length() > 0) {
            countSql.append(" where ");
            countSql.append(sqlContext.getSql());
        }
        return jdbcTemplate.queryForInt(countSql.toString(), sqlContext.getParams().toArray());
    }

    /**
     * 查询分页列表
     *
     * @param entity
     * @return
     */
    public Pager queryPageList(T entity) {
        Pager pager = new Pager();
        PagingOrder pagingOrder = (PagingOrder) entity;
        pager.setCurPage(pagingOrder.getCurPage());
        pager.setItemsPerPage(pagingOrder.getItemsPerPage());

        String tableName = this.getActualNameHandler().getTableName(entityClass.getSimpleName());
        String primaryName = this.getActualNameHandler()
            .getPrimaryName(entityClass.getSimpleName());
        StringBuilder querySql = new StringBuilder("select * from ");
        StringBuilder countSql = new StringBuilder("select count(*) from ");
        querySql.append(tableName);
        countSql.append(tableName);
        //不调用queryCount方法,条件共同组装一次,减少反射获取的次数
        SqlContext sqlContext = SqlUtils.buildQueryCondition(entity, this.getActualNameHandler());
        if (sqlContext.getSql().length() > 0) {
            querySql.append(" where ");
            countSql.append(" where ");
            querySql.append(sqlContext.getSql());
            countSql.append(sqlContext.getSql());
        }
        querySql.append(" order by ");
        querySql.append(primaryName);
        querySql.append(" desc ");
        querySql.append("limit ?,?");
        List<Object> queryParams = new ArrayList<Object>(sqlContext.getParams());
        queryParams.add(pager.getBeginIndex());
        queryParams.add(pager.getItemsPerPage());

        List<T> list = (List<T>) jdbcTemplate.query(querySql.toString(), queryParams.toArray(),
            new DefaultRowMapper(entityClass, this.getActualNameHandler()));

        int totalCount = jdbcTemplate.queryForInt(countSql.toString(), sqlContext.getParams()
            .toArray());
        pager.setList(list);
        pager.setItems(totalCount);
        return pager;
    }
}

这样,一个通用dao的实现思路已经清晰给出了。可能你注意到了里面的几个细节部分的几个方法,

SqlUtils.buildUpdateSql(entity, this.getActualNameHandler()); 

构建动态的sql,getActualNameHandler获取具体的名称处理器实现属性名到列名的转换。 (List) jdbcTemplate.query(sql, new DefaultRowMapper(entityClass, this.getActualNameHandler()));

 其中DefaultRowMapper是自定义实现的数据到实体对象的转换。

篇幅所限,具体见其专门的介绍。

你可能感兴趣的内容
Spring 之 JMS 监听JMS消息 收藏,4403 浏览
Spring 之 JMS 基于JMS的RPC 收藏,3253 浏览
Spring的BeanFactory和FactoryBean 收藏,10404 浏览
1条评论
tscnd3 1年前
学习学习

selfly

交流QQ群:32261424
Owner