对象存储的通用增删改查操作之自定义映射关系

hl.wang

发布于 2019.02.11 17:22 阅读 2263 评论 1

提出问题:之前我们介绍了一种通用的对象的增删改查存储方式,但是他的必须要求数据库表名与类名相同,表的字段名与类的属性相同,但是在实际开发当中我们发现有很多时候是无法满足这些条件的,那么有没有可能我们不需要上面所提到的两种对应也可以完成对对象的增删改查呢?

分析问题:如果你学习了mybatis你会发现,好像mybatis就是这样,那么我们仔细发现,mybatis是通过xml文件来建立对应关系,以此来解决我们上面所提出的问题,那么我们是否也可以仿照mybatis也在xml文件中配置两者之间的对应关系然后通过java代码解析xml文件来实现呢,下面让我们一起来看看吧!

解决问题:

既然要利用xml文件来关联两者之间的关系,那我们当然要先设计好xml文件,我这里利用peoperties对应数据库里的字段来设计的xml文件,具体设计我们一起来看:

<?xml version="1.0" encoding="UTF-8"?>
<tables>
    <table key="src/main/java/entity/Use.java" value="user">
        <column key="id" value="id"/>
         <column key="name" value="username"/>
         <column key="password" value="password"/>
    </table>
    <table id="src/main/java/entity/Shop.java" value="shop">
            <column key="id" value="id"/>
            <column key="name" value="name"/>
            <column key="price" value="price"/>
        </table>
</tables>

在这里<beans>、<bean>、<column>这些标签都是自己起的名字并且层级也是自己设定,包括它里面包含的id,properties都是自己定义的名字。xml文件设计好了那么下面我们来看一下应该如何解析吧!

 

 

 

 

首先还是要看一下services:

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

我们发现services还是没有改变,那我们就不多啰嗦了,接下来就进去正题,进入到我们解析xml文件的主要部分,实现类的编写。但是这次首先要看一下我们写的一个工具方法,用来完成将所有子标签传入进去判断传进来的类中是否有与字标签中对应的类没有则返回null。:

 

public Element isSameElement(List<Element> childs) {
    //读取我们对象所在的包位置
    File classFile =new File("src/main/java/entity/"+classes.getSimpleName()+".java");
    for(Element element:childs) {
        String className =classFile.getPath().substring(classFile.getPath().lastIndexOf("\\")+1, classFile.getPath().length());
        String name = element.attributeValue("key").substring(element.attributeValue("key").lastIndexOf("/")+1, element.attributeValue("key").length());    
        //判断操作的对象与xml<bean></bean>标签的key中映射的类是不是同一个
        if(className.equals(name)) {
            //相同则返回这个标签
            return element;
        }
    }        
    return null;
}

 

 

好了下面我们看一下需要的全局变量:

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";
private SAXReader reader = new SAXReader();
File file = new File("demo.xml");//读取我们刚才编写的xml文件
private Document document; //dom对象,后面用来获得根标签
private List<Element> eList ;//用来存储三级标签

 

 

下面我们来看一下添加功能的实现,添加功能主要就是先获得dom对象,然后通过dom对象来获得根节点所谓的根标签。然后通过根标签获得子标签,再通过子标签获得子标签的过程。再通过attributeValue()获得自己命名的xml标签属性的值是什么,好了我们来看看具体怎么实现吧:

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"+" ");
        
        try {
            //获得到document对象
            document = reader.read(file);
        } catch (DocumentException e1) {
            e1.printStackTrace();
        }
        //利用dom对象获得到根元素也就是根标签
        Element rootElement  = document.getRootElement();
        //通过跟标签获得到他的所有子标签(并不能获得到孙子标签)
        List<Element> childs = rootElement.elements();
        //将所有子标签传入进去判断传进来的类中是否有与字标签中对应的类没有则返回null
        Element element = isSameElement(childs);
        if(element != null) {
            //获得到对应的表名拼接到sql语句中
            sb.append(element.attributeValue("value")+"(");
            //获得所有的孙子标签
            eList = element.elements();
            for(Element element2 :eList) {
                //获得到对应的数据库中的字段名拼接到sql语句中
                sb.append(element2.attributeValue("value")+",");
            }
        int n = sb.lastIndexOf(",");
        sb.setLength(n);
        sb.append(")"+" "+"values(");
                for(int i = 0;i<eList.size();i++) {
                    sb.append("?,");
                }
            
        //去除末尾逗号
        int m = sb.lastIndexOf(",");
        sb.setLength(m);
        sb.append(")");
        try {
            
            ps = con.prepareStatement(sb.toString());
            
            for(int i  = 0;i<eList.size();i++) {
                //或得到定义的实体类中的私有属性
                String str = eList.get(i).attributeValue("key");
                //因为我们需要或得到传入的值因此需要用到反射来调用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();
        }
        }
                
    }

 添加功能会了之后,其余的三个功能就是大同小异了,一定也会很好理解了,就不一一详细叙述。相信你明白了添加之后剩下三个功能自己都可以完成了。

 

 

 

 

接下来是删除功能的实现:

public void delete(T t) {
        this.classes = (Class<T>) t.getClass();
        Field[] field = classes.getDeclaredFields();
        StringBuffer sb = new StringBuffer(SQL_DELETE);
        sb.append(" "+"from"+" ");
        try {
            document = reader.read(file);
            Element rootElement = document.getRootElement();
            List<Element> childs = rootElement.elements();
            Element element = isSameElement(childs);
            if(element != null) {
            sb.append(element.attributeValue("value"));
            eList = element.elements();
            }
        } catch (DocumentException e1) {
            e1.printStackTrace();
        }
        sb.append(" "+"where id = ?");
        try {
            ps = con.prepareStatement(sb.toString());
            for(int i = 0;i<eList.size();i++) {
                if("id".equals(eList.get(i).attributeValue("value"))) {
                    Method method = classes.getDeclaredMethod("getId");
                    ps.setObject(1, method.invoke(t));
                }
            }
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }

 

 

 

 

然后是修改更能的实现:

public void update(T t) {
        this.classes = (Class<T>) t.getClass();
        StringBuffer sb = new StringBuffer(SQL_UPDATE+" ");
        Field[] fields = classes.getDeclaredFields();
        try {
            document = reader.read(file);
        } catch (DocumentException e2) {
            e2.printStackTrace();
        }
        Element root  =document.getRootElement();
        List<Element> childs = root.elements();
        Element element  = isSameElement(childs);
        if(element != null) {
            sb.append(element.attributeValue("value"));
            eList = element.elements();
            sb.append(" "+"set");
            for(int i = 0;i<eList.size();i++) {
                sb.append(" "+eList.get(i).attributeValue("value")+"=?,");
            }
        int n = sb.lastIndexOf(",");
        sb.setLength(n);
        sb.append(" "+"where id = ?");
        try {
            ps = con.prepareStatement(sb.toString());
            for(int i = 0;i<eList.size();i++) {
                String str =eList.get(i).attributeValue("key");
                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();
        }
        }
    
    }

 

 

 

 

最后就是查询功能的实现了:

public List<T> select(T t) {
        List<T> list  = new ArrayList<T>();
        this.classes = (Class<T>) t.getClass();
        StringBuffer sb = new StringBuffer(SQL_SELETE);
        sb.append(" "+"*"+" "+"from"+" ");
        try {
            document = reader.read(file);
            
        } catch (DocumentException e1) {
            e1.printStackTrace();
        }
        Element root = document.getRootElement();
        List<Element> childs =  root.elements();
        Element element  = isSameElement(childs);
        if(element != null) {
            sb.append(element.attributeValue("value"));
            eList = element.elements();
        }
        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<eList.size();j++) {
                        //这个方法是用来让下面的set方法可以给我们的t中的属性赋值
                        fields[j].setAccessible(true);
                        //该方法相当于shop中的setName,t代表是要给t这个类赋值,后面是值
                        fields[j].set(t, rs.getObject(eList.get(j).attributeValue("value")));
                    }
                    //把查到的t加入到集合当中
                    list.add(t);
                }
                
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

 

 

 

 

 

最后我们来测试一下吧测试的时候特意用了一个Use类属性为name和password,数据库中表为user,字段为username,password:

首先是添加功能:

public static void main(String[] args) {
     BaseDao baseDao = new BaseDaoImpl();
     Use user = new Use();
     user.setName("zxx");
     user.setPassword("123");
     user.setId(2);
     baseDao.add(user);
}
}

我们发现测试成功了。

 

 

 

 

下面是修改功能:

public static void main(String[] args) {
     BaseDao baseDao = new BaseDaoImpl();
     Use user = new Use();
     user.setName("zxx");
     user.setPassword("123456");
     user.setId(2);
     baseDao.update(user);
}
}

我们发现也成了。

 

 

 

然后是查询功能:

public class Demo {
    
public static void main(String[] args) {
     BaseDao baseDao = new BaseDaoImpl();
//     Use user = new Use();
//     user.setName("zxx");
//     user.setPassword("123456");
//     user.setId(2);
     List<Use> list = baseDao.select(new Use());
     for(Use use:list) {
         System.out.println(use);
     }
}
}

我们发现查询也成功了

 

 

 

 

最后是删除功能:

public class Demo {
    
public static void main(String[] args) {
     BaseDao baseDao = new BaseDaoImpl();
     Use user = new Use();
     user.setName("zxx");
     user.setPassword("123456");
     user.setId(2);
     baseDao.delete(user);
}
}

哇删除功能也成功了,现在增删改查都测试成功了,那我们总结一下吧。

 

 

 

 

 

总结:

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

2、通过我们写的isSameElement()方法确定传入的到底是哪一个entity对应的是哪一个表。

3、通过elements()得到三级标签,获得到我们自己所定义的私有属性和数据库中对应的表字段名字,然后我们来拼接sql语句。

4、添加的时候我们需要知道传入的这个类到底有几个属性,我们可以通过三级标签集合长度来确定,然后我们需要通过读取xml文件来或得到数据库中的字段所对应的类中的属性时什么,最后我们需要用反射来调用我们获取到的class的get方法得到传入的t中的值。

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

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

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