初识JDBC-篇二
一、DAO设计规范
1.编写DAO组件
-
定义DAO接口
-
编写对应DAO实现类
为什么我们需要定义接口?
首先我们要知道接口就是只给出了函数声明,但是是没有函数体类。函数体在实现类中给出(必须实现)。我们可以根据客户提出的需求,定义接口,业务具体实现是通过实现类来完成。当客户提出新的需求,只需要编写该业务逻辑新的实现类。这就是面向接口编程,面向接口编程有以下几个好处
-
业务逻辑更加清晰
-
增强代码的扩展性,可维护性
-
接口和实现相分离,适合团队协作开发
-
降低耦合度。便于以后升级扩展
一个例子:
假设有一个数据库DAO,突然根据需要,程序要连接两个数据库,一个Oracle,一个MySQL。我们便可以利用多态的思想定义以下接口和实现类。
2.各种命名规范
1.包名规范
我们平时写java代码包名一般都是 域名倒写.模块名称.组件名称 这种格式,同样DAO也是符合这种格式。我们通常会建立以下四个包:
- package com.ThinMoon.jdbc.domain 存储所有的domain
- package com.thinmoon.jdbc.dao 存储所有的dao接口
- package com.thinmoon.jdbc.dao.impl 存储所有的Dao接口实现类
- package com.thinmoon.jdbc.dao.test 存储Dao组件的测试类
2.类名规范
类 | 起名规范 | 存储位置 |
---|---|---|
domain类 | 见名之意即可 | 存储在domain包中。用于描述一个对象,是一个javaBean |
dao接口 | IDomainDao 接口-domain-dao |
存储在dao包中,用于表示某一个对象的CRUD声明 |
dao实现类 | DomainDAOImpl domain-dao-impl |
存储到dao.impl包中,用于表示DAO接口的实现类,要实现DAO接口 |
一个栗子:
Dao接口类:IStudentDao
public interface IStudentDao {
//1.保存数据
void save(Student stu);
//2.修改数据
void update(Student stu);
//3.删除数据
void delete(int id);
//4.获取指定学生
Student get(int id);
//5.获取所有学生
List<Student> getAll();
}
Dao实现类:StudentDaoImpl
public class StudentDaoImpl implements IStudentDao {
@Override
public void save(Student stu) {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1.加载驱动
// 2.连接数据库
conn = JDBCUtil.getConn();
// 3.创建语句
int id = stu.getId();
int age = stu.getAge();
String name = stu.getName();
String sql = "insert into student(id, age, name)values(?, ?, ?)";
// 4.执行语句
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.setInt(2, age);
ps.setString(3, name);
int row = ps.executeUpdate();
// 5.释放
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.释放
JDBCUtil.close(conn, ps, null);
}
}
@Override
public void update(Student stu) {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1.加载驱动
// 2.连接数据库
conn = JDBCUtil.getConn();
// 3.创建语句
int id = stu.getId();
int age = stu.getAge();
String name = stu.getName();
String sql = "update student set name=?, age=? where id=?;";
ps = conn.prepareStatement(sql);
ps.setInt(3, id);
ps.setInt(2, age);
ps.setString(1, name);
int row = ps.executeUpdate();
// 5.释放
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.释放
JDBCUtil.close(conn, ps, null);
}
}
@Override
public void delete(int id) {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1.加载驱动
// 2.连接数据库
conn = JDBCUtil.getConn();
// 3.创建语句
String sql = "delete from student where id = ?;";
// 4.执行语句
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
int row = ps.executeUpdate();
// 5.释放
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.释放
JDBCUtil.close(conn, ps, null);
}
}
@Override
public Student get(int id) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet res = null;
try {
// 1.加载驱动
// 2.连接数据库
conn = JDBCUtil.getConn();
// 3.创建语句
String sql = "select * from student where id=?";
// 4.执行语句
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
res = ps.executeQuery();
if (res.next()) {
Student stu = new Student();
stu.setName(res.getString("name"));
stu.setAge(res.getInt("age"));
return stu;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtil.close(conn, ps, res);
}
return null;
}
@Override
public List<Student> getAll() {
Connection conn = null;
Statement st = null;
ResultSet res = null;
try {
// 1.加载驱动
// 2.连接数据库
conn = JDBCUtil.getConn();
// 3.创建语句
String sql = "select * from student;";
// 4.执行语句
st = conn.createStatement();
res = st.executeQuery(sql);
List<Student> list = new ArrayList<Student>();
while (res.next()) {
Student stu = new Student();
stu.setId(res.getInt("id"));
stu.setName(res.getString("name"));
stu.setAge(res.getInt("age"));
list.add(stu);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.释放
JDBCUtil.close(conn, st, res);
}
return null;
}
}
domain类:Student
public class Student {
Integer id;
Integer age;
String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
工具类:JDBCUtil
public class JDBCUtil {
public static String url = "jdbc:MySQL://localhost:3306/jdbc_db?serverTimezone=UTC&characterEncoding=utf-8";
public static String user = "root";
public static String pwd = "123456";
public static String driver = "com.mysql.cj.jdbc.Driver";
static {
// 1.加载驱动
try {
Class.forName(JDBCUtil.driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void close(Connection conn, Statement st, ResultSet res) {
// 5.释放
if (res != null) {
try {
res.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static Connection getConn() {
try {
// 2.连接数据库
return DriverManager.getConnection(JDBCUtil.url, JDBCUtil.user, JDBCUtil.pwd);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
测试类:StudentDaoTest
public class StudentDaoTest {
@Test
public void save() {
Student stu = new Student();
stu.setId(1);
stu.setName("李白");
stu.setAge(16);
IStudentDao dao = new StudentDaoImpl();
dao.save(stu);
}
@Test
public void delete() {
IStudentDao dao = new StudentDaoImpl();
dao.delete(1);
}
@Test
public void update() {
Student stu = new Student();
stu.setId(1);
stu.setAge(16);
stu.setName("老王");
IStudentDao dao = new StudentDaoImpl();
dao.update(stu);
}
@Test
public void get() {
IStudentDao dao = new StudentDaoImpl();
Student stu = dao.get(1);
if (stu != null) {
System.out.println(stu.getName());
System.out.println(stu.getAge());
}else {
System.out.println("null");
}
}
@Test
public void getAll() {
IStudentDao dao = new StudentDaoImpl();
List<Student> list = dao.getAll();
}
}
二、SQL注入问题
从上文中我们可以发现sql语句和第一篇文章中好像不一样了,并且代码中Statement变成了PreparedStatement,接下来我们就开始介绍什么是PreparedStatement,以及什么是sql注入问题和PreparedStatement是怎么防止sql注入的。
1.什么是PreparedStatement
PreparedStatement是Statement的一个子接口,又叫做预编译语句。见名之意,就是我们可以预先写好sql语句然后再后续代码中给我们在预先写好的sql语句中的占位符赋值,这样就简化了我们书写sql语句时需要拼接的麻烦步骤。
// 3.创建语句
int id = stu.getId();
int age = stu.getAge();
String name = stu.getName();
String sql = "insert into student(id, age, name)values(?, ?, ?)";
// 4.执行语句
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.setInt(2, age);
ps.setString(3, name);
int row = ps.executeUpdate();
类似上述代码般,我们可以给利用setXxx()给占位符赋值方便我们的书写。
2.什么是sql注入
在了解什么是sql注入前我们先看一个例子:
上面代码是一个简单的登录代码,通过和数据库对比用户名、密码返回是否登陆成功。可以看到我们上面还是使用之前sql语句需要自己进行拼接的情况,那么这种情况会出现什么问题呢?
在1中我们假设数据库中存在用户zs并且密码也是正确的,那么我们很自然的会登入成功这毋庸置疑。那么在2中呢?可以看到我们把2中的用户名写成" ' or 1=1 or ' "
这种形式,那么当他与我们写的sql进行拼接时就会变成
select * from user where name = ' ' or 1=1 or ' ' and pwd = '12'
这样我们就得到一个返回值不为null的res了那么我们就可以成功进入系统了,这是一个极其危险的问题,我们把它称之为sql注入。
所以我们知道了什么是sql注入,SQL注入就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
3.PreparedStatement是怎么防止sql注入的?
我们将上面代码替换成PreparedStatement后再次用原来 " ' or 1=1 or ' "
这种恶意代码去尝试登入会有什么结果呢?结果当然是登陆失败啦。当我们使用PreparedStatement时再用原来的恶意代码去尝试登陆,我们得到的拼接字符串是像下面代码一般:
select * from user where name = '\' or 1=1 or \' ' and pwd = '12'
可以看到恶意代码上的'
都被加上了转移字符,这就是PreparedStatement能够防止大部分sql注入的原理。PreparedStatement会对非法字符加上转义字符后在进行拼接!
三、调用存储过程
调用存储过程需要用到CallableStatement,CallableStatement也是Statement的一个子接口。
1.调用无输出参数存储过程
public static void main(String[] args) throws Exception {
Connection conn = JDBCUtil.getConn();
//1.调用存储过程
CallableStatement cs = conn.prepareCall("{ call getStu(?)}");
//2.设置参数
cs.setString(1, "老王");
//3.执行存储过程
ResultSet res = cs.executeQuery();
if(res.next()) {
Student stu = new Student();
stu.setId(res.getInt("id"));
stu.setAge(res.getInt("age"));
stu.setName(res.getString("name"));
System.out.println(stu);
}
}
2.调用有输出参数存储过程
public static void main(String[] args) throws Exception {
Connection conn = JDBCUtil.getConn();
//1.调用存储过程
CallableStatement cs = conn.prepareCall("{ call getName(?,?)}");
//2.设置参数
cs.setInt(1, 1);
cs.registerOutParameter(2, Types.VARCHAR);
//3.执行存储过程
cs.execute();
String name = cs.getString(2);
System.out.println(name);
}