1、预编译:statement 与 PreparedStatement
1.1 sql提供访问的接口:
- 数据库连接被用于向数据库服务器发送命令和 SQL 语句,在连接建立后,需要对数据库进行访问,执行 sql 语句
- 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
|----- Statement
|------PreparedStatement
|------CallableStatement
使用statement 进行预编译可能会导致SQL注入的发生
1.2 SQL 注入攻击
- SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,从而利用系统的 SQL 引擎完成恶意行为的做法
- 对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了
1.3 PreparedStatement
- 可以通过调用 Connection 对象的 preparedStatement() 方法获取 PreparedStatement 对象
- PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
- PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
1.4 PreparedStatement vs Statement
- 代码的可读性和可维护性.
- PreparedStatement 能最大可能提高性能:
DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.这样每执行一次都要对传入的语句编译一次.
(语法检查,语义检查,翻译成二进制命令,缓存)
- PreparedStatement 可以防止 SQL 注入防止 SQL 注入
1.5 数据类型转换表
1.6 连接数据库、操作表的步骤:
注意:
- 释放ResultSet, Statement,Connection。
- 数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
2 、ResultSet
2.1 ResultSet 的作用:
- 通过调用 PreparedStatement 对象的 excuteQuery() 方法创建该对象
- ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商实现
- ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行
ResultSet 接口的常用方法:
-
boolean next()
getString( )
2.2 案例
(1)
(2)
2.3 ResultSet的说明
1. 查询需要调用Prepared Statement 的 executeQuery() 方法,查询结果是一个 ResultSet 对象
2. 关于 ResultSet:代表结果集
- ResultSet: 结果集. 封装了使用 JDBC 进行查询的结果.
- 调用 PreparedStatement 对象的 executeQuery() 可以得到结果集.
- ResultSet 返回的实际上就是一张数据表. 有一个指针指向数据表的第一条记录的前面.
3.可以调用 next() 方法检测下一行是否有效. 若有效该方法返回 true, 且指针下移. 相当于Iterator 对象的 hasNext() 和 next() 方法的结合体
4.当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值.
例如: getInt(1), getString("name")
5.ResultSet 当然也需要进行关闭.
3、 普通的增删查改
3.1 创建一个类customer
public class Customer {
private int id;
private String name;
private String email;
private Date birth;
public Customer() {
super();
}
public Customer(int id, String name, String email, Date birth) {
super();
this.id = id;
this.name = name;
this.email = email;
this.birth = birth;
}
3.2 创建jdbc连接,并封装为方法
/* * 获取数据库的连接
*/
public static Connection getConnection() {
// 数据库的连接
Connection connection = null;
InputStream is = null;
try {
is = JDBCUtils.class.getClassLoader().getResourceAsStream("sqlDriver.properties");
Properties ps = new Properties();
ps.load(is);
String user = ps.getProperty("user");
String password = ps.getProperty("password");
String driverClass = ps.getProperty("driverClass");
String url = ps.getProperty("url");
// 创建Driver对象
Class clazz = Class.forName(driverClass);
Object obj = clazz.newInstance();
Driver driver = (Driver) obj;
// 注册驱动
DriverManager.registerDriver(driver);
connection = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return connection;
}
3.3 在操作完成后要进行连接的关闭
public static void close(Connection connection, PreparedStatement ps) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection connection, PreparedStatement ps, ResultSet rs) {
close(connection,ps);
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.4 增删查改操作
/*
* 对数据库的 增 ,删,改,查
*/
public class CRUDTest {
/*
* 从数据库中查找一条数据
*/
@Test
public void select() throws Exception {
Customer customer = getCustomerById();
System.out.println(customer);
List<Customer> customers = getCustomers();
for (Customer customer2 : customers) {
System.out.println(customer2);
}
}
/*
* 查询多条数据
*/
public List<Customer> getCustomers() throws Exception {
// 1.获取数据库的连接
Connection connection = JDBCUtils.getConnection();
String sql = "select * from customers";
// 2.预编译 -- 如果没有占位符可以不填充数据
PreparedStatement ps = connection.prepareStatement(sql);
// 4.执行sql语句
ResultSet rs = ps.executeQuery(); // 查找的结果都已经放到ResultSet中了
List<Customer> list = new ArrayList<>();
while(rs.next()){
//获取数据
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
//将每条数据放入集合中
list.add(new Customer(id, name, email, birth));
}
// 6.关闭资源
JDBCUtils.close(connection, ps, rs);
return list;
}
/*
* 查询一条数据
*/
public Customer getCustomerById() throws Exception {
// 1.获取数据库的连接
Connection connection = JDBCUtils.getConnection();
String sql = "select * from customers where id = ?";
// 2.预编译
PreparedStatement ps = connection.prepareStatement(sql);
// 3.填充数据
ps.setInt(1, 20);
// 4.执行sql语句
ResultSet rs = ps.executeQuery(); // 查找的结果都已经放到ResultSet中了
Customer customer = null;
// 5.取数据
if (rs.next()) { // 如果有数据就返回true,否则返回false
// 获取列上值的第一种方式
// int id = rs.getInt(1); //根据列的索引获取对应的列上的值
// String name = rs.getString(2);
// String email = rs.getString(3);
// Date date = rs.getDate(4);
// 获取列上值的第二种方式
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
// 考虑将数据进行封装
customer = new Customer(id, name, email, birth);
// 输出获取到的内容
System.out.println(id + " " + name + " " + email + " " + birth);
}
// 6.关闭资源
JDBCUtils.close(connection, ps, rs);
return customer;
}
/*
* 向数据库中插入一条数据
*/
@Test
public void insert() {
Connection connection = null;
PreparedStatement ps = null;
try {
// 1 : 连接数据库
connection = JDBCUtils.getConnection();
// sql语句
String sql = "INSERT INTO customers(NAME,email,birth) " + "VALUES(?,?,?);";
// 2.预编译
ps = connection.prepareStatement(sql);
// 3.设置内容
ps.setString(1, "强哥"); // 第一个参数:第几个问号 第二个参数:内容
ps.setString(2, "[email protected]");
// 获取一个sql下的Date
Date date = new Date(new java.util.Date().getTime());
ps.setDate(3, date);
// 4.执行sql语句
ps.execute(); // 如果是查询语句返回true
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.关闭资源
JDBCUtils.close(connection, ps);
}
}
/*
* 删除一条件数
*/
@Test
public void delete() throws Exception {
// 1. 获取数据库的连接
Connection connection = JDBCUtils.getConnection();
String sql = "DELETE FROM customers WHERE id = ?";
// 2. 预编译
PreparedStatement ps = connection.prepareStatement(sql);
// 3.填充数据
ps.setInt(1, 19);
// 4.执行sql语句
int executeUpdate = ps.executeUpdate();
System.out.println(executeUpdate + "条受到影响");
// 5.关闭资源
JDBCUtils.close(connection, ps);
}
/*
* 修改一条件数
*/
@Test
public void update() throws Exception {
Connection connection = null;
PreparedStatement ps = null;
try {
connection = JDBCUtils.getConnection();
String sql = "UPDATE customers SET NAME = ? WHERE id = ?";
ps = connection.prepareStatement(sql);
ps.setString(1, "成哥");
ps.setInt(2, 18);
int executeUpdate = ps.executeUpdate();
System.out.println(executeUpdate + "条数据受到影响");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(connection, ps);
}
}
}
4、通用增删查改
创建类 User.java
public class User {
private int id;
private String name;
private String address;
private String phone;
4.1 创建通用连接方案 - 通用增删改
/*
* 通用的增删改
*/
public static int UID(String sql,Object ...objects) {
// 1. 获取数据库的连接
Connection connection = null;
// 2. 预编译
PreparedStatement ps = null;
// 4.执行sql语句
int executeUpdate = -1;
try {
connection = JDBCUtils.getConnection();
ps = connection.prepareStatement(sql);
// 3.填充数据
for (int i = 0; i < objects.length; i++) { // i 表示索引
ps.setObject(i + 1, objects[i]); // i + 1表示的是第几个占位符
}
executeUpdate = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally{
// 5.关闭资源
JDBCUtils.close(connection, ps);
}
return executeUpdate;
}
测试代码:
/*
* 测试通用的增删改
*/
@Test
public void insert(){
String sql = "INSERT INTO customers(NAME,email,birth) VALUES(?,?,?)";
Date date = new java.sql.Date(new java.util.Date().getTime());
int uid = JDBCUtils.UID(sql, "强哥","[email protected]",date);
System.out.println(uid + "条数据受到影响");
}
4.2 通用查找(一条或多条)
只查一条数据:
/*
* 通用的查找 (只有一条数据)
*
* Class<T> clazz : 运行时类的对象
*/
public static <T> T getObject(Class<T> clazz,String sql,Object ...objects) throws Exception{
//1.获取数据库的连接
Connection connection = JDBCUtils.getConnection();
//2.预编译
PreparedStatement ps = connection.prepareStatement(sql);
//3.填充数据
for (int i = 0; i < objects.length; i++) {
ps.setObject(i + 1, objects[i]);
}
//4.执行sql语句
ResultSet rs = ps.executeQuery();
ResultSetMetaData md = rs.getMetaData(); //获取元数据。
//获取列的数量
int columnCount = md.getColumnCount();
//创建对象
T t = clazz.newInstance();
while(rs.next()){ //遍历每一条数据
/*
* 第一个问题 : 如何获取到表的列数
* 第二个问题 : 需要知道类中的属性
* 第三个问题 : 对象中属性的名字怎么来
*/
for (int i = 0; i < columnCount; i++) {
//获取某列的列名(如果有别名获取的是别名)
String columnLabel = md.getColumnLabel(i + 1);
//通过列名获取列中的数据。
Object value = rs.getObject(columnLabel);
//封装
//通过反射 - 给对象中的属性进行赋值
//将表中的列名当作类中的属性名。如果列名和属性名不一样,可以通过别名的方式(别名 = 属性名)
//通过列名就获取到了类中的对应的属性
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);
/*
* 第一个参数 : 是给哪个对象进行赋值
* 第二个参数 : 属性值
*/
declaredField.set(t, value);
}
}
//关闭资源
JDBCUtils.close(connection, ps, rs);
return t;
}
查多条数据:
/*
* 通用的查找 (返回多条数据)
*
*/
public static <T> List<T> getObjects(Class<T> clazz,String sql,Object ...objects) throws Exception{
//1.获取数据库的连接
Connection connection = JDBCUtils.getConnection();
//2.预编译
PreparedStatement ps = connection.prepareStatement(sql);
//3.填充数据
for (int i = 0; i < objects.length; i++) {
ps.setObject(i + 1, objects[i]);
}
//4.执行sql语句
ResultSet rs = ps.executeQuery();
ResultSetMetaData md = rs.getMetaData(); //获取元数据。
//获取列的数量
int columnCount = md.getColumnCount();
//创建一个集合
List<T> list = new ArrayList<>();
while(rs.next()){ //遍历每一条数据
//创建对象
T t = clazz.newInstance();
/*
* 第一个问题 : 如何获取到表的列数
* 第二个问题 : 需要知道类中的属性
* 第三个问题 : 对象中属性的名字怎么来
*/
for (int i = 0; i < columnCount; i++) {
//获取某列的列名(如果有别名获取的是别名)
String columnLabel = md.getColumnLabel(i + 1);
//通过列名获取列中的数据。
Object value = rs.getObject(columnLabel);
//封装
//通过反射 - 给对象中的属性进行赋值
//将表中的列名当作类中的属性名。如果列名和属性名不一样,可以通过别名的方式(别名 = 属性名)
//通过列名就获取到了类中的对应的属性
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);
/*
* 第一个参数 : 是给哪个对象进行赋值
* 第二个参数 : 属性值
*/
declaredField.set(t, value);
}
//将对象放入到集合中
list.add(t);
}
//关闭资源
JDBCUtils.close(connection, ps, rs);
return list;
}
测试代码:
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = getObject(Customer.class, sql, 5);
System.out.println(customer);
sql = "select * from users where id = ?";
User object = getObject(User.class, sql, 1);
System.out.println(object);
sql = "select * from users";
List<User> users = getObjects(User.class, sql);
for (User user : users) {
System.out.println(user);
}