`
cloverprince
  • 浏览: 129807 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

[自己动手]用Java的反射实现DAO

    博客分类:
  • Java
阅读更多
(难得,我在JavaEye博客的第一篇Java文章。)

Java: 一种语言。具体的说:
引用
来自http://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html

1996 - James Gosling invents Java. Java is a relatively verbose, garbage collected, class based, statically typed, single dispatch, object oriented language with single implementation inheritance and multiple interface inheritance. Sun loudly heralds Java's novelty.

西元1996年,詹姆斯·高斯林(James Gosling)发明了Java语言。Java是一个相对罗嗦一点的有垃圾回收的基于类的静态类型的单指派的单实现继承的多接口继承的面向对象的语言。【译注:咳……咳……咳咳……我靠,憋死我了】。Sun公司高声宣布:Java牛逼!


Reflection:反射。具体的说,就是允许程序在运行时查询“某个对象属于什么类?这个类有什么域?有什么方法?”并且可以根据以上查询,修改某个域,或者调用某个方法。

Data Access Object:简称DAO,数据访问对象。一种设计模式,将上层应用中的数据访问和下层的数据库管理系统(DBMS)分离开。我的理解:DAO的一个方面就是让上层应用不用再关心怎么写SQL语句。

写数据库程序免不了和DBMS打交道,尤其是RDBMS。然后,也许每次访问数据都要写一堆SQL语句。反正我是不太喜欢直接写SQL,尤其是当表的数量很大的时候,可能要给每个表写至少4个SQL语句(也就是CRUD:Create(INSERT),Read(SELECT),Update,Delete)。一个有6个表的小数据库就够让我copy&paste一共20次。随着数据库规模扩大,工作量复杂度虽然是线性的,但是这个乘数太大了(起码对于我这种应付课程作业,不能全职coding的学生而言),而且修改的代价也是线性的。

程序里假设有这样的模型:
数据库里有employee表:
CREATE TABLE employee (
    id INTEGER PRIMARY KEY,
    ename VARCHAR(32),
    salary FLOAT,
    birth_date DATE
);


为了方便,Java里做一个类,存放这个表中的元组:
class Employee {
    public int id;
    public String ename;
    public double salary;
    public java.sql.Date birth_date; // 原谅我没有服从Java的命名规范。
    // 因为我的数据库就是这么写的。(这有办法补救,可以做到既服从Java的namingConvention,又满足数据库中的不同命名)
}


然后呢,我做一个EmployeeDAO类访问这个Employee表:
class EmployeeDAO {
    public static ArrayList<Employee> select() throws SQLException {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        ArrayList<Employee> al = null;
        try {
            conn = DriverManager.getConnection("jdbc:....",null);
            st = conn.createStatement();
            rs = st.executeQuery("select * from employee");

            al = new ArrayList<Employee>();

            while (rs.next()) {
                Employee e = new Employee();
                e.id = rs.getInt("id");
                e.ename = rs.getString("ename");
                e.salary = rs.getFloat("salary");
                e.birth_date = rs.getDate("birth_date");
                al.add(e);
            }
        } finally {
            if (rs != null)
                rs.close();
            if (st != null)
                st.close();
            if (conn != null)
                conn.close();
        }

        return al;
    }    
}

为了简单,我们暂且只实现SELECE操作。我们看看我们的上述实现。它创建连接,执行语句,解析结果。其中,有两项和employee表相关:
1. 那个SQL语句是和employee表相关的:“SELECT * FROM employee”。
2. ResultSet的解析也和employee表相关:我们需要知道employee表中有哪些列,也需要知道Employee类中有哪些域。(它们实际上是相对应的)

我们目前只有一个表employee。但是,随着我们增加更多的表,DAO的数据也会增加。

我们加一个project表
CREATE TABLE project (
    id INTEGER PRIMARY KEY,
    pname STRING,
    leader_id INTEGER
);


然后,紧接着用一个Java类对应这个表:
class Project {
    public int id;
    public String pname;
    public int leader_id;
}


然后,还需要一个DAO:
public class ProjectDAO {
    public static ArrayList<Project> select() throws SQLException {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        ArrayList<Project> al = null;
        try {
            conn = DriverManager.getConnection("jdbc:....",null);
            st = conn.createStatement();
            rs = st.executeQuery("select * from project");

            al = new ArrayList<Project>();

            while (rs.next()) {
                Project p = new Project();
                p.id = rs.getInt("id");
                p.pname = rs.getString("pname");
                p.salary = rs.getInt("leader_id");
                al.add(p);
            }
        } finally {
            if (rs != null)
                rs.close();
            if (st != null)
                st.close();
            if (conn != null)
                conn.close();
        }

        return al;
    }    
}

我承认上面的代码是copy&paste了EmployeeDAO的代码。仔细观察,其实区别也只有两部分:
1. SQL语句中的employee变成了project
2. ResultSet的处理,只是列/域不同而已。


------- 分割线 -------


对于懒惰的我,怎么会忍心copy&paste呢?如果有更多的表,每个表有更多的操作,要改多少地方?一致性怎么维护?改错了怎么办?

下面介绍Java的“反射”:Reflection

“反射,简单的说,就是在运行时查看某个类有什么成员”。它可以列举所有的域、方法、内部类、枚举等等,并可以修改/调用/实例化它们。

---- java.lang.Class类 ----

在Java里有一个特殊的类叫Class,全称是java.lang.Class。注意C是大写的,小写的class是Java的一个关键字。这个类的每个对象表示Java中的每一个类。有点绕?这么解释吧;如果
Class cls;

那么,Class是一个类。cls是Class类的一个对象。每个cls表示一个类。

如何“表示一个类”?
Class cls1 = int.class;
Class cls2 = String.class;

java的语法:<类名>.class,可以得到一个Class类的对象。这里,cls1是一个Class对象,表示int这个数据类型(确切的说int不是类);cls2也是一个Class对象,标识String这个类。

如果已知一个对象:
Object obj = new Object();
String str = "blah";
Employee emp = new Employee();

Class cls1 = obj.getClass();
Class cls2 = str.getClass();
Class cls3 = emp.getClass();

凡是Object类及其子类的对象,都具有getClass()方法。返回一个Class对象,表示它所属的类。

可以用Class.getName()方法获得类名。
cls1.getName() // "java.lang.Object"
cls1.getSimpleName() // "Object"


---- java.lang.reflect.Field类 ----

Field类,全称java.lang.reflect.Field,表示一个类中的一个域。

可以用Class.getField()和Class.getFields()方法获得一个类中的域的Field对象。
可以用Field.getName()查看该域的名称,getType()方法获得类型。
注意:Class.getField()只能返回public的域。
Class cls = Employee.class;
Field[] fields = cls.getFields();
Field field = cls.getField("ename");
field.getName(); // "ename"
field.getType(); // 返回值等于String.class


注意:Field对象并不和某个对象绑定:它只标识一个“类”中的域,而不是一个“对象”中的域。如果获得某个域的值,或修改一个域,需要指定对象。
Employee e = new Employee();
Class cls = e.getClass();

Field f1 = cls.getField("id");
Field f2 = cls.getField("ename");
Field f3 = cls.getField("salary");

int id = f1.getInt(e);
String ename = f2.get(e); // 注意:字符串是对象;对象一律用get取值
double salary = f3.getDouble(e);
Double salary2 = f3.get(e); // 用封装的基础类型也可以。

f1.setInt(e,1);
f2.set(e, "foobar"); // 对象一律用set赋值
f3.setDouble(e, 345.67);
f3.set(e, new Double(345.67)); // 用封装的基础类型也可以。


==== “内窥”某个对象 ====

这里举一个例子,怎么用以上的Class和Field类,查看一个对象的内部。

public void introspectObject(Object obj) throws Exception {
  Class cls = obj.getClass();  // 先获得Class对象
  System.out.println(cls.getName());  // 打印类名。对象是没有名字的,但类有。

  Field[] fields = cls.getFields(); // 获得域
  for(Field field : fields) {
    String name = field.getName(); // 获得域名
    Object value = field.get(obj); // 获得域值
    String valueStr = value==null?"null":value.toString(); // 对null要特殊处理。
    System.out.format("%s: %s\n", name, value);
  }
}



--------- 分割线 ---------


有了上述准备,我们可以开始利用“反射”优化我们的DAO了。

好啦,现在就是从copy&paste的噩梦中逃脱出来的时候了!

---- Java数据类型与SQL数据类型的对应关系 ----

每种SQL数据类型都有对应的Java数据类型。请参考你的DBMS的手册。这里以Apache Derby为例:
SQL的INTEGER,对应Java的int。
SQL的FLOAT,对应Java的double。
SQL的VARCHAR,对应Java的String。
SQL的DATE,对应Java的java.sql.Date。

所以,设计对象的时候要注意这一点。比如Employee类的birth_date域的类型是java.sql.Date。

而ResultSet有getObject和setObject两个方法,可以略过列的数据类型。

我们再看看Employee类的定义:
class Employee {
    public int id;
    public String ename;
    public double salary;
    public java.sql.Date birth_date;
}


我们通过反射,可以知道这个对象的所有的域的名称和类型。这样,就可以构造SQL语句。然后通过ResultSet.getObject方法获得SQL表中元组的成员,然后用Field.set方法设置对象的域。

---- 用反射重新实现DAO ----

下面,我们写一个新的DAO。这个DAO不与任何一个具体的表(如employee或project)绑定。它可以用于任何表。这样,我们就不用为增加一个表而增加工作量了。

public class GenericDAO {
    private final Class tableClass;

    public GenericDAO(Class cls) {
        this.tableClass = cls; // 我们需要记录对象对应的类的Class对象
    }

    public static ArrayList<? extends Object> select() throws Exception {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        ArrayList<Object> al = null;
        try {
            conn = DriverManager.getConnection("jdbc:....",null);
            st = conn.createStatement();
            // rs = st.executeQuery("select * from employee");

我们不能这样做:我们的表不一定叫employee。所以,需要替换掉。
            rs = st.executeQuery(String.format(
                 "select * from %s",tableClass.getSimpleName()
                 ));

接下来,是从ResultSet中提取域。
注意:Class.getFields()方法获得的Field对象的顺序是不可预知的。所以,对于列,应该用列名表示,而不是位置。
            al = new ArrayList<Object>();

            Field[] fields = tableClass.getFields(); // 获得域列表

            while (rs.next()) {
                Project p = new Project();
                for (Field field : fields) {
                    Object value = rs.getObject(field.getName());
                    field.set(p, value);  // 设定域值
                }
                al.add(p);
            }

接下来做一些善后工作就行了。
        } finally {
            if (rs != null)
                rs.close();
            if (st != null)
                st.close();
            if (conn != null)
                conn.close();
        }

        return al;
    }    
}


看,这个代码和具体的表没有直接联系。使用的时候:
ArrayList<Employee> employees = new genericDAO(Employee.class).select();
ArrayList<Project> projects = new genericDAO(Project.class).select();


---- 插入(INSERT)操作 ----

有了这样的方法,我们也不用怕增加一种操作了。下面是INSERT操作的代码。
protected void insert(Object obj) throws Exception {
    Connection conn = null;
    PreparedStatement st = null;
    try {
        conn = DbProvider.getConnection(site);

        Field[] fields = tableClass.getFields();

        // 下面一段代码准备SQL语句的两部分。
        StringBuilder sb1 = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();

        for (int i = 0; i < fields.length; i++) {
            if(i>0) {
                sb1.append(",");
                sb2.append(",");
            }
            sb1.append(fields[i].getName());
            sb2.append("?");
        }

        String commaSeparatedFieldNames = sb1.toString();
        String commaSeparatedQuestionMarks = sb2.toString();

        // 安全起见,我们需要用prepareStatement处理用户输入。
        // 但是因为类的名称是可以由程序员控制的,我们用String.format生成语句
        st = conn.prepareStatement(String.format(
                    "INSERT INTO %s(%s) values(%s)",
                    tableClass.getSimpleName(), commaSeparatedFieldNames,
                    commaSeparatedQuestionMarks));

        // 然后,填充这个PreparedStatement
        for (int i = 0; i < fields.length; i++) {
            st.setObject(i + 1, fields[i].get(obj));
        }

        st.executeUpdate();
    } finally {
        if (st != null)
            st.close();
        if (conn != null) {
            conn.close();
        }
    }
}



------- 分割线 -------

总结一下:Java的反射机制在这种情况下给我带来了很大的方便。优点就是方便,减少工作量;缺点是有运行时效率代价,而且少了一些类型检查。

反射对于懒人尤其适用。我常常以“好的程序员总是懒的“为借口。不过,谁愿意像巨兽一样在焦泥潭里挣扎,越挣扎陷得越深呢?

最后,有个问题还是没有解决:像我们这些学生,很多人很忙碌——实验室的项目、实习、找工作,还有作业、考试……
引用

因为忙,所以没有时间;没有时间,就需要一些快速完成任务的方法;想快速完成人物,就需要恰当的技术(如上述的反射);要技术,就要学习知识;要学习知识,就需要时间……

可是我们没有时间!

所以不能学习新的知识,没有知识也就没有技术,没有技术也就不能快速完成任务,不能完成任务就没有时间,没有时间就忙。越忙,就越没有时间学习,然后就越来越忙。


这是怎样的恶性循环!怎样才能摆脱这个无休止的噩梦呢。。。。。




分享到:
评论
4 楼 cloverprince 2013-06-08  
z707208280 写道
写多表查询咋整? select 列 这里用别名.字段 这咋整手写啊? 我查询的时候不查询* 而是反射实体里所有的列 作为查询字段


这就不知道了。试试hibernate?

不过,现在觉得,改用python,手写sql,然后直接返回元组,动态语言简练得多。也就没必要封装了。
3 楼 z707208280 2013-05-06  
写多表查询咋整? select 列 这里用别名.字段 这咋整手写啊? 我查询的时候不查询* 而是反射实体里所有的列 作为查询字段
2 楼 cloverprince 2011-07-29  
zhou363667565 写道
写的还不错.

重新发明了Spring JDBC,还有Hibernate。见笑了。
1 楼 zhou363667565 2011-07-26  
写的还不错.

相关推荐

    利用Java反射实现万能DAO

    利用Java的反射机制实现的万能DAO工具类,包含对应的测试代码。具体功能包括:单表查询,多表查询,模糊查询,添加,修改,删除等。利用万能DAO可以对数据库中任意表进行操作,只需一个DAO类即可完成。阅读本代码...

    java反射实现数据库操作Dao

    java反射实现数据库增、删、改、查操作Dao

    java 基于泛型与反射的通用 DAO

    综上所述,这个项目展示了如何利用Java的泛型和反射技术实现一个通用的DAO,使得数据库操作更加灵活和易于维护。泛型确保了类型安全,而反射则提供了运行时的动态行为。这种设计模式在实际开发中非常常见,尤其是在...

    JAVA反射的实现(使DAO层变得更加简单)

    总的来说,Java反射使得DAO层的实现变得更加灵活和简洁。它减少了代码的重复,提高了代码的复用性,同时也增强了程序的动态性和扩展性。然而,需要注意的是,反射操作的性能相对较低,且可能带来安全风险,因此在...

    反射机制反射Dao 反射机制

    在Java中,使用反射可以实现通用的DAO,无需为每种数据表创建特定的DAO类。通过传入不同的类名,可以动态创建对应数据表的DAO实例,执行CRUD操作。 7. **安全性与性能**: 反射虽然强大,但也有其缺点。由于绕过了...

    Java反射泛型,实现数据库的动态增删改查等功能

    具体实现时,我们可以为每个数据库操作创建一个泛型方法,使用反射获取实体类的字段,根据字段生成对应的SQL语句片段。比如在插入操作中,我们可以遍历`T`的所有字段,构建一个`INSERT INTO table_name (field1, ...

    JDBCTemplate+JavaPOJO实现通用DAO

    在这个"JDBCTemplate+JavaPOJO实现通用DAO"的项目中,我们将探讨如何利用这两者构建一个通用的DAO层。 首先,Java POJO(Plain Old Java Object)是指那些没有特殊约束的简单Java对象,通常用于表示数据库中的实体...

    mvc中dao层反射实现

    在本主题“mvc中dao层反射实现”中,我们将探讨如何利用Java的反射机制来实现DAO层的方法。 反射是Java语言的一个强大特性,它允许程序在运行时动态地获取类的信息并调用其方法。在DAO层中使用反射,可以提高代码的...

    自定义Dao,反射实现

    2. **执行SQL语句**:使用Java的JDBC API,通过`Class.forName()`加载数据库驱动,`Connection`的`prepareStatement()`或`createStatement()`方法创建`Statement`或`PreparedStatement`对象,然后利用反射调用`...

    java好用的dao

    "java好用的dao"这个标题暗示我们将讨论一个方便、高效的Java DAO实现,而"autodao"标签则指向了一个特定的库——AutoDAO,它自动为开发者生成DAO类,简化了开发过程。 AutoDAO是一个轻量级的开源Java库,它的主要...

    JAVA反射实现数据层框架

    4. **插件化开发**:如Spring AOP(面向切面编程)利用反射实现动态代理,可以在不修改源代码的情况下对方法进行增强。 5. **序列化与反序列化**:某些库可能会使用反射来实现对象的序列化和反序列化,如JSON转换...

    java DAO模式实现 附源码

    **Java DAO模式实现详解** DAO(Data Access Object)模式是一种常用的设计模式,它在软件开发中主要用于数据库操作的抽象和封装。DAO模式的核心思想是将数据访问层与业务逻辑层解耦,使得业务代码不直接与数据库...

    利用java反射、注解及泛型模拟ORM实现

    4. **执行SQL**:使用Java的JDBC API执行SQL,处理结果集,并将数据转换为Java对象,或将Java对象转换为数据库记录。 5. **泛型接口**:创建泛型的DAO接口,如`GenericDAO&lt;T&gt;`,其中T代表任何实现了ORM注解的实体类...

    java ssh通用DAO另类实现示例

    3. 使用Java的动态代理技术创建一个`DAOProxy`类,它在运行时生成具体DAO的代理实例。代理类将在调用实际方法时处理事务管理和Session管理。 ```java public class DAOProxy&lt;T&gt; implements InvocationHandler { ...

    Java中的反射实现数据库操作

    在Java编程语言中,反射是一种强大的机制,它允许我们在运行时检查类、接口、字段和方法的信息,并且能够在不知道对象具体类型的情况下调用其方法或访问其属性。反射是Java提供的一种动态类型功能,这对于创建灵活、...

    java中dao层反射使用.doc

    Java 中 DAO 层反射使用是指在 DAO 层中使用反射机制来实现数据访问和操作。反射机制可以动态地获取类的信息、字段信息、方法信息,并可以动态地调用方法和创建对象。通过反射机制,可以简化 DAO 层的实现,提高开发...

    Java反射技术的一般应用

    我的代码注释非常详细,相信当你看完之后,一般来说,如果不是新手(非常菜的人),那么你应该学会使用反射技术来实现封装的动作了--也就是说,你的技术有了一个非常大的提高--如果你看完之后,参见该示例中另外的...

    java_开发Dao层的经典实现

    ### Java开发DAO层的经典实现详解 #### 一、引言 在Java开发中,DAO(Data Access Object)层是用于处理数据库操作的关键组件之一。它主要负责与数据库进行交互,执行增删改查等基本操作,并将数据封装为实体对象...

    java dao模式搭建教程

    创建DAO接口的实现类,如`UserDaoImpl.java`,在这里编写实际的SQL语句或者使用ORM框架如Hibernate、MyBatis来实现数据操作。 7. **配置数据源** 配置数据源连接,如在Spring框架中,通过`applicationContext.xml...

    JDBC_Java反射技术入门

    在这个“JDBC_Java反射技术入门”资源中,初学者可以了解到如何使用Java进行数据库操作的基础知识,包括连接数据库、执行SQL语句、处理查询结果等。 1. **JDBC基础**: - **加载驱动**:在使用JDBC之前,我们需要...

Global site tag (gtag.js) - Google Analytics