前二天,在struts介绍的实例中就提到数据库的访问使用了工厂模式,可以实现在线切换数据库的功能,同样是那个NetBeans工程,今天就来具体介绍我实现的思路。
网上有很多工厂模式的介绍,我最先接触是在.Net的PetShop项目里看到的,最近公司要搞Java,所以就搬到Java里来运用下,看了一些资料好象我这种实现的方法叫做简单工厂,是通过定义接口来实现的,在面向对象编程的世界里面,接口用来定义的一组规范,它强制规范实现类要一定要实现完成它的所有成员,至于接口的调用到底使用那个实现类则是在工厂类里面产生的。接口一般多定义对象的行为动作即方法,而抽象类则多用来定义对象的公共属性,比如男人和女人可以抽象出人做为抽象基类,因为都有人的一些公共特征,至于什么时候用接口什么时候用抽象类,这个就需要看实际项目中对象的关系了。
还是用户的增删改查功能的实现为例子,我们先需要定义用户的接口IUser,然后使用不同数据库来分别实现它,程序使用那种数据库则放在properties资源文件里配置,工厂在根据配置产生实例类,以接口形式返回到业务逻辑层,然后在页面里调用业务逻辑层方法。这里我用PowerDesiger简单画了一个类图如下:

首先我们来看一下IUser接口类的代码:
package dal;
import bean.User;
import java.util.List;
public interface IUser {
public int UserAdd(User user);
public int UserDel(int id);
public int UserUpdate(User user);
public User GetUserInfo(int id);
public List<User> GetUserList();
}
代码比较简单,就是定义用户业务逻辑所需要的全部方法,然后在子类里将会被一一实现。接口里增加一个方法,所有子类都必须要提供该方法的实现,不然会编译不过,这也是使用接口的一个规范问题。这里需要注意的是.Net里所有接口成员都不需要加访问修饰符,默认为public,而java则可以加public,如果接口里有字段属性则需要赋初始值,在调用的是以static final成员的形式来调用的。
我们再看一下DataAccess工厂代码:
/*
* 数据库访问工厂
*/
package dal;
/**
*
* @author Jonllen
* @create 2009-05-18 22:04:42
* @site http://www.jonllen.com
*/
public class DataAccess {
private DataAccess(){}
public static String daoPackageName = db.DbManager.GetConfig().getDefaultItem().getPackageName();
public static Object InstanceObject(String className)
{
String classPath = "dao." daoPackageName "." className;
try {
System.out.println("--实例化" classPath "类开始--");
return Class.forName(classPath).newInstance();
} catch (Exception ex) {
System.out.println("**实例化" classPath "类失败**");
ex.printStackTrace();
}
return null;
}
public static IUser CreateUser()
{
return (IUser)InstanceObject("UserDAO");
}
}
首先这个工厂类里面全是静态的成员,而且构造函数是private级别的,这其实是Java里单例的一种,为确保全局只存在一个实例。这里的daoPackageName是当前访问数据库类的包名,相当与.Net里的命名空间,我在程序里面做为一个约定,就是所有操作访问数据库的全部放在dao包下面,然后下面不同的数据库创建不同的包名,每个包下面的类名一样,这样我们根据不同数据库的包名就能创建下面类的事例,在.Net里我们使用Assembly.Load("命名空间").CreateInstance("类名全路径")程序集动态创建类的实例,而在Java里面使用Class.forName("类名全路径").newInstance(),再将Object类型强转为接口,因为这些类是实现了对应的业务接口的。在这里我推荐使用以上InstanceObject方法这种命名约定来动态创建类实例,而不要根据daoPackageName包名判断去new一个事例,如下代码是不妥的:
public static IUser CreateUser()
{
if (daoPackageName.equals("derby")) {
return (IUser)new dao.derby.UserDAO();
}else if(daoPackageName.equals("mysql")) {
return (IUser)new dao.mysql.UserDAO();
}else if(daoPackageName.equals("sqlserver")) {
return (IUser)new dao.sqlserver.UserDAO();
}
return null;
}
原因很简单,因为我都不知道到底将有多少像derby、mysql、sqlserver这样的实现类,所以使用daoPackageName判断去动态new对象是不合理的,这也违工厂模式设计的初衷,假如我现在新增一个oracle的实现类,那我岂不是又需要修改程序增加为oracle的判断,如果使用类全名动态实例则程序不需要修改任何源代码,只需要添加oracle实现类的引用,再设置daoPackageName为oracle即可。在实际应用中,完成好实现类,编译上传,接入到应用程序里,甚至可以不需要重启就能使用这个类。
通过使用接口,我们把功能的调用推迟给实例化的子类。不同的子类,实现的方式可能又不同,就像访问不同的数据库,sql语法可能存在差异。这里贴一下我在Java里面访问数据库的类,代码如下:
Java数据库访问辅助类 1
/**//*
2
* 数据库访问辅助类
3
*/
4
package db;
5
6
import java.sql.*;
7
8
/** *//**
9
* @author Jonllen
10
* @create 2009-05-23 19:21:40
11
* @update 2009-10-03 15:34:50
12
*/
13
public class SqlHelper ...{
14
15
public static String ConnString = DbManager.GetConfig().getDefaultItem().getConnString();
16
17
public static String DriverClass = DbManager.GetConfig().getDefaultItem().getDriverClass();
18
19
//并发处理,确保线程是同步的
20
public static synchronized Connection GetConnection()
21
...{
22
try ...{
23
Class.forName(DriverClass).newInstance();
24
Connection conn = null;
25
String user = DbManager.GetConfig().getDefaultItem().getUser();
26
String password = DbManager.GetConfig().getDefaultItem().getPassword();
27
if( user!=null && user.trim().length()!=0 && password!=null && password.trim().length()!=0)
28
...{
29
conn = DriverManager.getConnection(ConnString,user,password);
30
}else
31
...{
32
conn = DriverManager.getConnection(ConnString);
33
}
34
return conn;
35
} catch (Exception e) ...{
36
System.out.println("连接数据库失败:" ConnString);
37
e.printStackTrace();
38
}
39
return null;
40
}
41
42
public static ResultSet GetProcedureResultSet(String sql )
43
...{
44
return GetProcedureResultSet(sql, null, null);
45
}
46
47
public static ResultSet GetProcedureResultSet(String sql, Object[][] inargs)
48
...{
49
return GetProcedureResultSet(sql, inargs, null);
50
}
51
52
public static ResultSet GetProcedureResultSet(String sql, Object[][] inargs, Object[][] outargs)
53
...{
54
Connection con = GetConnection();
55
CallableStatement cstmt = null;
56
try ...{
57
cstmt = con.prepareCall(sql);
58
if(inargs!=null && inargs.length>0)
59
...{
60
//设置输入参数
61
for(int i=0;i<inargs.length;i )
62
...{
63
cstmt.setObject(inargs[i][0].toString(),inargs[i][1]);
64
}
65
}
66
if (outargs!=null && outargs.length>0)
67
...{
68
//如果有多个输出参数则注册
69
for(int i=0;i<outargs.length;i )
70
...{
71
cstmt.registerOutParameter(outargs[i][0].toString(),Integer.valueOf(outargs[i][1].toString()));
72
}
73
}
74
Boolean b = cstmt.execute();
75
if (outargs!=null && outargs.length>0)
76
...{
77
for(int i=0;i<outargs.length;i )
78
...{
79
//把原来 输出参数 的类型 变成输出参数值 返回
80
outargs[i][1] = cstmt.getObject(outargs[i][0].toString());
81
}
82
}
83
if (b)
84
...{
85
return cstmt.getResultSet();
86
}
87
} catch (SQLException e) ...{
88
// TODO Auto-generated catch block
89
e.printStackTrace();
90
}
91
return null;
92
}
93
94
public static ResultSet GetResultSet(String sql)
95
...{
96
Connection con = GetConnection();
97
try ...{
98
//PreparedStatement pstmt = con.prepareStatement();
99
Statement stmt = con.createStatement();
100
return stmt.executeQuery(sql);
101
} catch (SQLException e) ...{
102
// TODO Auto-generated catch block
103
e.printStackTrace();
104
}
105
return null;
106
}
107
108
public static ResultSet GetResultSet(String sql, Object ... parms)
109
...{
110
Connection con = GetConnection();
111
try ...{
112
PreparedStatement pstmt = con.prepareStatement(sql);
113
if(parms!=null && parms.length>0)
114
...{
115
//设置输入参数
116
for(int i=0;i<parms.length;i )
117
...{
118
pstmt.setObject(i 1,parms[i]);
119
}
120
}
121
boolean hasResutl = pstmt.execute();
122
if (hasResutl) return pstmt.getResultSet();
123
if(pstmt.getMoreResults() )...{
124
return pstmt.getResultSet();
125
}
126
System.out.println("UpdateCount:" pstmt.getUpdateCount());
127
} catch (SQLException e) ...{
128
// TODO Auto-generated catch block
129
e.printStackTrace();
130
}
131
return null;
132
}
133
134
public static Object ExcuteSclare(String sql, Object ... parms)
135
...{
136
ResultSet rs = GetResultSet(sql, parms);
137
try ...{
138
if( rs!=null && rs.next())
139
...{
140
return rs.getObject(1);
141
}else System.out.println("NULL");
142
} catch (SQLException e) ...{
143
// TODO Auto-generated catch block
144
e.printStackTrace();
145
}finally...{
146
CloseConnection( rs );
147
}
148
return null;
149
}
150
151
public static int ExecuteQuery(String sql, Object ... parms)
152
...{
153
Connection con = GetConnection();
154
try ...{
155
PreparedStatement pstmt = con.prepareStatement(sql);
156
if(parms!=null && parms.length>0)
157
...{
158
//设置输入参数
159
for(int i=0;i<parms.length;i )
160
...{
161
pstmt.setObject(i 1,parms[i]);
162
}
163
}
164
return pstmt.executeUpdate();
165
} catch (SQLException e) ...{
166
// TODO Auto-generated catch block
167
e.printStackTrace();
168
}finally...{
169
CloseConnection(con);
170
}
171
return -1;
172
}
173
174
public static void CloseConnection(ResultSet rs)
175
...{
176
177
try ...{
178
if (rs!=null && rs.getStatement()!=null && rs.getStatement().getConnection()!=null)
179
...{
180
//rs.getStatement().close();
181
rs.getStatement().getConnection().close();
182
}
183
} catch (SQLException e) ...{
184
// TODO Auto-generated catch block
185
e.printStackTrace();
186
System.out.println("关闭连接时出错(From ResultSet)!" e.getMessage());
187
}
188
}
189
190
public static void CloseConnection(Connection con)
191
...{
192
try ...{
193
if (con!=null && !con.isClosed())
194
...{
195
con.close();
196
}
197
} catch (SQLException e) ...{
198
// TODO Auto-generated catch block
199
e.printStackTrace();
200
System.out.println("关闭连接时出错(From Connection)!" e.getMessage());
201
}
202
}
203
204
205
}
其实,不同的数据库在细节上的差异还是有很多的,比如像自动增长主键的处理等。我这个事例中用了三种数据库:derby、mysql、sqlserver,由于derby是装NetBeans自带开源免费的数据库,以前从没有听说过,所以在插入用户的时候只能先查出最大编号做为主键插入,而在mysql里插入自动编号则显示的指定为default,sqlserver里自增主键列则不需要指定,如果是oracle的话还需要使用序列。另外不同的数据库函数使用也不一,如取当前时间access和mysql是使用now(),sqlserver里则是getdate(),oracle里用SYSDATE,这些都是我们需要注意的,说不定我们的项目那一天就需要移植到另一种数据库。
java里访问数据库都是使用java.sql.*下面的类,然后加载驱动文件。而.Net里则是提供了IConnection、IDataReader、IDbDataParameter等接口,并且为不同种数据库提供了专门的命名空间,提高访问效率。下面我整理java里访问sql2000、sql2005、mysql、derby数据库的连接字符串、驱动包类名表列出比较。
数据库 | 连接字符串 | 驱动包 |
sql2000 |
jdbc:microsoft:sqlserver://localhost;databaseName=master;user=sa;password=123 |
com.microsoft.jdbc.sqlserver.SQLServerDriver |
sql2005 |
jdbc:sqlserver://localhost\sql2005;databaseName=dbTest;user=sa;password=123 |
com.microsoft.sqlserver.jdbc.SQLServerDriver |
mysql |
jdbc:mysql://localhost:3306/dbtest?user=root&password=123&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true |
com.mysql.jdbc.Driver |
derby |
jdbc:derby://localhost:1527/dbTest;create=true;user=app;password=app |
org.apache.derby.jdbc.ClientDriver |
oracle |
jdbc:oracle:thin:system/123:@localhost:1521:orcl |
oracle.jdbc.driver.OracleDriver |
这些信息是配置在我工程connector.properties资源文件里的,把数据库名做为key,然后对应connString连接字符串、driverClass驱动类多个属性,使用DbManager单件类读取到,以确保只读取一次资源文件,由于要做到在线切换数据库,而不是重起应用程序后才能生效,而之前DataAccess的daoPackageName、SqlHelper的ConnString和DriverClass静态字段只会在初始化读取一次,所以切换数据库的时候也重新指定这些静态字段,不然还会是初始化时候的值,代码如下:
public void setDefaultName(String defaultName) {
for(DbItem item : this.list)
{
if (item.getDbName().equalsIgnoreCase(defaultName))
{
System.out.println("修改设置当前数据库为:" defaultName );
this.defaultName = defaultName;
this.defaultItem = item;
dal.DataAccess.daoPackageName = item.getPackageName();
db.SqlHelper.ConnString = item.getConnString();
db.SqlHelper.DriverClass = item.getDriverClass();
}
}
}
这样切换数据库的功能就实现了,所有页面功能的调用是在BLL业务逻辑层,这一层就是使用接口实例调用方法。好了不多说了,有什么问题可以下载NetBeans工程的源代码,仍然是和上一篇struts同一工程。
Java工厂模式切换数据库实例源代码下载(带Struts的增、删、改、查功能)