昨天将数据库的连接基本上实现了,但是其中存在很多的问题,首先在创建Statement对象时我一直在使用PreparedStatement对象通过先占位后输入的办法来绕过了一些由于字符串拼接引起的一系列问题。而除此之外JDBC编程当中也有不少值得思考的东西
SQL注入
这个问题会出现在之前创建Statement对象时。比如要查询一个人,我让用户输入要查询的姓名,那么SQL语句是:
select * from person where name='用户输入'
但是用户输入可没你想的那么规范,他随意输入一个名字 然后加上or 1=1.这样一来条件肯定成立,整个表都暴露了出来。这只是一种简单的方式。
在web编程当中其也是值得关注的一个点,SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤,从而执行了非法的数据查询。基于此,SQL注入的产生原因通常表现在以下几方面:①不当的类型处理;②不安全的数据库配置;③不合理的查询集处理;④不当的错误处理;⑤转义字符处理不合适;⑥多个提交处理不当。
所以也可以选择一下方法来预防SQL注入的攻击:
1.永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和双"-"进行转换等。
2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装
6.sql注入的检测方法一般采取辅助软件或网站平台来检测,软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具。MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入,XSS攻击等。
更多有关SQL注入的详情转WIKI或其他的博客:SQL injection
批处理
类似昨日的简单CRUD在实际的生产生活当中就完全失去了意义,面对大批量的数据,系统的负载和效率成了不得不考虑的一个问题,而使用不同的方法所担任的重量也是不同的。可以简单的对Statement对象和PreparedStatement对象在相同情况下的耗时做一个简单的比较:
package xin.work;
import java.sql.*;
public class CompareStAndPrSt {
static Connection con;
static PreparedStatement tkb;
static Statement tka;
public static void DelDate() {
String sql = "delete from t_student";
try {
tka = con.createStatement();
tka.execute(sql);
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void UseStatement() {
long begin = System.currentTimeMillis();
try {
for (int i = 0; i < 10000; i++) {
String sql = "insert into t_student values(1,'Test',1)";
tka = con.createStatement();
tka.execute(sql);
tka.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.print(" "+ (end - begin));
}
public static void UseprepareStatement() {
long begin = System.currentTimeMillis();
try {
for (int i = 0; i < 10000; i++) {
String sql = "insert into t_student values(?,?,?)";
tkb = con.prepareStatement(sql);
tkb.setInt(1, 1);
tkb.setString(2, "Test");
tkb.setInt(3, 1);
tkb.execute();
tkb.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.print(" "+ (end - begin));
}
public static void main(String[] args) throws Exception {
con = DriverManager
.getConnection("jdbc:oracle:thin:xin/root@localhost:1521:xe");
System.out.print("UseStatement:");
for(int i=0;i<10;i++){
UseStatement();
DelDate();
}
System.out.println();
System.out.print("UseprepareStatement:");
for(int i=0;i<10;i++){
UseprepareStatement();
DelDate();
}
con.close();
}
}
显然大批量的数据进行插入的时候上述程序就有点毒瘤了。所以采取批量处理:
tkb.addBatch(); tkb.executeBatch();
tka.addBatch(sql); tka.executeBatch();
而其中有个十分重要 指标就是什么时候(积攒到多少量的数据进行处理)进行excute?视你的电脑而定 ,要是银河我觉得可以一次性搞定,普通电脑多测试几次就好了。
事务控制
jdbc是自动事务提交的, 因此,执行一条sql语句之后,sql语句可以自动将数据插入到数据库,并且事务自动提交。如果不希望sql事务自动提交,则可以设置connection.setAutoCommint(false);
如此需要在用户手动的提交事务。connection.commit();
如果需要保存回滚点,可以使用:Savepoint a2 = connection.setSavepoint("a1");
如果需要回滚事务到某一指定保存点,可以使用: Savepoint a2 = connection.rollback(a2);
如果希望事务回滚,可以使用:connection.rollback();
回调函数
这里显得就有点麻烦了。在说明这个之前得搞清以下过程:

由图可知Util层可以提供两个方法:获取连接(getConnection)和关闭Statement(closeStatement)以及释放数据库连接,所以可以写出UTILs的类:
package xin.work;
import java.sql.*;
import java.util.Properties;
import java.io.*;
public class DBUtils {
private static Properties tk = new Properties();
static{
try {
tk.load(new FileInputStream("src/db.properties"));
System.out.println(tk);
} catch (IOException e) {
throw new RuntimeException("加载异常", e);
}
}
public static Connection getConnection() throws Exception {
return DriverManager.getConnection(tk.getProperty("url"),
tk.getProperty("user"), tk.getProperty("password"));
}
public static void closeStatement(ResultSet rs,Statement stmt,Connection conn){
if(rs!=null) try{rs.close();}catch(Exception e){e.printStackTrace();}
if(stmt!=null) try{stmt.close();}catch(Exception e){e.printStackTrace();}
if(conn!=null) try{conn.close();}catch(Exception e){e.printStackTrace();}
}
}
而JdbcTemplate.java可以由下组成:
package xin.work;
import java.sql.*;
import java.util.logging.Handler;
public class JdbcTemplate {
public void executeQuery(String sql, ResultHandler handler) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = DBUtils.getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
if (handler != null)
handler.handler(rs);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
DBUtils.closeStatement(rs, stmt, conn);
}
}
public int executeUpdate(String sql, boolean flag, ParamHandler handler) {
Connection conn = null;
Statement stmt = null;
try {
conn = DBUtils.getConnection();
int count = 0;
if (flag) {
stmt = conn.createStatement();
count = stmt.executeUpdate(sql);
} else {
PreparedStatement pstmt = conn.prepareStatement(sql);
if (handler != null)
handler.paramSet(pstmt);
pstmt.executeUpdate();
stmt = pstmt;
}
return count;
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.closeStatement(null, stmt, conn);
}
return 0;
}
}
其次而上面的一些办法由接口提供的回调方法来实现:
package xin.work;
import java.sql.*;
public interface ParamHandler {
public void paramSet(PreparedStatement pstmt) throws Exception;
}
package xin.work;
import java.sql.ResultSet;
public interface ResultHandler {
public void handler(ResultSet rs) throws Exception;
}
Metada(元数据)
又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。详情转百科:百度词条-元数据
框架
在这里通常就是将整个业务应用划分为:表现层(UI)、业务逻辑层(BLL)、数据访问层(DAL)。区分层次的目的即为了“高内聚,低耦合”的思想。(也类似MVC的模式)
1:数据访问层:主要是对原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据的操作,而不是数据库,具体为业务逻辑层或表示层提供数据服务.
2:业务逻辑层:主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,如果说数据层是积木,那逻辑层就是对这些积木的搭建。
3:表示层:主要表示用户的操作终端,如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。
具体的区分方法
1:数据访问层:主要看你的数据层里面有没有包含逻辑处理,实际上他的各个函数主要完成各个对数据文件的操作。而不必管其他操作。
2:业务逻辑层:主要负责对数据层的操作。也就是说把一些数据层的操作进行组合。
3:表示层:主要对用户的请求接受,以及数据的返回,为客户端提供应用程序的访问。位于最外层(最上层),最接近用户。用于显示数据和接收用户输入的数据,为用户提供一种交互式操作的界面。
优点
1、开发人员可以只关注整个结构中的其中某一层;
2、可以很容易的用新的实现来替换原有层次的实现;
3、可以降低层与层之间的依赖;
4、有利于标准化;
5、利于各层逻辑的复用。
6、结构更加的明确
7、在后期维护的时候,极大地降低了维护成本和维护时间
缺点
1、降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。
2、有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。
3、增加了开发成本。