对象存储的通用增删改查操作

hl.wang

发布于 2019.02.09 10:41 阅读 2477 评论 0

提出问题:目前,我们在完成项目的时候发现每一个entity对应数据库的增删改查都需要写一个dao,这样只是对操作的表不同但是完成的事情是一样了,当表过多时大大的增加了耗费的时间,那设想我们能不能有一个通用的类可以完成对所有表的正删改查呢?

分析问题:我们既然需要一个通用的类来对所有的表进行增删改查,那么代表我们在传入参数的时候可以让他为任何entity,我们发现泛型可以实现这个功能,可是我们还需要对每一个对象进行操作,那么我们又需要确定传入的泛型是哪一个对象,经过学习发现这个工作可以通过反射来实现,那我们是不是或许就可以通过泛型和反射来实现呢,下面我们来试试看!

案例演示:

首先我们来看一下service的设计每一个方法的参数都是利用泛型,因为我们是一个通用的对象的增删改查,我们并不确定是对哪一个对象的操作,所以我们就用T来代替所有的对象,具体的对象在实现类的操作,同样查询的时候我们也无法判断查询的是哪一个对象,所以也用T来代替:

public interface BaseDao<T> {
void add(T t);
void delete(T t);
void update(T t);
List<T> select(T t);
}

 

接下来我们定义好我们实现类所需要全局变量:

public class BaseDaoImpl<T> implements BaseDao<T>{
private Class<T> classes;
private Connection con = new JDBCUtils().getUtils();
private PreparedStatement ps ;
private static final String SQL_INSERT="insert";
private static final String SQL_DELETE="delete";
private static final String SQL_UPDATE="update";
private static final String SQL_SELETE="select";

 

 

 

 

 

 

 

添加功能首先需要通过getClass()方法确定传入的泛型t到底是哪一个class,我们要操作哪一个对象,class的getSimpleName()可以让我们得到具体操作的对象名称,Class的getDeclaredFields()方法可以让我们得到我们自己给这个对象定义的所有私有属性有什么,我们还需要用过拼接字符串来拼接sql语句,具体的实现我们一起来看看吧:

@Override
    public void add(T t1) {
        this.classes = (Class<T>) t1.getClass();
        //获得到该class中自己声明的字段例如:自己定义私有属性
        Field[] field=classes.getDeclaredFields();
        //sb用来拼接sql语句
        StringBuffer sb  = new StringBuffer(SQL_INSERT);
        sb.append(" "+"into"+" ");
        sb.append(classes.getSimpleName()+"(");
    
        for(int i = 0;i<field.length;i++) {
            //获得到我们自己定义的属性例如shop类,这里获得到的就是,name,price
            sb.append(field[i].getName()+",");
        }
            int n = sb.lastIndexOf(",");
            sb.setLength(n);
            sb.append(")"+" "+"values(");
            for(int i = 0;i<field.length;i++) {
                sb.append("?,");
            }
            //去除末尾逗号
            int m = sb.lastIndexOf(",");
            sb.setLength(m);
            sb.append(")");
            try {
                
                ps = con.prepareStatement(sb.toString());
                
                for(int i  = 0;i<field.length;i++) {
                    String str = field[i].getName();
                    //因为我们需要或得到传入的值因此需要用到反射来调用get方法获得值,这里是用来拼接所需要属性的get方法例如getName
                    Method method = classes.getDeclaredMethod("get"+str.replaceFirst(str.substring(0, 1),str.substring(0, 1).toUpperCase()));
                    
                    ps.setObject(i+1,method.invoke(t1));
                }
                ps.executeUpdate();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

 看完添加的实现是不是发现也不是很难呢,增删改查添加的看明白了后面的基本上都是一个道理了。

 

 

 

 

 

 

删除的时候呢我们同样需要确定我们要删除的对象是哪一个,然后要通过反射通过class的getDeclaredMethod来调用get方法得到要删除的对象的id,然后拼接出来sql语句,最后提交,我们一起来看一下吧:

@Override
    public void delete(T t) {
        this.classes = (Class<T>) t.getClass();
        Field[] field = classes.getDeclaredFields();
        StringBuffer sb = new StringBuffer(SQL_DELETE);
        sb.append(" "+"from");
        sb.append(" "+classes.getSimpleName());
        sb.append(" "+"where id = ?");
        try {
            ps = con.prepareStatement(sb.toString());
            for(int i = 0;i<field.length;i++) {
                if("id".equals(field[i].getName())) {
                    Method method = classes.getDeclaredMethod("getId");
                    ps.setObject(1, method.invoke(t));
                }
            }
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }

 

 

 

 

 

 

 

更新和添加呢大同小异,只是更新需要一个where条件来确定需要更新哪一个对象,但是目前更新只能做到全部更新无法做到局部更新,至于如何局部更新还有待你们自己去探索,最需要注意的是更新也需要通过getDeclaredMethod()方法调用对象所有属性的get方法,重要的是例如要getName(),Name的首字母需要大写。下面我们来看一下如何操作:

@Override
    public void update(T t) {
        this.classes = (Class<T>) t.getClass();
        StringBuffer sb = new StringBuffer(SQL_UPDATE);
        Field[] fields = classes.getDeclaredFields();
        sb.append(" "+classes.getSimpleName());
        sb.append(" "+"set");
        for(int i = 0;i<fields.length;i++) {
            sb.append(" "+fields[i].getName()+"=?,");
        }
        int n = sb.lastIndexOf(",");
        sb.setLength(n);
        sb.append(" "+"where id = ?");
        try {
            ps = con.prepareStatement(sb.toString());
            for(int i = 0;i<fields.length;i++) {
                String str =fields[i].getName();
                Method method = classes.getDeclaredMethod("get"+str.replaceFirst(str.substring(0, 1), str.substring(0,1).toUpperCase()));
                ps.setObject(i+1, method.invoke(t));
            }
            Method method  = classes.getDeclaredMethod("getId");
            ps.setObject(fields.length+1,method.invoke(t));
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

 

 

 

 

 

 

最后我们来看一下查询的实现,当然也需要拼接sql语句,查询还需要判断要查询的对象是哪个,要查询的属性有什么,查询出来之后需要用过field的set方法给我们新建立的对象赋值,然后将这个对象放到list之中,返回一个list,下面我们来看看代码如何实现:

@Override
    public List<T> select(T t) {
        List<T> list  = new ArrayList<>();
        this.classes = (Class<T>) t.getClass();
        StringBuffer sb = new StringBuffer(SQL_SELETE);
        sb.append(" "+"*"+" "+"from");
        sb.append(" "+classes.getSimpleName());
        ResultSet rs= null;
    
        Field[] fields = classes.getDeclaredFields();
        try {
            ps = con.prepareStatement(sb.toString());
            rs = ps.executeQuery();
                
                while(rs.next()) {
                    t = classes.newInstance();
                    for(int j = 0;j<fields.length;j++) {
                        //这个方法是用来让下面的set方法可以给我们的t中的属性赋值
                        fields[j].setAccessible(true);
                        //该方法相当于shop中的setName,t代表是要给t这个类赋值,后面是值
                        fields[j].set(t, rs.getObject(fields[j].getName()));
                    }
                    //把查到的t加入到集合当中
                    list.add(t);
                }
                
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

 

 

 

 

现在我们来测试一下看看到底能不能使用:

首先是添加功能shop中有三个成员变量id,name,price,user中为id,username,password:

public class Test {
    
public static void main(String[] args) {
     BaseDao baseDao = new BaseDaoImpl();
     baseDao.add(new User("whl","123"));
     baseDao.add(new Shop("汽车",1000000));
}
}

我们发现添加成功了。

 

 

 

然后我们来看一下修改功能:

public static void main(String[] args) {
     BaseDao baseDao = new BaseDaoImpl();
//     baseDao.add(new User("whl","123"));
//     baseDao.add(new Shop("汽车",1000000));
     baseDao.update(new User(1,"whl","123456"));
}
}

我们发现也修改成功了。

然后我们来看一下查询功能:

public static void main(String[] args) {
     BaseDao baseDao = new BaseDaoImpl();
//     baseDao.add(new User("whl","123"));
//     baseDao.add(new Shop("汽车",1000000));
//     baseDao.update(new User(1,"whl","123456"));
     List<Shop> list=baseDao.select(new Shop());
     for(Shop shop:list) {
         System.out.println(shop);
     }
}
}

我们发现也查询到了。

 

 

最后我们看一下删除功能的测试:

public static void main(String[] args) {
     BaseDao baseDao = new BaseDaoImpl();
//     baseDao.add(new User("whl","123"));
//     baseDao.add(new Shop("汽车",1000000));
//     baseDao.update(new User(1,"whl","123456"));
//     List<Shop> list=baseDao.select(new Shop());
//     for(Shop shop:list) {
//         System.out.println(shop);
//     }
     baseDao.delete(new Shop(1));
}
}

删除成功了,说明我们的想法是可以的,通过泛型和反射是可以实现用一个类对所有对象操作。

总结:最后我们发现其实整体思路就是

1、我们要将传入的t用getClass()方法赋给一个Class。 

2、通过getSimpleName()方法确定传入的到底是哪一个entity。

3、用一个getDeclaredFields()获得到我们自己所定义的私有属性,然后我们来拼接sql语句。

4、添加的时候我们需要知道传入的这个类到底有几个属性,我们可以遍历上面获得到的私有属性那个数组来得到,包括私有属性有什么,长度是什么,然后我们需要用反射来调用我们获取到的class的get方法得到传入的t中的值。

5、执行sql语句就可以了,删除的时候比添加更简单我们只需要拿到一个id不需要和添加的时候拿到所有的值,然后执行拼接的sql与就可以了。

6、更新的时候和添加基本上一样只是多可一个where条件,在获取一遍id就可以了,只是目前更新只有全部更新,不能做到局部更新,要完成局部更新的话非常之麻烦。

7、查找中需要注意就是field[i]的setAccessible(true)这个方法不要忘不然下面的set()方法会报错,最后我们把查询到的t放入到集合之中就可以了。

8、我觉得这里面对于数据的操作有很多相似之处可以抽离出来,会在研究一下,也希望师兄们和老师给我多提意见