利用Java反射机制+泛型重新封装的BaseDao
前段时间仿着C#写了个Java版的BaseDao,呵呵,感觉真烂!后来看到有学长利用反射+泛型封装的BaseDao后,心里甚感不快。从来没去探究过Java的反射机制,也从来没想到过自动数据类型映射的做法。于是乎,自己琢磨了下,弄了一个自己的版本,感觉有收获了。实践得真知,我糅合了下自己的理解,以及别人做法的的优点,写了这个版本。感觉有点问题,但还是先帖出来,作为下一个版本的参照目标。
BaseDao源码清单(负责SQL命令执行及泛型pojo集合的返回):
package com.china.codingmouse.cmsdk4j.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.Vector; import com.china.codingmouse.cmsdk4j.util.SQLParamHelper; /** * BaseDao 泛型数据库访问基类 * @author CodingMouse * @version 1.0.0.1 2009-3-26 * @param
DBConnection 源码清单(负责从创建数据库连接,使用了同步):
package com.china.codingmouse.cmsdk4j.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import com.china.codingmouse.cmsdk4j.util.DBConfigReader; /** * DBConnection 与特定数据库的连接(会话)对象管理类 * @author CodingMouse * @version 1.0.0.1 2009-3-26 */ public final class DBConnection { private static Connection conn = null; // 数据库连接对象 /** * 私有化默认构造器 */ private DBConnection() { // 获取数据库连接对象 getConnection(); } /** * 同步创建与特定数据库连接(会话)对象的方法 */ private static synchronized void createConnection() { try { // 从DBConfig.xml配置文件中同步读取数据库驱动Url、连接Url String[] drvierAndUrl = DBConfigReader.ReadDBConfig(); if (conn == null || conn.isClosed()) { // 将指定字串的数据驱动加载到JVM(Java虚拟机环境) Class.forName(drvierAndUrl[0]); // 获取数据库连接 conn = DriverManager.getConnection(drvierAndUrl[1]); } } catch (ClassNotFoundException ex) { System.err.println("异常信息:无法找到特定数据库驱动类错误!/r/n" + ex.getMessage()); } catch (SQLException ex) { System.err.println("异常信息:特定数据库访问及其他相关错误!/r/n" + ex.getMessage()); } } /** * 获取与特定数据库连接(会话)对象的方法 * @return 数据库连接对象 */ public static Connection getConnection() { // 同步创建数据库连接对象 createConnection(); // 返回数据库连接对象 return conn; } /** * 开始数据库连接对象的事务 */ public void beginTrans() { try { if (conn != null && !conn.isClosed() && conn.getAutoCommit()) { // 禁用事务自动提交(设置事务手动提交) conn.setAutoCommit(false); } } catch (SQLException ex) { System.err.println("异常信息:数据库连接开始事务时发生错误!/r/n" + ex.getMessage()); } } /** * 提交数据库连接对象的事务 */ public void commitTrans() { try { if (conn != null && !conn.isClosed() && !conn.getAutoCommit()) { // 提交事务 conn.commit(); // 启用事务自动提交 conn.setAutoCommit(true); } } catch (SQLException ex) { System.err.println("异常信息:数据库连接提交事务时发生错误!/r/n" + ex.getMessage()); } } /** * 回滚数据库连接对象的事务 */ public void rollbackTrans() { try { if (conn != null && !conn.isClosed() && !conn.getAutoCommit()) { // 回滚事务 conn.rollback(); // 启用事务自动提交 conn.setAutoCommit(true); } } catch (SQLException ex) { System.err.println("异常信息:数据库连接回滚事务时发生错误!/r/n" + ex.getMessage()); } } }
DBConfigReader 源码清单(负责XML配置文件的内容读取,没用使用三方jar包,仅用了JDK内置的XML相关类):
package com.china.codingmouse.cmsdk4j.util; import java.io.IOException; import java.io.InputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * DBConfigReader 数据库配置文件阅读工具类 * @author CodingMouse * @version 1.0.0.1 2009-3-26 */ public final class DBConfigReader { /** * 同步读取数据库配置文件内容 * @return 仅有两个元素的配置信息内容字串数组(0下标-数据库驱动Url,1下标-数据库连接Url) */ public static synchronized String[] ReadDBConfig() { // 从DBConfig.xml配置文件中读取数据库驱动Url、连接Url String[] driverAndUrl = new String[2]; try { // 获取字节输入流对象 // InputStream xmlInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("DBConfig.xml"); InputStream xmlInputStream = DBConfigReader.class.getClassLoader().getResourceAsStream("DBConfig.xml"); // 创建XML解析器并获取DocumentBuilderFactory的对象 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // 获取能解析xml文件为文档的的DocumentBuilder对象 DocumentBuilder db = dbf.newDocumentBuilder(); // 将指定的xml文件解析成文档对象 Document doc = db.parse(xmlInputStream); // 获取文档的根节点元素 Element element = doc.getDocumentElement(); // 获取根节点元素集合 NodeList nodelist = element.getChildNodes(); // 保存当前数据库服务器名称 String currentDBServer = null; // 遍历整个根节点(
SQLParamHelper 源码清单(负责Java数据类型与SQL参数数据类型的自动映射,写得有点细,但我觉得细化些好,部分类型还未进行测试):
package com.china.codingmouse.cmsdk4j.util; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; /** * SQLParamHelper SQL参数辅助工具类 * @author CodingMouse * @version 1.0.0.1 2009-3-26 */ public final class SQLParamHelper { /** * 自动将Java数据类型参数映射转换为SQL参数 * @param param Java数据类型参数数组 * @param ps SQL命令执行对象 * @return 返回自动匹配添加好SQL参数的SQl命令执行对象 */ public static PreparedStatement JavaParam2SQLParam(Object[] param, PreparedStatement ps) { // 使用Class.isInstance()方法判定指定的 Object // 是否与此 Class 所表示的对象赋值兼容。 // 此方法是 Java 语言 instanceof 运算符的动态等效方法。 // 如果当前SQL命令包含参数则进行Java数据类型与SQL命令参数的人工映射 if (param != null && param.length != 0) { try { for (int i = 0; i < param.length; i++) { // 将第i个参数的Java数据类型通过其对应的包装器类人工映射并设定SQL命令参数 if (Boolean.class.isInstance(param[i])) { // 映射boolean类型 ps.setBoolean(i + 1, Boolean.parseBoolean(param[i].toString())); } else if (Byte.class.isInstance(param[i])) { // 映射byte类型 ps.setByte(i + 1, Byte.parseByte(param[i].toString())); } else if (byte[].class.isInstance(param[i])) { ps.setBytes(i + 1, (param[i].toString()).getBytes()); // 映射byte[]类型 } else if (Character.class.isInstance(param[i]) || String.class.isInstance(param[i])) { // 映射char和String类型 ps.setString(i + 1, String.valueOf(param[i])); } else if (Short.class.isInstance(param[i])) { // 映射short类型 ps.setShort(i + 1, Short.parseShort(param[i].toString())); } else if (Integer.class.isInstance(param[i])) { // 映射int类型 ps.setInt(i + 1, Integer.parseInt(param[i].toString())); } else if (Long.class.isInstance(param[i])) { // 映射long类型 ps.setLong(i + 1, Long.parseLong(param[i].toString())); } else if (Float.class.isInstance(param[i])) { // 映射float类型 ps.setFloat(i + 1, Float.parseFloat(param[i].toString())); } else if (Double.class.isInstance(param[i])) { // 映射double类型 ps.setDouble(i + 1, Double.parseDouble(param[i].toString())); } else if (BigDecimal.class.isInstance(param[i])) { // 映射BigDecimal类型 ps.setBigDecimal(i + 1, new BigDecimal(param[i].toString())); } else if (Date.class.isInstance(param[i])) { // 映射Date类型 ps.setDate(i + 1, Date.valueOf(param[i].toString())); } else if (Time.class.isInstance(param[i])) { // 映射Time类型 ps.setTime(i + 1, Time.valueOf(param[i].toString())); } else if (Timestamp.class.isInstance(param[i])) { // 映射Timestamp类型 ps.setTimestamp(i + 1, Timestamp.valueOf(param[i].toString())); } else { throw new Exception("Java程序中包含不支持的自动映射数据类型:" + param[i].getClass().getName()); } } } catch(SQLException ex) { System.err.println("异常信息:特定数据库访问及其他相关错误!/r/n" + ex.getMessage()); } catch (Exception ex) { System.err.println("异常信息:程序兼容问题!/r/n" + ex.getMessage()); } } // 返回结果 return ps; } /** * 自动将SQL数据类型参数映射转换为Java数据类型 * @param
DBConfig.xml 文件内容(负责保存各类数据库驱动完整包名+类名及连接字串):
------------------------------------------------------------------------------------
以下是相关的测试类源码清单!
------------------------------------------------------------------------------------
UserPojo 源码清单(测试一个简单的Pojo):
package com.china.codingmouse.cmsdk4j.example.pojo; import java.sql.Timestamp; /** * UserPojo 用户信息实体类 * @author CodingMouse * @version 1.0.0.1 2009-3-29 */ public class UserPojo { private int id; // 用户ID private String name; // 用户姓名 private boolean sex; // 用户性别 private int age; // 用户年龄 private String address; // 用户住址 private Timestamp regTime; // 用户注册时间 /** * 默认构造器 */ public UserPojo() { super(); } /** * 参数化构造器 * @param id 用户ID * @param name 用户姓名 * @param sex 用户性别 * @param age 用户年龄 * @param address 用户住址 * @param regTime 用户注册时间 */ public UserPojo(int id, String name, boolean sex, int age, String address, Timestamp regTime) { super(); this.setId(id); this.setName(name); this.setSex(sex); this.setAge(age); this.setAddress(address); this.setRegTime(regTime); } /** * 用户ID取值方法 * @return 用户ID */ public int getId() { return id; } /** * 用户ID赋值方法 * @param id 用户ID */ public void setId(int id) { this.id = id; } /** * 用户姓名取值方法 * @return 用户姓名 */ public String getName() { return name; } /** * 用户姓名赋值方法 * @param name 用户姓名 */ public void setName(String name) { this.name = name; } /** * 用户性别取值方法 * @return 用户性别 */ public boolean getSex() { return sex; } /** * 用户性别赋值方法 * @param sex 用户性别 */ public void setSex(boolean sex) { this.sex = sex; } /** * 用户年龄取值方法 * @return 用户年龄 */ public int getAge() { return age; } /** * 用户年龄赋值方法 * @param age 用户年龄 */ public void setAge(int age) { this.age = age; } /** * 用户住址取值方法 * @return 用户住址 */ public String getAddress() { return address; } /** * 用户住址赋值方法 * @param address 用户住址 */ public void setAddress(String address) { this.address = address; } /** * 用户注册时间取值方法 * @return 用户注册时间 */ public Timestamp getRegTime() { return regTime; } /** * 用户注册时间赋值方法 * @param regTime 用户注册时间 */ public void setRegTime(Timestamp regTime) { this.regTime = regTime; } /** * 重写equals方法 */ @Override public boolean equals(Object obj) { // 自身比较 if (obj == this) { return true; } // 类型相同 if (obj.getClass() == this.getClass()) { // 强转类型以匹配属性 UserPojo up = (UserPojo)obj; if (up.getId() == this.getId() && up.getName().equals(this.getName()) && up.getSex() == this.getSex() && up.getAge() == this.getAge() && up.getAddress().equals(this.getAddress()) && up.getRegTime().equals(this.getRegTime())) { return true; } } // 未通过相等比较则返回逻辑假 return false; } /** * 重写hashCode方法 */ @Override public int hashCode() { // 生成简单的位运算hash散列码 String key = this.toString(); int prime = key.hashCode(); int hash = prime; for (int i = 0; i < key.length(); i++) { hash ^= (hash << 23 >> 17) ^ key.charAt(i) * 13131; } // 返回结果 return (hash % prime) * 33; } /** * 重写toString方法 */ @Override public String toString() { // 采用数组的toString方式输出 return "[" + this.getId() + ", " + this.getName() + ", " + this.getSex() + ", " + this.getAge() + ", " + this.getAddress() + ", " + this.getRegTime() + "]"; } }
对UserPojo中hashCode及equals方法重写的测试:
package com.china.codingmouse.cmsdk4j.example.pojo.test; import java.sql.Timestamp; import com.china.codingmouse.cmsdk4j.example.pojo.UserPojo; /** * 用户信息实体类Equals与HashCode方法测试类 * @author CodingMouse * @version 1.0.0.1 2009-3-29 */ public class UserPojoEqualsAndHashCodeTest { /** * 测试类主方法 * @param args */ public static void main(String[] args) { UserPojo up1 = new UserPojo(3, "邓超", true, 25, "四川隆昌", new Timestamp(System.currentTimeMillis())); UserPojo up2 = new UserPojo(3, "邓超", true, 25, "四川隆昌", new Timestamp(System.currentTimeMillis())); System.out.println("User1的内容:" + up1); System.out.println("User2的内容:" + up2); System.err.println("User1的散列码:" + up1.hashCode()); System.err.println("User2的散列码:" +up2.hashCode()); System.out.println("测试User1与User2地址(==)相等:" + (up1 == up2)); System.out.println("测试User1与User2内容(equals)相等:" + up1.equals(up2)); UserPojo up3 = new UserPojo(6, "CodingMouse", false, 22, "中华人民共和国四川成都", new Timestamp(System.currentTimeMillis())); UserPojo up4 = new UserPojo(13, "Michael Jackson", false, 53, "美利坚合众国纽约市唐人街", new Timestamp(System.currentTimeMillis())); System.out.println("User3的内容:" + up3); System.out.println("User4的内容:" + up4); System.err.println("User3的散列码:" +up3.hashCode()); System.err.println("User4的散列码:" +up4.hashCode()); System.out.println("测试User3与User4地址(==)相等:" + (up3 == up4)); System.out.println("测试User3与User4内容(equals)相等:" + up3.equals(up4)); } }
UserPojo测试类输出结果:
User1的内容:[3, 邓超, true, 25, 四川隆昌, 2009-03-30 16:53:13.995] User2的内容:[3, 邓超, true, 25, 四川隆昌, 2009-03-30 16:53:13.995] User1的散列码:-619793365 User2的散列码:-619793365 测试User1与User2地址(==)相等:false 测试User1与User2内容(equals)相等:true User3的内容:[6, CodingMouse, false, 22, 中华人民共和国四川成都, 2009-03-30 16:53:13.999] User4的内容:[13, Michael Jackson, false, 53, 美利坚合众国纽约市唐人街, 2009-03-30 16:53:13.999] User3的散列码:-37057156 User4的散列码:-714365817 测试User3与User4地址(==)相等:false 测试User3与User4内容(equals)相等:false
UserDao源码清单(负责通知BaseDao类型参数及添加相关业务逻辑代码,这里由于无实际业务逻辑,仅以注释说明):
package com.china.codingmouse.cmsdk4j.example.dao; import java.util.Vector; import com.china.codingmouse.cmsdk4j.dao.BaseDao; import com.china.codingmouse.cmsdk4j.example.pojo.UserPojo; /** * UserDao 用户信息数据访问类 * @author CodingMouse * @version 1.0.0.1 2009-3-29 */ public class UserDao extends BaseDao
UserDaoTest源码清单(测试了一下简单的CRUD操作):
package com.china.codingmouse.cmsdk4j.example.dao.test; import java.sql.Timestamp; import java.util.Vector; import com.china.codingmouse.cmsdk4j.example.dao.UserDao; import com.china.codingmouse.cmsdk4j.example.pojo.UserPojo; /** * UserDaoTest 用户信息数据访问类测试类 * @author CodingMouse * @version 1.0.0.1 2009-3-29 */ public class UserDaoTest { // 创建用户信息实体数据访问类实例 UserDao ud = null; /** * 默认构造器 */ public UserDaoTest() { this.ud = new UserDao(); } /** * 测试类主方法 * @param args */ public static void main(String[] args) { // 创建自身的实例 UserDaoTest userDaoTest = new UserDaoTest(); // 显示数据 System.err.println("数据库中的原始数据:"); userDaoTest.showMessage(); // 添加一条数据 if (userDaoTest.ud.executeUpdate("insert into [User] values(?, ?, ?, ?, ?)", new Object[] {"陈九", 0, 19, "成都市纱帽街" + (int)(Math.random() * 100) + "号", new Timestamp(System.currentTimeMillis())})) { javax.swing.JOptionPane.showMessageDialog(null, "数据添加成功!"); } else { javax.swing.JOptionPane.showMessageDialog(null, "数据添加失败!"); } // 显示数据 System.err.println("/r/n添加一条数据之后:"); userDaoTest.showMessage(); // 修改一条数据 if (userDaoTest.ud.executeUpdate("update [User] set [Name] = ? , [Sex] = ?, [Age] = ?, [Address] = ?, [RegTime] = ? where [Id] = ?", new Object[] {"徐八" + (int)(Math.random() * 100), false, 18, (int)(Math.random() * 100) + "石头里蹦出来的!", new Timestamp(System.currentTimeMillis()), 6})) { javax.swing.JOptionPane.showMessageDialog(null, "数据修改成功!"); // 显示数据 System.err.println("/r/n修改一条数据之后:"); userDaoTest.showMessage(); } else { javax.swing.JOptionPane.showMessageDialog(null, "数据修改失败!"); } // 删除一条数据 if (userDaoTest.ud.executeUpdate("delete from [User] where [Name] = ?", new Object[] {"陈九"})) { javax.swing.JOptionPane.showMessageDialog(null, "数据删除成功!"); // 显示数据 System.err.println("/r/n删除一条数据之后:"); userDaoTest.showMessage(); } else { javax.swing.JOptionPane.showMessageDialog(null, "数据删除失败!"); } } /** * 查询并显示所有用户信息 */ private void showMessage() { // 查询数据库User表内容并返回Pojo泛型集合 Vector
测试运行结果为CRUD操作全部通过,控制台输出为:
数据库中的原始数据: [1, 张三, true, 23, 成都市大慈寺路1号, 2003-01-23 08:12:34.0] [2, 李四, true, 18, 成都市春熙路102号, 2009-09-03 19:52:02.0] [3, 王五, false, 29, 成都市书院街169号, 2005-12-07 13:41:56.0] [4, 赵六, true, 19, 成都市望平街119号, 2001-10-11 17:32:05.0] [5, 周七, false, 36, 成都市福字街206号, 2006-02-21 18:39:21.0] [6, 徐八42, false, 18, 43石头里蹦出来的!, 2009-03-30 16:51:47.697] 添加一条数据之后: [1, 张三, true, 23, 成都市大慈寺路1号, 2003-01-23 08:12:34.0] [2, 李四, true, 18, 成都市春熙路102号, 2009-09-03 19:52:02.0] [3, 王五, false, 29, 成都市书院街169号, 2005-12-07 13:41:56.0] [4, 赵六, true, 19, 成都市望平街119号, 2001-10-11 17:32:05.0] [5, 周七, false, 36, 成都市福字街206号, 2006-02-21 18:39:21.0] [6, 徐八42, false, 18, 43石头里蹦出来的!, 2009-03-30 16:51:47.697] [9, 陈九, false, 19, 成都市纱帽街69号, 2009-03-30 18:50:17.86] 修改一条数据之后: [1, 张三, true, 23, 成都市大慈寺路1号, 2003-01-23 08:12:34.0] [2, 李四, true, 18, 成都市春熙路102号, 2009-09-03 19:52:02.0] [3, 王五, false, 29, 成都市书院街169号, 2005-12-07 13:41:56.0] [4, 赵六, true, 19, 成都市望平街119号, 2001-10-11 17:32:05.0] [5, 周七, false, 36, 成都市福字街206号, 2006-02-21 18:39:21.0] [6, 徐八83, false, 18, 85石头里蹦出来的!, 2009-03-30 18:50:22.657] [9, 陈九, false, 19, 成都市纱帽街69号, 2009-03-30 18:50:17.86] 删除一条数据之后: [1, 张三, true, 23, 成都市大慈寺路1号, 2003-01-23 08:12:34.0] [2, 李四, true, 18, 成都市春熙路102号, 2009-09-03 19:52:02.0] [3, 王五, false, 29, 成都市书院街169号, 2005-12-07 13:41:56.0] [4, 赵六, true, 19, 成都市望平街119号, 2001-10-11 17:32:05.0] [5, 周七, false, 36, 成都市福字街206号, 2006-02-21 18:39:21.0] [6, 徐八83, false, 18, 85石头里蹦出来的!, 2009-03-30 18:50:22.657]
相关的 SQL Server 数据库脚本:
-- 建库 use [master] go if exists(select * from [sysdatabases] where [name] = 'GroundHogSDK4jTestDB') drop database [GroundHogSDK4jTestDB] go create database [GroundHogSDK4jTestDB] on ( name = 'GroundHogSDK4jTestDB', filename = 'D:/Workspace/GroundHogSDK4jTestDB.mdf', size = 3mb, maxsize = 100mb, filegrowth = 1mb ) log on ( name = 'GroundHogSDK4jTestDB_Log', filename = 'D:/Workspace/GroundHogSDK4jTestDB_Log.ldf', size = 1mb, maxsize = 50mb, filegrowth = 1mb ) go -- 建表 use [GroundHogSDK4jTestDB] go create table [User] ( [Id] int identity(1, 1) constraint [PK_User_Id] primary key, [Name] nvarchar(8) constraint [UQ_User_Name] unique not null, [Sex] bit not null, [Age] int constraint [CK_User_Age] check([Age] between 18 and 60) not null, [Address] nvarchar(100) constraint [CK_User_Address] check(len([Address]) > 0) not null, [RegTime] datetime constraint [DF_User_RegTime] default(getdate()) not null ) go -- 添加测试数据 insert into [User] values('张三', 1, 23, '成都市大慈寺路1号', '2003-01-23 8:12:34') insert into [User] values('李四', 1, 18, '成都市春熙路102号', '2009-09-03 19:52:02') insert into [User] values('王五', 0, 29, '成都市书院街169号', '2005-12-07 13:41:56') insert into [User] values('赵六', 1, 19, '成都市望平街119号', '2001-10-11 17:32:05') insert into [User] values('周七', 0, 36, '成都市福字街206号', '2006-02-21 18:39:21') insert into [User] values('王八', 1, 25, '成都市顺城街132号', '2003-07-19 23:19:33') go -- 查看测试数据 select [Id], [Name], [Sex], [Age], [Address], [RegTime] from [User] go
以上代码编译及调试环境:Microsoft Vista Home Basic SP1简体中文版 + JDK5.0 + Eclipse3.3 + Microsoft SQL Server 2005
前面提到感觉这样的封装还是有问题,这个问题就是代码中还是存在SQL命令(排除使用存储过程的情况,这里仅体现“封装”二字),下个版本干脆就将SQL命令全部使用String[]拼接字段名列表,使用Object[]拼接Where条件列表,分别为CRUD操作提供四个具体的方法,这样我觉得应该算作“比较优雅”了吧!最重要的是,以数组参数方式自动构建SQL命令时可以很方便地在方法内容对所有SQL参数进行部分校验工作,这样可以提高SQL命令的可靠性。
使用Java来实现OOP的确显得很优雅,因为自己可以了解其实现的所有细节,行行代码都尽是自己思维的体现。呵呵!妙哉!
By CodingMouse
2009年3月31日