与数据库的连接
在遇到数据交互需求较大的项目时,在java文件中创建列表会导致数据维护的操作量增加,且不易维护,所以我们需要将外部的sql数据库导入并进行数据的交互
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=utf8", "F1CT0R", "password");
System.out.println(con);
我们通过上述的语句即可与已准备好的数据库获得连接,接下来就可以与数据库进行查询了,查询的方式也非常的简单粗暴——将查询语句插进去。
String sql="select * from t_emps";
Statement statement=con.createStatement();
ResultSet rs = statement.executeQuery(sql);
但是执行完这条语句后我们并不能获得全部的数据,rs中获得的只有一条数据,所以我们需要将索引下移
rs.next();
然后在循环中新建一个列表接收获得的数值
while (rs.next()){
String id=rs.getString("id");
String name=rs.getString("name");
String sex=rs.getString("sex");
Date birth=rs.getDate("birth");
double salary=rs.getDouble("salary");
int deptId=rs.getInt("deptId");
Emp emp = new Emp(id,name,sex,birth,salary,deptId);
empList.add(emp);
}
最后不要忘记关闭资源导致其他交互被阻挡
statement.close();
con.close();
return empList;
查询的工具化
如果每次进行数据的查询都要去新建一次查询方法会导致代码的重复量很高,我们需要的是将获取列的操做进行泛化,首先需要获取列数。
ResultSetMetaData md=rs.getMetaData();
int columnCount=md.getColumnCount();
通过while遍历列表,双层循环对列表进行获取
// rs解析
while (rs.next()){//读取结果集的光标下移
// 根据每一行的数据封装为一个实体对象
T t=c.newInstance();
// 取出某一行的每一列数据,封装到对象t点属性中
for(int i=1;i<=columnCount;i++){
// 获取每一列的值
Object value=rs.getObject(i);
if(value!=null){
// 通过列的序号获取每一列的列名
String columnName= md.getColumnName(i);
// 根据列名新建f接收数据并插入t中
Field f= c.getDeclaredField(columnName);
f.setAccessible(true);
f.set(t,value);
}
}
// 对象存入集合
tList.add(t);
}
return tList;
这样我们就可以获得一个来自数据库的列表了。在controler中进行测试。
@Test
public void list(){
List<Dept> list= deptService.list();
System.out.println(list);
}
Object ... params
为了进一步的简化并提高组件的泛用性,使用变长数组进行参数的获取。通过这个方法可以将可变个数的参数传入方法。
public static <T> List<T> list(String sql, Object ... params){
....
}
用户端的操做
三层架构中只有controler层是直接面向用户的,用户能且只能使用controler中的方法,所以所有的查询方法都会完整的走完三层结构,为了安全性和易用性,我们一般只会让用户输入值而不是整条sql语句。
controler层
public void slectRow(int id){
List<Dept> list= deptService.selectRow(id);
System.out.println(list);
}
service层
public List<Dept> selectRow(id) {
return deptDao.selectRow(id);
}
Dao层
public class DeptDaoImpl implements IDeptDao {
public List<Dept> selectRow(){
return JdbcUtil.selectRow("select * from t_dept where id="+id,Dept.class);
}
}
通过逐级的调用我们获得了一条指向特定id的记录,用户按正常操做并不能取得其他id的数据。
sql注入攻击
如果我们的id恰巧是string类型,并且用户恰巧想搞些事情,原本要输入数字的输入栏被填上了这样的字符串
3 or '1'='1'
这样看这串字符毫无意义,好像对结果不会造成什么影响,但是如果把这段字符按照执行的方式执行到Dao层时操做就变成了这样
return JdbcUtil.selectRow("select * from t_dept where id="+"3 or '1'='1'",Dept.class);
执行的sql语句变成了
select * from t_dept where id=3 or '1'='1'
其中”1=1“等式恒成立,or操做将判定式id=3稀释掉了,最终执行的语句为
select * from t_dept where true
操做从查询一行语句变成了查询整个列表。如果将这种行为带入到账号的注册和登录时,这个人就会获得很大的操做权限,甚至对他人的信息进行盗窃和修改。
预准备的描述(prepareStatement)
为了防止别有用心的人对数据库进行非法的操做,我们可以将原本的Statement语句改为prepareStatement,这个语句可以预先识别操做语句,将语句的格式强行定义为一开始设定的形式,而不是在用户输入后再进行语句的识别。
并且prepareStatement方法使用占位符进行数值的替换而不是使用拼接字符的方式
public class DeptDaoImpl implements IDeptDao {
public List<Dept> selectRow(){
return JdbcUtil.selectRow("select * from t_dept where id=?",Dept.class,id);
}
}
工具类中的Statement换为prepareStatement即可。
public static <T> List<T> selectRow(String sql, Object ... params){
....
}