- 浏览: 127167 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
cloverprince:
z707208280 写道写多表查询咋整? select 列 ...
[自己动手]用Java的反射实现DAO -
z707208280:
写多表查询咋整? select 列 这里用别名.字段 这咋整手 ...
[自己动手]用Java的反射实现DAO -
亮亮婷婷:
4楼正解,顶!
能在Eclipse 3.7中给m2e提供WTP支持的插件在哪里? -
cloverprince:
沙舟狼客 写道貌似2b青年的代码很有技术含量嗯,简单问题复杂化 ...
普通青年 文艺青年 2B青年 -
沙舟狼客:
貌似2b青年的代码很有技术含量
普通青年 文艺青年 2B青年
(难得,我在JavaEye博客的第一篇Java文章。)
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表:
为了方便,Java里做一个类,存放这个表中的元组:
然后呢,我做一个EmployeeDAO类访问这个Employee表:
为了简单,我们暂且只实现SELECE操作。我们看看我们的上述实现。它创建连接,执行语句,解析结果。其中,有两项和employee表相关:
1. 那个SQL语句是和employee表相关的:“SELECT * FROM employee”。
2. ResultSet的解析也和employee表相关:我们需要知道employee表中有哪些列,也需要知道Employee类中有哪些域。(它们实际上是相对应的)
我们目前只有一个表employee。但是,随着我们增加更多的表,DAO的数据也会增加。
我们加一个project表
然后,紧接着用一个Java类对应这个表:
然后,还需要一个DAO:
我承认上面的代码是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表示一个类。
如何“表示一个类”?
java的语法:<类名>.class,可以得到一个Class类的对象。这里,cls1是一个Class对象,表示int这个数据类型(确切的说int不是类);cls2也是一个Class对象,标识String这个类。
如果已知一个对象:
凡是Object类及其子类的对象,都具有getClass()方法。返回一个Class对象,表示它所属的类。
可以用Class.getName()方法获得类名。
---- java.lang.reflect.Field类 ----
Field类,全称java.lang.reflect.Field,表示一个类中的一个域。
可以用Class.getField()和Class.getFields()方法获得一个类中的域的Field对象。
可以用Field.getName()查看该域的名称,getType()方法获得类型。
注意:Class.getField()只能返回public的域。
注意:Field对象并不和某个对象绑定:它只标识一个“类”中的域,而不是一个“对象”中的域。如果获得某个域的值,或修改一个域,需要指定对象。
==== “内窥”某个对象 ====
这里举一个例子,怎么用以上的Class和Field类,查看一个对象的内部。
--------- 分割线 ---------
有了上述准备,我们可以开始利用“反射”优化我们的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类的定义:
我们通过反射,可以知道这个对象的所有的域的名称和类型。这样,就可以构造SQL语句。然后通过ResultSet.getObject方法获得SQL表中元组的成员,然后用Field.set方法设置对象的域。
---- 用反射重新实现DAO ----
下面,我们写一个新的DAO。这个DAO不与任何一个具体的表(如employee或project)绑定。它可以用于任何表。这样,我们就不用为增加一个表而增加工作量了。
我们不能这样做:我们的表不一定叫employee。所以,需要替换掉。
接下来,是从ResultSet中提取域。
注意:Class.getFields()方法获得的Field对象的顺序是不可预知的。所以,对于列,应该用列名表示,而不是位置。
接下来做一些善后工作就行了。
看,这个代码和具体的表没有直接联系。使用的时候:
---- 插入(INSERT)操作 ----
有了这样的方法,我们也不用怕增加一种操作了。下面是INSERT操作的代码。
------- 分割线 -------
总结一下:Java的反射机制在这种情况下给我带来了很大的方便。优点就是方便,减少工作量;缺点是有运行时效率代价,而且少了一些类型检查。
反射对于懒人尤其适用。我常常以“好的程序员总是懒的“为借口。不过,谁愿意像巨兽一样在焦泥潭里挣扎,越挣扎陷得越深呢?
最后,有个问题还是没有解决:像我们这些学生,很多人很忙碌——实验室的项目、实习、找工作,还有作业、考试……
因为忙,所以没有时间;没有时间,就需要一些快速完成任务的方法;想快速完成人物,就需要恰当的技术(如上述的反射);要技术,就要学习知识;要学习知识,就需要时间……
可是我们没有时间!
所以不能学习新的知识,没有知识也就没有技术,没有技术也就不能快速完成任务,不能完成任务就没有时间,没有时间就忙。越忙,就越没有时间学习,然后就越来越忙。
这是怎样的恶性循环!怎样才能摆脱这个无休止的噩梦呢。。。。。
这就不知道了。试试hibernate?
不过,现在觉得,改用python,手写sql,然后直接返回元组,动态语言简练得多。也就没必要封装了。
重新发明了Spring JDBC,还有Hibernate。见笑了。
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牛逼!
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
写的还不错.
发表评论
-
能在Eclipse 3.7中给m2e提供WTP支持的插件在哪里?
2011-07-09 00:51 9017更新: 在目前(2012年5月19日),安装m2e-wtp不 ... -
spring-security-config:脱机也能运行带spring-security的web程序。
2011-04-21 16:14 3153这是spring xml <beans xmlns= ... -
WARC里的HTTP响应
2011-02-16 17:20 2537WARC是一种格式。Heritrix(http://crawl ... -
ThreadPoolExecutor的陷阱
2010-11-15 23:15 13528下面的程序有什么问题吗? package tpe2; ... -
Executor介绍
2010-11-15 22:39 1372介绍Java的ThreadPoolExecutor ... -
Java词汇
2010-02-10 11:53 1346学习Java语言是几年前,但近期才开始深入学习各种应用,主要是 ...
相关推荐
利用Java的反射机制实现的万能DAO工具类,包含对应的测试代码。具体功能包括:单表查询,多表查询,模糊查询,添加,修改,删除等。利用万能DAO可以对数据库中任意表进行操作,只需一个DAO类即可完成。阅读本代码...
java反射实现数据库增、删、改、查操作Dao
java的基于泛型+反射的通用DAO例子,原创,没事写着玩的,请多指教哈。。。
里面包含java之mvc框架中的dao层反射,dao层里面方法的实现采取的都是反射机制,比较灵活
java DAO模式实现 附源码java DAO模式实现 附源码java DAO模式实现 附源码java DAO模式实现 附源码
java ssh通用DAO另类实现示例 java ssh通用DAO另类实现示例
java_开发Dao层的经典实现
学以致用,用以促学方能增身其能...!如果各位想交个朋友或增加自身能力可以与我联系,大家共同进步!
反射反射反射反射反射反射反射反射反射反射反射反射反射反射反射反射
我的代码注释非常详细,相信当你看完之后,一般来说,如果不是新手(非常菜的人),那么你应该学会使用反射技术来实现封装的动作了--也就是说,你的技术有了一个非常大的提高--如果你看完之后,参见该示例中另外的...
Java通用连接DAO有JDBC连接和JNDI 两种,含源代码。
Java源代码 一个简单的通用DAO实现 (基于hibernate)面向应用层按POJO类缓存hibernate的session对象.使用举例: DAO dao = DAOFactory.getDAO(POJO.class);//获得一个全局类单例的DAO实例 dao.save(pojo); 你也可以...
JAVA反射机制-Class类-Class对象的获取.pdf
java的DAO开发
java中dao层反射使用
Java之JDBC连接数据库实现增删改查(2018 使用Dao层实现 完美封装解决硬编码问题 使用预编译对象PreparedStatement) 配置文件 db.properties(保存数据库账号和密码等) 工具类 JDBCUtil.java(抽取公共部分,解决硬...
一个Java Dao模式的的具体实例,详细描述了Dao的在实际项目中的开发应用
JAVA Dao 数据库操作 添加 删除 修改 查询 初学者 可以看一看
Java之JDBC连接数据库实现增删改查(2018 使用Dao层实现) 实体类:User.java 接口类:IUserDao.java 实现接口类:UserDaoImpl.java 使用Junit4测试增删改查类:UserDaoTest.java
一个简单的JAVA版的DAO生成器,通过反射机制获取bean实体类属性,前提是bean属性都有自动生成的getters和setters。 之前由于一个bean实体类就要写一个DAO,实在是没有效率,就想了这个方法,反射机制获取私有属性的...