day54
JDBC
封装工具类01
创建配置文件
DBConfig.properties
driverName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/qnz01?characterEncoding=utf8&serverTimezone=UTC
username=root
password=root
新建配置文件,不用写后缀名
创建工具类
- 将变化的配置信息搬到配置文件中
- 工具类提供获取连接的方法
- 工具类提供关闭资源的方法
package com.qf.utils;
/**
* 数据库工具类
*/
public class DBUtil {
private static String url;
private static String username;
private static String password;
static{
//负责加载配置文件,只加载一次
Properties properties = new Properties();
try {
properties.load(DBUtil.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
String driverName = properties.getProperty("driverName");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
try {
//加载驱动只加载一次
Class.forName(driverName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 获取连接对象
*/
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url,username,password);
return connection;
}
/**
* 关闭资源
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
理解
(1)新建配置文件
把导入驱动包和获取连接对象的参数url、root、password写入配置文件
(2)将获取连接对象方法封装到工具类DBUtil
01:加载配置文件
new 一个配置文件对象,配置文件对象调用加载方法加载进来【报异常先用try-catch】
之后就可以配置文件对象调用方法拿到配置信息【注意要与配置文件的名字相同,否则找不到就是null】
02:导入驱动包03:在获取连接对象方法里,调用方法获取连接对象,返回连接对象
04:对于关闭资源,不用每次都写,直接在工具类写个关闭资源方法传入可能关的资源,里面非空判断哪些要关就行了
注意:
通常会把加载配置文件和导入驱动包放在静态代码块里,提高效率,不用获取一次连接加载一次,加载一次就行
对于重复使用的属性先静态定义【注意静态代码块只能初始化静态的属性】基本操作报错try-catch【例如类这里的forName类未找到异常就要么资源文件没导入,要么配置文件对应的名称没写对】,对于需要对应不同业务处理异常就需要抛异常,在外面处理
现在对于先前的增删改查就不用那么麻烦,每个都写导入驱动包和获取连接对象的一大坨代码,当然这里就涉及封装工具类
直接用工具类调用获取连接对象的方法,关闭资源方法
测试类
对day53的增删改查进行进一步优化
package com.qf.jdbc01;
import com.qf.utils.DBUtil;
import org.junit.Test;
import java.sql.*;
public class Test01 {
/**
* 知识点:封装DBUtil - v1.0
*/
//添加数据
@Test
public void test01() throws SQLException {
//获取连接对象
Connection connection = DBUtil.getConnection();
//获取发送指令对象
Statement statement = connection.createStatement();
//发送SQL指令
String sql = "INSERT INTO student(name,sex,age,salary,course) VALUES('柳如烟','女',28,6000,'HTML');";
int num = statement.executeUpdate(sql);
System.out.println("对于" + num + "行造成了影响");
//关闭资源
DBUtil.close(connection,statement,null);
}
//删除数据
@Test
public void test02() throws SQLException {
//获取连接对象
Connection connection = DBUtil.getConnection();
//获取发送指令对象
Statement statement = connection.createStatement();
//发送SQL指令
String sql = "DELETE FROM student WHERE id>10;";
int num = statement.executeUpdate(sql);
System.out.println("对于" + num + "行造成了影响");
//关闭资源
DBUtil.close(connection,statement,null);
}
//修改数据
@Test
public void test03() throws SQLException {
//获取连接对象
Connection connection = DBUtil.getConnection();
//获取发送指令对象
Statement statement = connection.createStatement();
//发送SQL指令
String sql = "UPDATE student SET age=21,salary=50000 WHERE id=3;";
int num = statement.executeUpdate(sql);
System.out.println("对于" + num + "行造成了影响");
//关闭资源
DBUtil.close(connection,statement,null);
}
//查询数据
@Test
public void test04() throws SQLException {
//获取连接对象
Connection connection = DBUtil.getConnection();
//获取发送指令对象
Statement statement = connection.createStatement();
//发送SQL指令,并获取结果集对象
String sql = "select * from student";
ResultSet resultSet = statement.executeQuery(sql);
//遍历结果集
while(resultSet.next()){//判断是否有可迭代的数据行
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String sex = resultSet.getString("sex");
int age = resultSet.getInt("age");
float salary = resultSet.getFloat("salary");
String course = resultSet.getString("course");
System.out.println(id + " -- " + name + " -- " + sex + " -- " + age + " -- " + salary + " -- " + course);
}
//关闭资源
DBUtil.close(connection,statement,resultSet);
}
}
SQL注入问题
理解
ps:select * from student where name=‘’ or 1=1 #’ and passwd=‘111111’;
当输入用户名和密码
’ or 1=1 # '【对于它分不清sql命令和数据,就会or 1=1判断为true,#后面理解为注释,就直接登录进去了】
package com.qf.jdbc02;
import com.qf.utils.DBUtil;
public class Test01 {
/**
* 知识点:SQL注入问题
*
* 出现原因:数据库分不清哪些是sql命令,哪些是数据
*
* 需求:模拟登录功能
*/
public static void main(String[] args) throws SQLException {
Connection connection = DBUtil.getConnection();
Statement statement = connection.createStatement();
Scanner scan = new Scanner(System.in);
System.out.println("请输入账号:");
String usernameVal = scan.nextLine();
System.out.println("请输入密码:");
String passwordVal = scan.nextLine();
//select * from user where username='' or 1=1 #' and password='12312345'
String sql = "select * from user where username='"+usernameVal+"' and password='"+passwordVal+"'";
ResultSet resultSet = statement.executeQuery(sql);
if(resultSet.next()){
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String nikeName = resultSet.getString("nike_name");
System.out.println("登录成功");
System.out.println(username);
System.out.println(password);
System.out.println(nikeName);
}else{
System.out.println("登录失败");
}
DBUtil.close(connection,statement,resultSet);
}
}
解决
采用预编译对象来编程:告诉他,使用prepareStatement
sql命令中用?表示数据
注意:prepareStatement继承于statement
现在用prepareStatement拿到的sql是没有数据的,得到它的对象
再将输入的数据设置给这个对象,注意方法SetString的两个参数(下标,值)【注意下标从1开始】
- 安全性,避免了SQL注入
- 性能,预编译,语句-编译-执行
package com.qf.jdbc02;
import com.qf.utils.DBUtil;
public class Test02 {
/**
* 知识点:SQL注入问题
*
* 出现原因:数据库分不清哪些是sql命令,哪些是数据
* 解决方案:告诉数据库哪些是sql命令,哪些是数据
*
* 需求:模拟登录功能
*/
public static void main(String[] args) throws SQLException {
Connection connection = DBUtil.getConnection();
String sql = "select * from user where username=? and password=?";
PreparedStatement statement = connection.prepareStatement(sql);
//输入数据后,将数据设置给statement对象
Scanner scan = new Scanner(System.in);
System.out.println("请输入账号:");
String usernameVal = scan.nextLine();
System.out.println("请输入密码:");
String passwordVal = scan.nextLine();
statement.setString(1,usernameVal);
statement.setString(2,passwordVal);
ResultSet resultSet = statement.executeQuery();
if(resultSet.next()){
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String nikeName = resultSet.getString("nike_name");
System.out.println("登录成功");
System.out.println(username);
System.out.println(password);
System.out.println(nikeName);
}else{
System.out.println("登录失败");
}
DBUtil.close(connection,statement,resultSet);
}
}
封装工具类02
更新数据(添加、删除、修改):
01方法传的参数(一个sql命令,另一个数据不确定用可变参数)
02由于不知道下标和值用setObject设置给statement,但有多个参数需要遍历处理就写个方法来专门处理【处理statement对象参数数据的处理器,注意setObject下标从1开始】直接调用处理
03添加操作返回影响行数
04而考虑到关闭资源,处理异常就不会关闭资源,对代码进行try-finally,考虑作用域把定义对象放在之外
添加–主键回填:
就是添加数据后,不再是返回影响的行数,加一个查询返回主键
主键回填理解:
查询:
以前查询就打印出来结果,现在查询的数据封装成对象,不同表封装不同的对象
01学生类:有参无参自动生成时,window+alt+回车快速生成,生成时涉及属性选择ctrl+1全选
02方法返回值设置成list集合,类型用泛型;传的参数(哪个类的对象clazz【反射创建】,一个sql,一个可变参数)
03对于拿到的结果集,需要遍历, 一个数据创建一个对象
先泛型创建对象【无数据】,获取数据字段名才可以利用反射去设置对象的属性
怎么获取:先用结果集对象调用方法获取表数据对象,再表数据对象调用方法获取字段个数,再在遍历中通过字段个数循环获取字段名,字段值反射获取类的属性对象和设置对象的属性【对于反射设置属性回顾以前反射讲到的方法】
04然后将封装的对象添加到对象集合再返回
05而考虑到关闭资源,处理异常就不会关闭资源,对代码进行try-finally,考虑作用域把定义对象放在之外
package com.qf.utils;
/**
* 数据库工具类
*/
public class DBUtil {
private static String url;
private static String username;
private static String password;
static{
//负责加载配置文件,只加载一次
Properties properties = new Properties();
try {
properties.load(DBUtil.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
String driverName = properties.getProperty("driverName");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
try {
//加载驱动只加载一次
Class.forName(driverName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 获取连接对象
*/
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url,username,password);
return connection;
}
/**
* 关闭资源
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* 更新数据(添加、删除、修改)
*/
public static int commonUpdate(String sql,Object... params) throws SQLException {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = getConnection();
statement = connection.prepareStatement(sql);
paramHandler(statement,params);
int num = statement.executeUpdate();
return num;
}finally {
close(connection,statement,null);
}
}
/**
* 添加数据 - 主键回填(主键是int类型可以返回)
*/
public static int commonInsert(String sql,Object... params) throws SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
statement = connection.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
paramHandler(statement,params);
statement.executeUpdate();
resultSet = statement.getGeneratedKeys();
int primaryKey = 0;
if(resultSet.next()){
primaryKey = resultSet.getInt(1);
}
return primaryKey;
}finally {
close(connection,statement,resultSet);
}
}
/**
* 查询数据
*/
public static <T> List<T> commonQuery(Class<T> clazz,String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
statement = connection.prepareStatement(sql);
paramHandler(statement,params);
resultSet = statement.executeQuery();
//获取表数据对象
ResultSetMetaData metaData = resultSet.getMetaData();
//获取字段个数
int count = metaData.getColumnCount();
List<T> list = new ArrayList<>();
while(resultSet.next()){
T t = clazz.newInstance();
//获取字段名及数据
for (int i = 1; i <= count; i++) {
String fieldName = metaData.getColumnName(i);
Object fieldVal = resultSet.getObject(fieldName);
setField(t,fieldName,fieldVal);
}
list.add(t);
}
return list;
} finally {
DBUtil.close(connection,statement,resultSet);
}
}
/**
* 处理statement对象参数数据的处理器
*/
private static void paramHandler(PreparedStatement statement,Object... params) throws SQLException {
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1,params[i]);
}
}
/**
* 获取当前类及其父类的属性对象
* @param clazz class对象
* @param name 属性名
* @return 属性对象
*/
private static Field getField(Class<?> clazz,String name){
for(Class<?> c = clazz;c != null;c = c.getSuperclass()){
try {
Field field = c.getDeclaredField(name);
return field;
} catch (NoSuchFieldException e) {
} catch (SecurityException e) {
}
}
return null;
}
/**
* 设置对象中的属性
* @param obj 对象
* @param name 属性名
* @param value 属性值
*/
private static void setField(Object obj,String name,Object value){
Field field = getField(obj.getClass(), name);
if(field != null){
field.setAccessible(true);
try {
field.set(obj, value);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
调用工具类的方法实现增删改查
package com.qf.jdbc03;
import com.qf.utils.DBUtil;
public class Test01 {
/**
* 知识点:封装数据库 - v2.0
*/
//添加数据
@Test
public void test01() throws SQLException {
String sql = "INSERT INTO student(name,sex,age,salary,course) VALUES(?,?,?,?,?)";
int num = DBUtil.commonUpdate(sql, "天使萌", "女", 23, 10000, "HTML");
System.out.println("对于" + num + "行造成了影响");
}
//删除数据
@Test
public void test02() throws SQLException {
String sql = "delete from student where id=?";
int num = DBUtil.commonUpdate(sql, 3);
System.out.println("对于" + num + "行造成了影响");
}
//修改数据
@Test
public void test03() throws SQLException {
String sql = "update student set salary=? where id=?";
int num = DBUtil.commonUpdate(sql, 15000,1);
System.out.println("对于" + num + "行造成了影响");
}
//添加数据 - 主键回填
@Test
public void test04() throws SQLException {
String sql = "INSERT INTO student(name,sex,age,salary,course) VALUES(?,?,?,?,?)";
int primaryKey = DBUtil.commonInsert(sql, "铃原爱蜜莉", "女", 23, 10000, "HTML");
System.out.println("返回的主键是:" + primaryKey);
}
//查询数据
@Test
public void test05() throws SQLException, InstantiationException, IllegalAccessException {
String sql = "select * from student where id<?";
List<Student> list = DBUtil.commonQuery(Student.class, sql, 8);
for (Student stu : list) {
System.out.println(stu);
}
}
}
学生类
package com.qf.jdbc03;
public class Student {
private int id;
private String name;
private String sex;
private int age;
private float salary;
private String course;
public Student() {
}
public Student(int id, String name, String sex, int age, float salary, String course) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
this.salary = salary;
this.course = course;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
", salary=" + salary +
", course='" + course + '\'' +
'}';
}
}
小结:
JDBC:封装工具类,对增删改查优化,SQL注入问题