8.门面模式(Facade)
到蛋糕店买蛋糕,不需要知道蛋糕怎么制作,蛋糕房就是蛋糕的门面,屏蔽了制作蛋糕的细节,门面模式又称为外观模式。实际开发中,屏蔽了子模块内部的实现细节,只是将客户端需要的接口提供给客户。
哪里会用到门面模式
网上商城有银行支付功能,而银行肯定不能将后台的数据库直接开放给网上商城使用,不同银行提供相应的支付接口,这样的支付接口对于银行而言就是一个门面。
薪资系统开发中,计算每个月薪酬时,要获取很多数据库信息,如果每个员工都这样到每个不同子模块表去获取,则暴露细节太多,如果后台表变化,则需要修改薪资计算的代码,明显不符合松耦合的设计原则,因此需要门面模式,屏蔽细节。
在框架设计中,比如Spring、Struts、Hibernate等,很多地方需要读取xml文件,此时就应该把读取xml配置文件的代码抽取出来,形成统一的接口。
比如对于薪酬模块:
代码比较简单,就不在此贴出,全部放在项目中。
门面模式的实现原理
门面模式在Spring JDBC中的实际应用
软件开发一般需要对数据库进行操作,直接使用JAVA提供的JDBC往往会比较麻烦,因此需要对JAVA提供的JDBC进行封装,提供一个公用的工具类,这就是门面模式。已有的工具类如Hibernate、Spring JDBC等。
先自己写一个封装的JDBC工具类:
JdbcUtil
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.rowset.CachedRowSet;
import com.sun.rowset.CachedRowSetImpl;
public class JdbcUtil {
private Connection conn;
private PreparedStatement pstmt = null;
private Statement stmt = null;
private boolean isStmt = true;
private String sql = "";
public JdbcUtil() {
}
public JdbcUtil(Connection conn) {
this.conn = conn;
}
// 使用PreparedStatement
public void setPst() {
this.isStmt = false;
}
/**
* @description 执行传入的sql
* @date 2016年1月13日
*/
public int executeUpdate(String sql) throws SQLException {
this.sql = sql;
int numRow = 0;
stmt = null;
pstmt = null;
// 判断连接是否为空或已关闭
if (conn == null || conn.isClosed())
throw new SQLException("请首先创建或获取一个连接");
// 判断传进来的sql是否为空
if (sql == null || "".equals(sql.trim()))
throw new SQLException("sql为null");
try {
// 判断是否使用Statement
if (isStmt) {
// 使用Statement
synchronized (this.conn) {
// 用来保证一个对象在多个线程中访问该方法时是线程安全的
stmt = this.conn.createStatement();
}
numRow = stmt.executeUpdate(sql);// 返回执行成功的笔数
if (stmt != null) {
stmt.close();
}
}
else {
// 使用PreparedStatement
synchronized (this.conn) {
// 用来保证一个对象在多个线程中访问该方法时是线程安全的
pstmt = this.conn.prepareStatement(sql);
}
numRow = pstmt.executeUpdate();// 返回执行成功的笔数
if (pstmt != null) {
pstmt.close();
}
}
}
catch (SQLException e) {
throw new SQLException("执行executeUpdate失败" + sql + e);
}
finally {
return numRow;
}
}
/**
* @description 根据sql语句返回单笔数据结果集
* @date 2016年1月13日
*/
public ResultSet getResultSet(String sql) throws SQLException {
ResultSet rs = null;
try {
rs = this.getAllResultSet(sql, 1);//通过getAllResultSet()方法实现ResultSet
}
catch (SQLException e) {
throw new SQLException("执行getResultSet失败" + sql + e);
}
finally {
return rs;
}
}
/**
* @description 根据sql语句和数量数 返回结果集(limit=1时即单笔)
* @date 2016年1月13日
*/
public ResultSet getAllResultSet(String sql, int limit) throws SQLException {
this.sql = sql;
stmt = null;
pstmt = null;
ResultSet rs = null;
//判断连接是否为空或已关闭
if (conn == null || conn.isClosed())
throw new SQLException("请首先创建或获取一个连接");
//判断传进来的sql是否为空
if (sql == null || "".equals(sql.trim()))
throw new SQLException("sql为null");
try {
//判断是否使用Statement
if (isStmt) {
//使用Statement
synchronized (this.conn) {
//用来保证一个对象在多个线程中访问该方法时是线程安全的
stmt = this.conn.createStatement();
}
if (limit > 0) {
stmt.setMaxRows(limit);
}
rs = stmt.executeQuery(sql);
}
else {
//使用PreparedStatement
this.PreparedStatement(sql, limit);//通过PreparedStatement()方法生成pstmt
rs = pstmt.executeQuery();
}
}
catch (SQLException e) {
throw new SQLException("执行getAllResultSet失败" + sql + e);
}
finally {
return rs;
}
}
/**
* @description 用来获取单笔数据结果集,将ResultSet转成Map形式
* @date 2016年1月13日
*/
public Map queryMap(String sql) throws SQLException {
Map map = null;
try {
//单笔通过多笔实现
Map[] maps = this.queryAllMap(sql, 1);
if (maps != null && maps.length == 1) {
map = maps[0];//只取一笔
}
}
catch (SQLException e) {
throw new SQLException("执行queryMap失败" + sql + e);
}
finally {
return map;
}
}
/**
* @description 用来获取多笔数据结果集 ,将ResultSet转成Map形式
* @date 2016年1月13日
*/
public Map[] queryAllMap(String sql, int limit) throws SQLException {
Map[] map = null;
CachedRowSet rs = null;
List list = new ArrayList();
try {
//通过getAllCachedRowSet获取rs,然后循环将每笔转换为map
for (rs = this.getAllCachedRowSet(sql, limit); rs.next();) {
list.add(getMapFromRs(rs));//将每笔转换为map
}
}
catch (SQLException e) {
throw new SQLException("执行queryAllMap失败" + sql + e);
}
finally {
if (rs != null) {
rs.close();//关闭rs
}
if (list.size() != 0) {
map = new Map[list.size()];
list.toArray(map);//将list转换为map数组
}
return map;
}
}
/**
* @description 用来获取单笔数据结果集,希望返回的是对象,可以转换为Javabean
* @date 2016年1月13日
*/
public Object queryObj(String sql) throws SQLException {
Object obj = null;
try {
//单笔通过多笔实现
List list = this.queryAllObj(sql, 1);
if (list != null && list.size() == 1) {
obj = list.get(0);//只取一笔
}
}
catch (SQLException e) {
throw new SQLException("执行queryObj失败" + sql + e);
}
finally {
return obj;
}
}
/**
* @description 用来获取多笔数据结果集,希望返回的是对象,可以转换为Javabean
* @date 2016年1月13日
*/
public List queryAllObj(String sql, int limit) throws SQLException {
CachedRowSet rs = null;
List list = null;
try {
list = new ArrayList();
//通过getAllCachedRowSet,取得rs,然后循环获取每个Object
for (rs = this.getAllCachedRowSet(sql, limit); rs.next();) {
list.add(getObjFromRs(rs));//获取每个Object,将其存入list
}
} catch (SQLException e) {
throw new SQLException("执行queryAllObj失败" + sql + e);
} finally {
if (rs != null) {
rs.close();//关闭rs
}
return list;
}
}
public CachedRowSet getAllCachedRowSet(String sql, int limit) throws SQLException {
CachedRowSetImpl crs = null;
try {
crs = new CachedRowSetImpl();
crs.populate(this.getAllResultSet(sql, limit));//通过getAllResultSet()方法实现CachedRowSet
} catch (SQLException e) {
e.printStackTrace();
throw new SQLException("执行getAllCachedRowSet失败" + sql + e);
} finally {
return crs;
}
}
/**
* @description 将获取的栏位名称和栏位内容相对应,
* @date 2016年1月13日
*/
private Map getMapFromRs(CachedRowSet rs) throws SQLException {
Map map = new HashMap();
List columnNamesList = new ArrayList();
int columnCount = 0;
try {
columnCount = rs.getMetaData().getColumnCount();//获取ResultSetMetaData的字段数目
columnNamesList = setColumnNameByMeta(rs.getMetaData());//获取每个字段的名称
for (int i = 0; i < columnCount; i++) {
map.put((String) columnNamesList.get(i), rs.getString(i + 1));//将字段名和对应的值存入Map
}
} catch (SQLException e) {
throw new SQLException("执行getMapFromRS失败" + e);
} finally {
return map;
}
}
/**
* @description 该方法用来将获取的栏位名称和栏位内容相对应,可以由继承它的类实现,这样可以与VO相结合。
* @date 2016年1月13日
*/
protected Object getObjFromRs(ResultSet rs) throws SQLException {
/*//获取infoIn中存储的在Xml中设定的对应信息
List voId = (infoIn.get("voId") == null) ? new ArrayList() : (List)infoIn.get("voId");//获取Xml中设定的voId
List voClass = (infoIn.get("voClass") == null) ? new ArrayList() : (List)infoIn.get("voClass");//获取Xml中设定的voClass
List voType = (infoIn.get("voType") == null) ? new ArrayList() : (List)infoIn.get("voType");//获取Xml中设定的voType
List voTable = (infoIn.get("voTable") == null) ? new ArrayList() : (List)infoIn.get("voTable");//获取Xml中设定的voValidate
//确定开发人员设定sql中的表名,这里只对第一个表中的值转换为VO
int startPos = sql.toUpperCase().indexOf("FROM") + 4;//取得第一个from中m的后一个字符的位置
String strStart = sql.substring(startPos).trim();//取得从第一个表名后的sql字符串的值
char[] sqlStr = strStart.toCharArray();//将其转换为char[]
int endPos = 0;
//对从第一个表名后的sql字符串的值进行一个一个的循环
for (int i = 0; sqlStr != null && sqlStr.length > i; i++) {
//如果从第一个表名后的sql字符串的值中有空格或,则停止循环,并记录此时的位置,则之前的字符串即为表名
if (" ".equals(String.valueOf(sqlStr[i])) || ",".equals(String.valueOf(sqlStr[i]))) {
endPos = i;
break;
} else {
endPos = i + 1;//如果整个从第一个表名后的sql字符串的值都为表名
}
}
String classVo = "";
String table = strStart.substring(0, endPos).toUpperCase();//取得表名,并转换为大写
//和XMl中设定的表名进行对比
for (int i = 0; voId.size() > i; i++) {
String strTable = voTable.get(i) == null ? "" : (String)voTable.get(i);
if (table.equals(strTable.toUpperCase())) {//如果该sql中的表名和Xml中的相对应,则去对该表对应的Class,即VO
classVo = (String)voClass.get(i);
}
}
Object obj = null;
List columnNamesList = new ArrayList();
int columnCount = 0;
try {
columnCount = rs.getMetaData().getColumnCount();//获取ResultSetMetaData的字段数目
columnNamesList = setColumnNameByMeta(rs.getMetaData());//获取每个字段的名称
Class cls = Class.forName(classVo);
obj = (Object)cls.newInstance();//取得该Class即VO的实例
Method[] mtdVos = cls.getMethods();//获取该VO中的所有方法
for (int i = 0; i < columnCount; i++) {
//对该VO中的所有方法进行循环,如果方法名和表中的字段名一致,且该方法为set方法,则将值注入
for (int m = 0; mtdVos.length > m; m++) {
if ("set".equals(mtdVos[m].getName().substring(0, 3)) && ((String) columnNamesList.get(i)).toUpperCase().equals(mtdVos[m].getName().substring(3).toUpperCase())) {
Method mtdVo = cls.getMethod(mtdVos[m].getName(), new Class[]{String.class});
mtdVo.invoke(obj, rs.getString(i + 1));//将数据库中的值注入VO
}
}
}
} catch (SQLException e) {
throw new SQLException("执行getObjFromRS失败" + e);
} finally {
return obj;
}*/
return null;
}
/**
* @description 获取处理数据库中的栏位名和栏位数目
* @param rsMetadata
* @return
* @throws SQLException
* @author 刘泉 QQ:3262332995
* @date 2016年1月13日
*/
private List setColumnNameByMeta(ResultSetMetaData rsMetadata) throws SQLException {
List columnNamesList = new ArrayList();
try {
//获取ResultSetMetaData的字段数目
for (int i = 0; i < rsMetadata.getColumnCount(); i++) {
columnNamesList.add(rsMetadata.getColumnName(i + 1));//获取每个字段的名称
}
} catch (SQLException e) {
throw new SQLException("执行setColumnNameByMeta失败" + e);
} finally {
return columnNamesList;
}
}
/**
* @description 用来通过PreparedStatement设定数据结果集
* @date 2016年1月13日
*/
public void PreparedStatement(String sql) throws SQLException{
this.PreparedStatement(sql, 0);
}
/**
* @description 用来通过PreparedStatement设定多笔数据结果集
* @date 2016年1月13日
*/
public void PreparedStatement(String sql, int limit) throws SQLException{
this.PreparedStatement(sql, limit, 0);
}
/**
* @description 用来通过PreparedStatement设定多笔数据结果集
* @param sql
* @param limit 用来限制查询笔数,<=0则代表不限制
* @param gk 用来自动生成主键值,>0表示自动生成主键
* @date 2016年1月13日
*/
public void PreparedStatement(String sql, int limit, int gk) throws SQLException{
this.sql = sql;
pstmt = null;
// 判断连接是否为空或已关闭
if (conn == null || conn.isClosed())
throw new SQLException("请首先创建或获取一个连接");
//判断传进来的sql是否为空
if (sql == null || "".equals(sql.trim()))
throw new SQLException("sql为null");
try {
synchronized (this.conn) {
//用来保证一个对象在多个线程中访问该方法时是线程安全的
if (gk > 0) {
pstmt = this.conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);//自动生成主键
} else {
pstmt = this.conn.prepareStatement(sq