随笔 - 3  文章 - 8  trackbacks - 0
<2010年4月>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

常用链接

留言簿(1)

随笔档案(8)

文章档案(1)

搜索

  •  

最新评论

阅读排行榜

评论排行榜

JDBC针对条件个数不定的查询,一般的做法是直接拼装查询字符串,但是这样做无法抵御注入攻击,还是要封装才安全,但是封装对条件个数、类型不定,是很有困难的,最近终于找到了解决办法:
public class queryDAOImpl implements queryDAO {
    
/**
     * 动态拼装查询
     * 
@param mq 封装查询条件的类
     * 
@return
     * 
@throws java.sql.SQLException
     
*/
    
public List<Map> queryAll(MnewsQ mq) throws SQLException {
        List
<Map> v = new Vector<Map>();
        Connection con 
= null;
        PreparedStatement pstmt 
= null;
        ResultSet rs 
= null;
        Vector t 
= new Vector();   //储存查询条件
        Vector<String> t2 = new Vector<String>();   //储存查询条件类型
        String sql = "SELECT * FROM mnews WHERE mnews.sc=0";
        
//如果提交了查询条件gj (int型)
        if (mq.getGj() > 0) {
            sql 
= sql + " AND gj=?";
            t.add(mq.getGj());
            t2.add(
"int");
        }
        
//如果提交了查询条件gjz4 (String型)
        if (mq.getGjz4() != null && mq.getGjz4().length() > 0) {
            sql 
= sql + " AND gjz4=?";
            t.add(mq.getGjz4());
            t2.add(
"String");
        }
        
try {
            con 
= ConnectionMannagerJNDI.getCon();    //取得数据库连接
            pstmt = con.prepareStatement(sql);
            
int paramNum = t.size();    //查询条件数量
            for (int i=0; i<paramNum; i++) {
                
if ("int".equals(t2.get(i).toString())) {
                    pstmt.setInt(i 
+ 1 + 2, Integer.parseInt(t.get(i).toString()));
                } 
else if ("String".equals(t2.get(i).toString())) {
                    pstmt.setString(i 
+ 1 + 2, t.get(i).toString());
                }
//Date、float等类型依此类推
            }
            rs 
= pstmt.executeQuery();
            
//以字段名为键,以字段值为值,将查询结果存入Map,再装进Vector
            ResultSetMetaData rsmd = rs.getMetaData();
            
int columnNum = rsmd.getColumnCount();
            
while (rs.next()) {
                Map map 
= new HashMap();
                
for (int i=1; i<=columnNum; i++) {
                    map.put(rsmd.getColumnName(i),rs.getObject(i));
                }
                v.add(map);
            }
        } 
finally {
            ConnectionMannagerJNDI.releaseConnection(rs, pstmt, 
null, con);
        }
        
return v;
    }
}


posted on 2009-01-26 18:38 cccp21 阅读(5446) 评论(8)  编辑  收藏

FeedBack:
# re: JDBC查询动态封装 2009-01-26 19:00 银河使者
条件是动态的也可以用查询参数,用java.sql.PreparedStatement就可以。楼主只是做了个封装,不过个人认为ResultSet本身也可以根据列名来定位列,而再把每行放在一个Map中,再把结果集放在Vector中,感觉有些多此一举。  回复  更多评论
  
# re: JDBC查询动态封装 2009-01-29 11:31 cccp21
@银河使者
请注意,是条件的个数是动态的,PreparedStatement只是让开发者设置参数值,但是参数个数还是固定的。
把每行放在一个Map中,再把结果集放在Vector中,是为了把JDBC与应用分离开,我开始的时候也和你一样,后来才听别人指出这一点的。  回复  更多评论
  
# re: JDBC查询动态封装 2009-01-29 19:26 银河使者
@ cccp21
不讨论PreparedStatement了,要想动态,一般也是采用你的方法,即动态生成带SQL参数的SQL语句,以及通过循环调用setter方法来设置参数的值,这就是动态,所有的动态基本都是这么弄的。
我是说第二个问题,在查询后,又使用Map和Vector再对数据进行封装,是不是有些耗资源。要注意哦,如果是Web程序,可不是一个用户或几个用户在访问程序啊,要是门户类型的系统,可能在一分钟之内就会有数以万计的用户来访问。这时就要建立多达数万个Map和Vector,服务器会挂的。当然,如果用户在可控范围内,这么做是没有问题的。但是可以封装得更高级一些,如返回的这些数据直接可以被某些用于显示的组件使用,如使用json格式的组件。这根据具体情况而定。
是的,使用Map和Vector可以从逻辑上将JDBC和应用进行分离。但个人认为要想分离,可以从数据层及应用逻辑层进行分离更好。也就是有数据层提供相应的访问数据的方法,如获得指定用户的保存在数据库中的密码。而应用逻辑层一般不直接访问数据库,可以访问数据层中的方法进行逻辑处理。
虽然将数据放在Map是Vector中是更抽象了,但JDBC本身就是抽象的,在业务逻辑层直接访问ResultSet并没有什么不妥,因为不管后台用的是什么数据库,从ResultSet中获得数据的方法都是一样的。如果非想要跨数据库,不如直接用hibernate。  回复  更多评论
  
# re: JDBC查询动态封装 2009-01-29 19:31 银河使者
封装本是面象对象最值得称道的地方,如果封装得太多有时得不偿失。个人认为在业务逻辑层,只要不直接涉及到SQL就可以,因为SQL本身是和数据库相关的,而业务逻辑层应和数据库不相关。但对于JDBC中的对象的使用上来说,一般也是和数据库不相关的(至少对于象SQL Server、oracle、db2、mysql这样的数据库来说是这样的)。

结论:数据访问层直接和数据库相关,可以涉及任何的SQL语句。业务逻辑层与数据库无关,在该层直接访问数据层,不能直接在业务逻辑层编写SQL语句,但可以使用JDBC的相关对象。  回复  更多评论
  
# re: JDBC查询动态封装 2009-01-31 10:57 cccp21
@银河使者
我觉得这个思路不错是因为这样可以在同一个方法中建立、关闭数据库连接。之前曾经在关闭数据库连接这个问题上吃过亏。
如果在应用中直接访问ResultSet,关闭数据库连接有点麻烦——如果之前就关闭了数据库连接,那么应用在ResultSet中就访问不到数据,而不关闭的话,就要在应用完成后关闭数据库连接,这里就会有些混乱。你有什么好主意呢?  回复  更多评论
  
# re: JDBC查询动态封装 2009-01-31 21:05 银河使者
@ cccp21
在使用ResultSet时当然不能关闭Connection,不知道你为什么会在使用ResultSet之间关闭Connection,这个Connection是共享的?还是用的什么其他方式。为了提高效率,可以使用连接池,一般每个Connection都是线程独享的,用完了再还给连接池。以上只针对Web应用,如果是桌面引用,建议使用Web Service方式。

如果想关闭ResultSet方便些,可以使用jdbc+spring的方式,也就是jdbcTemplate,这个很方便。基本原理是从连接池中获得一个Connection,执行一条或多条SQL语句,返回结果(这个结果也是封装的,和你的方法差不多,但考虑了异常处理),在返回结果之前,Connection就已经关闭了,关闭的动作是由Spring自动完成的。 而对于用户来说,只是调用了JdbcTemplate的一个方法来获得查询结果。再也不用考虑什么Connection、ResultSet,另人讨厌。不过对于小程序,用什么都行,我还是喜欢直接用jdbc,对于大一点的程序,尤其对Web程序,个人认为用jdbc +Spring比较好,如果想更高级一些,还跨数据库,可以使用hibernate+spring的方式。  回复  更多评论
  
# re: JDBC查询动态封装 2009-02-16 21:57 cccp21
@银河使者
你是说在web页中接受并使用ResultSet然后再在该页中关闭ResultSet和数据库连接?
还是说接受并用完ResultSet后关闭ResultSet并调用另一个函数关闭连接?

一般每个Connection都是线程独享的——那为什么我以前看到很多取数据源连接的方法要保证之生成一个数据源对象?比如:
    private static DataSource ds = null;
    private static Object Lock = new Object();
   
/**
 * 生成DataSource
 * @return 返回一个DataSource对象
 */
    public static DataSource gainDataSource(){
        try {
            if(ds == null){
                synchronized(Lock){
                    if(ds == null){
                        InitialContext ctx = new InitialContext();
                        ds = (DataSource)ctx.lookup("数据源名");
                    }
                }
            }
        } catch (NamingException e) {e.printStackTrace();}
        return ds;
    }
这有什么好处呢?
  回复  更多评论
  
# re: JDBC查询动态封装 2010-04-14 01:00 CodingMouse
要想重复造轮子就要造得好用一些才行:

我也造了一个轮子,下面这样的效果。

代码片段:

// 构建DAO工厂
AbstractDAOFactory<AccountPOJO, Long> factory = AbstractDAOFactory.newInstance();
// 构建DAO实例
IGenericsDAO<AccountPOJO, Long> dao = factory.buildGenericsDAO();

// 按条件检索数据并自动映射成POJO有序集合
List<AccountPOJO> pojos = dao.find(
AccountPOJO.class,
Condition.valueOf("accountid", Condition.LE, 10),
Condition.and("birthday", Condition.ISNULL),
Condition.or("accountname", Condition.LIKE, "邓%"));

// 在控制台输出结果
for (AccountPOJO account : pojos) {
System.out.println(account);
}

在控制台输出的调试信息为:

2010-04-14 00:48:58,281 main DEBUG [com.china.codingmouse.cmsdk4j.dao.sql.generator.SQLGenerator]
[CmSdk4j Auto SQL Generator In 2010-04-14 00:48:58.281]
SELECT accountid, accountname, onlinecount, birthday, loginname, loginpassword, email, accountstate FROM account WHERE accountid <= ? AND birthday IS NULL OR accountname LIKE ?
------------------------------
2010-04-14 00:48:58,328 main DEBUG [com.china.codingmouse.cmsdk4j.dao.converter.DataTypeConverter]
[SQL Parameter List Information In 2010-04-14 00:48:58.328]
No:1
Type:java.lang.Integer
Value:10
------------------------------
2010-04-14 00:48:58,343 main DEBUG [com.china.codingmouse.cmsdk4j.dao.converter.DataTypeConverter]
[SQL Parameter List Information In 2010-04-14 00:48:58.343]
No:2
Type:java.lang.String
Value:邓%
------------------------------

在控制台输出的程序结果为:

[ accountid = 1, accountname = 邓超, onlinecount = 2203, birthday = 1984-12-26, loginname = CodingMouse, loginpassword = cmsdk4j, email = CodingMouse@gmail.com, accountstate = true ]
[ accountid = 3, accountname = 李四, onlinecount = 932, birthday = null, loginname = LiSi, loginpassword = lisi123, email = lisi123@126.com, accountstate = false ]
[ accountid = 7, accountname = 邓远辰, onlinecount = 429, birthday = null, loginname = ChengCheng, loginpassword = chengchengpwd, email = chengchengdream@gmail.com, accountstate = true ]

我也非常支持重复造轮子的行为,我上面那个泛型DAO的find方法实现是这样的:

/**
* 获取该类型匹配的模型有序集合。<br><br>
*
* @param clz 注册返回类型。
* @param conditions SQL条件列表。
* @return 该类型匹配的全部模型有序集合。
* @throws DataAccessException 数据访问异常。
*/
public List<T> find(Class<? extends Object> clz, Condition... conditions)
throws DataAccessException {
return this.findAction.find(clz, conditions);
}

这段时间一直也在自己琢磨动态SQL的拼装问题,但总是显得对复杂SQL的支持还不够。也许还是只有单独封装个高内聚的SQL查询条件包装器才能达到效果。

由于我的DAO抽象基类还是采用的短事务处理方式,所以,总感觉还是不能完全满足需求:

/**
* 执行查询SQL命令并返回模型有序集合。<br><br>
*
* @param clz 注册模型类型。
* @param sql 要执行的带占位符SQL命令字串。
* @param param SQL参数列表。
* @return 泛型模型集合。
* @throws DataAccessException 数据访问异常。
*/
public List<Object> executeQuery(
Class<? extends Object> clz,
String sql,
Parameter... param)
throws DataAccessException {

// 数据库连接对象
Connection conn = null;
// SQL命令执行对象
PreparedStatement ps = null;
// 结果集对象
ResultSet rs = null;
// JDBC事务管理对象
DBTransaction trans = null;

try {
conn = DB_CONNECTION_POOL.getConnection();
trans = DBTransaction.begin(conn);
ps = DataTypeConverter.java2Jdbc(
conn.prepareStatement(sql),
param);
rs = ps.executeQuery();
List<Object> modelList = new Vector<Object>();
while(rs.next()) {
Object model = DataTypeConverter.jdbc2Java(
clz,
rs);
modelList.add(model);
}
trans.commit();
return modelList;
} catch (Throwable e) {
trans.rollback();
logger.error(
this.getClass().getName(),
Logger.DIR_INTERNAL,
null,
Logger.getStackTrace(e));
throw new DataAccessException(e);
} finally {
this.closeAll(rs, ps, conn, trans);
}

}  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: