Spring JdbcTemplate实现通用的超级dao,比泛型dao更加强大

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

这两天比较忙,没抽出什么时间,这一停顿居然已经有2个评论了,无疑增添了我的不少动力。

在开始之前,先说下前面实现的通用泛型dao,在第四篇 通用自定义转换到JavaBean的RowMapper实现中,把获取属性的操作也放到了mapRow的方法中,这会导致在每一行数据转换的时候都会获取一遍这个类的所有属性信息,虽然有缓存但总还是只获取一次的好,之前有参考这个实现的朋友可以自行优化一下。

顺便提一下,这个实现的dao名,我命名为SuperDao,之前的泛型实现都是以BaseDao的方式来命名,因为它需要有子类来继承它并指定具体的泛型实体类才行,这次的封装不需要任何子类的继承即可使用(即使继承它,子类得到的所有方法都可以直接调用,根本没有必要,完全解耦),又想着换个名字,就取了SuperDao。

前段时间,写了一个基于Spring JdbcTemplate的通用dao,有兴趣的可以看看这个系列,记录了实现过程。

最初的目的只是为了方便自己,没想到发出来之后关注的人还挺多,看来这方面还是有实际需求的。

之前的通用dao在使用过程中,虽然省掉了我很多基础的增删改查代码操作,但是操作还是麻烦,功能也不够强大,碰到一些不大通用但是又经常碰到的情况,完全无力或不怎么使的上力,比如以下几个情况:

1、查询时,需要某个字段不等于(column != value)某个值的时候。

2、查询时,需要某个字段等于多个值(column = value1 or column = value2)的时候。

3、需要以某个或多个字段特定排序的时候。

4、当表中有个字段比较大(clob等大字段及text等),查询时又不需要用到不想返回,节省性能时。

5、分页查询很不方便,当有以某个字段排序等要求时无法实现通用。

6、有时只需要更新某一个字段时,也需要new整个对象。

7、虽然通用,但是每个实体都需要建立一个号称通用的泛型dao,有些简单的dao里面就是空的,感觉很多余。

感受中最主要的就是这几个,其它就不列举了。如果你用过或封装过一些常规的通用dao,相信也会有这些感受。

鉴于以上原因,决定再封装一个更加强大的通用dao出来,可能是我实在太懒了吧,实在不喜欢写这些重复又没技术性的代码。

阅读和参考了网上一些比较优秀的框架思路,发现还是可以实现的。有时候不是做不到,而是根本没想到往这个思路上走。目前已经封装的差不多了,在封装完成之前,连我自己都没想到可以这么方便。个人感觉比之前的通用dao方便和强大的太多了,主要有以下方面的改进:

1、解决了上面列出的所有问题,并且代码量反而更少更简洁了。

2、增加查询字段的黑白名单功能,要查哪些字段由你说了算。

3、单个字段的更新或查询可以直接set、where,不必再new整个对象,并且支持一个字段匹配多个值。

4、排序功能增强,多字段、升序降序自由组合。

5、方便强大的分页功能,无须额外操作,二三行代码搞定分页,自动判断数据库,无须指定。

6、不必再每个实体类对应建立一个继承于通用dao的dao了,一个dao自动判断操作所有表。

先来看一下我设想中,理想的结构图:

SuperDao是各数据库通用操作的实现,其它一些数据库特有的操作可以由具体的子类来实现,这些特有的操作指的是该数据库独有的一些特性,比如Oracle的XMLTYPE字段类型操作。

目前我用的比较多的数据库是Mysql,在使用过程中并没有碰到特有的需要封装的操作(分页在其它地方完成,下面会介绍),所以下面的介绍都是以SuperDao类为主,毕竟那么多数据库也列不完全。

先来看一下SuperDao的主要方法及实现的功能,

SuperDao接口:

package com.mincoder.speed.persistence;
import java.util.List;
/**
 * 通用dao
 *
 * Created by liyd on 6/26/14.
 */
public interface SuperDao {
    /**
     * 添加白名单
     *
     * @param field
     * @return
     */
    public SuperDao include(String... field);
    /**
     * 添加黑名单
     *
     * @param field
     * @return
     */
    public SuperDao exclude(String... field);
    /**
     * asc 排序属性
     *
     * @param field the field
     * @return the super dao
     */
    public SuperDao asc(String... field);
    /**
     * desc 排序属性
     *
     * @param field the field
     * @return the super dao
     */
    public SuperDao desc(String... field);
    /**
     * 设置操作属性
     *
     * @param fieldName the field name
     * @param value the value
     * @return super dao
     */
    public SuperDao set(String fieldName, Object value);
    /**
     * 设置where条件属性
     *
     * @param fieldName
     * @param values
     * @return
     */
    public SuperDao where(String fieldName, Object... values);
    /**
     * 设置where条件属性
     *
     * @param fieldName the field name
     * @param operator the operator
     * @param values the values
     * @return super dao
     */
    public SuperDao whereAssign(String fieldName, String operator, Object... values);
    /**
     * 按设置的参数及条件更新
     *
     * @param clazz
     */
    public void update(Class<?> clazz);
    /**
     * 按设置的条件删除
     * 
     * @param clazz
     */
    public void delete(Class<?> clazz);
    /**
     * 按设置的条件查询
     * 
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> query(Class<T> clazz);
    /**
     * 插入一条记录
     *
     * @param entity
     * @return
     */
    public Long insert(Object entity);
    /**
     * 插入一条记录,不生成主键,主要用于oracle这类非自增长主键类型
     *
     * @param entity
     */
    public void add(Object entity);
    /**
     * 更新一条记录
     *
     * @param entity
     */
    public void update(Object entity);
    /**
     * 删除记录
     *
     * @param clazz the clazz
     * @param id the id
     */
    public void delete(Class<?> clazz, Long id);
    /**
     * 删除记录 此方法会以实体中不为空的字段为条件
     *
     * @param entity
     */
    public void delete(Object entity);
    /**
     * 删除所有记录
     * @param clazz the clazz
     */
    public void deleteAll(Class<?> clazz);
    /**
     * 得到记录
     *
     * @param clazz
     * @param id
     * @return
     */
    public <T> T getById(Class<T> clazz, Long id);
    /**
     * 查询单个记录
     *
     * @param <T>   the type parameter
     * @param entity the entity
     * @return t t
     */
    public <T> T querySingleResult(T entity);
    /**
     * 查询记录数
     *
     * @param entity
     * @return
     */
    public int queryCount(Object entity);
    /**
     * 查询列表
     *
     * @param entity the entity
     * @return the list
     */
    public <T> List<T> queryList(T entity);
    /**
     * 查询所有列表
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> findAll(Class<T> clazz);
}

下面来说明一下各个方法的作用,首先我们来创建一下测试表和对应实体,

建表语句:

CREATE TABLE `BOOK` (
  `BOOK_ID` BIGINT NOT NULL AUTO_INCREMENT,
  `BOOK_NAME` VARCHAR(45) NULL,
  `BOOK_AUTHOR` VARCHAR(45) NULL,
  `GMT_CREATE` DATETIME NULL,
  `GMT_MODIFY` DATETIME NULL,
  PRIMARY KEY (`BOOK_ID`));
CREATE TABLE `USER` (
  `USER_ID` INT NOT NULL AUTO_INCREMENT,
  `USER_NAME` VARCHAR(45) NULL,
  `USER_AGE` INT NULL,
  `GMT_CREATE` DATETIME NULL,
  `GMT_MODIFY` DATETIME NULL,
  PRIMARY KEY (`USER_ID`));

对应实体:

/**
 * Created by liyd on 7/30/14.
 */
public class User extends Page {
    private Long    userId;
    private String  userName;
    private Integer userAge;
    private Date    gmtCreate;
    private Date    gmtModify;
    //getter and setter...
}
/**
 * Created by liyd on 7/30/14.
 */
public class Book extends Page {
    private Long   bookId;
    private String bookName;
    private String bookAuthor;
    private Date   gmtCreate;
    private Date   gmtModify;
    //getter and setter...
}

两个实体类都继承于Page,Page类主要就是为了保存页码等信息,分页时会用到,代码如下:

public class Page implements Serializable {
    /** serialVersionUID */
    private static final long serialVersionUID = 4060766214127186912L;
    /** 每页显示条数 */
    protected int             itemsPerPage     = 20;
    /** 当前页码 */
    protected int             curPage          = 1;
    /** 关键字 */
    protected String          keywords;
    //getter and setter...
}

下面来说说SuperDao具体的使用,它不需要再有所谓通用的泛型类继承于它,直接使用即可。

插入数据:

@Test
public void testInsert() {
    User user = new User();
    user.setUserName("liyd");
    user.setUserAge(18);
    user.setGmtCreate(new Date());
    Long userId = superDao.insert(user);
    System.out.println("insert userId:" + userId);
    Book book = new Book();
    book.setBookName("Java教程");
    book.setBookAuthor("liyd");
    book.setGmtCreate(new Date());
    Long bookId = superDao.insert(book);
    System.out.println("insert bookId:" + bookId);
}

插入数据,统一用superDao.insert(Object entity)方法,可以传入任何的实体对象,方法会自动判断,以上调用将会输出:

insert userId:1
insert bookId:1
//查询数据库,发现数据已经成功插入
MariaDB [test]> select * from USER;
+---------+-----------+----------+---------------------+------------+
| USER_ID | USER_NAME | USER_AGE | GMT_CREATE          | GMT_MODIFY |
+---------+-----------+----------+---------------------+------------+
|       1 | liyd      |       18 | 2014-07-30 21:17:05 | NULL       |
+---------+-----------+----------+---------------------+------------+
MariaDB [test]> select * from BOOK;
+---------+------------+-------------+---------------------+------------+
| BOOK_ID | BOOK_NAME  | BOOK_AUTHOR | GMT_CREATE          | GMT_MODIFY |
+---------+------------+-------------+---------------------+------------+
|       1 | Java教程   | liyd        | 2014-07-30 21:17:05 | NULL       |
+---------+------------+-------------+---------------------+------------+

与insert方法对应的,还有一个add方法,add方法不会自动处理主键id,即insert方法适用于mysql、sql server等这类主键自增的数据库,add方法适用于Oracle这类没有自增主键的数据库。

更新数据

@Test
public void testUpdate() {
    User user = new User();
    user.setUserId(1L);
    user.setUserName("liyd22");
    user.setUserAge(28);
    user.setGmtModify(new Date());
    superDao.update(user);
}

更新数据用update方法,同样可以传入任何的实体对象。需要注意的是这个方法是以主键为更新的where条件的,主键的规范还是和之前的通用dao保持一致,USER表的主键为USER_ID,当然你可以实现自己的nameHandler来改变它。

删除数据

@Test
public void testDeleteById() {
    superDao.delete(User.class, 1L);
}
@Test
public void testDeleteByEntity() {
    User user = new User();
    user.setUserId(1L);
    superDao.delete(user);
}

删除数据有两个delete方法,直接传入实体class对象和主键值,或者new一个实体设置主键值后传入,个人比较喜欢前一个。

删除所有数据

@Test
public void testDeleteAll() {
    superDao.deleteAll(User.class);
}

该调用将会删除USER表的所有数据,慎用。删除所有数据采用的是TRUNCATE,因此删除后自增的主键id也会从新开始计算。

根据id查询

@Test
public void testGetById() {
    User user = superDao.getById(User.class, 1L);
    System.out.println(user.getUserId());
    System.out.println(user.getUserName());
    System.out.println(user.getUserAge());
}

查询单个结果

@Test
public void testQuerySingleResult() {
    User user = new User();
    user.setUserName("liyd");
    user = superDao.querySingleResult(user);
    System.out.println(user.getUserId());
    System.out.println(user.getUserName());
    System.out.println(user.getUserAge());
}

这个方法适合只有一个结果的情况。因为jdbcTemplate自带的queryForObject等方法在没有结果时会抛出异常,所以这个底层仍使用了查询列表的方式,当有多个结果时取第一个。

查询结果数量

@Test
public void testQueryCount() {
    User user = new User();
    user.setUserName("liyd");
    int count = superDao.queryCount(user);
    System.out.println(count);
}

同样,可以传入任何的实体对象。

查询列表

@Test
public void testQueryList() {
    User user = new User();
    user.setUserName("liyd");
    List<User> userList = superDao.queryList(user);
}

传入的,仍然可以是任何的实体对象。

以上这些基本的操作方法虽然有所改变,但是变化并不大,方便的也有限。接下来要讲的就是如何对一些方法组合使用了,还有分页功能等。如果你够细心的话,可能早就发现了怎么没有封装分页的方法。别急,不需要特地的封装分页方法,在查询列表时直接就可以实现,这也是方便的地方,同样它还可以和别的方法组合使用!

写的没力了,明天继续。。。

上一篇:
下一篇:
3 条评论
jLion · 1年前
最喜jdbcTemplate,坐等楼主续文。

回复

guarder · 1年前
赞一个,等着下文。

回复

398626166 · 1年前
如果多表操作,该怎么操作呢?!

回复