目 录
一、实现简单的登录功能
public class DbUtils {
// 工具类的构造方法都是私有的,不能被实例化。
private DbUtils() {}
// 静态变量
private static String driver;
private static String url;
private static String user;
private static String password;
static {
// 读取属性配置文件
ResourceBundle bundle = ResourceBundle.getBundle("jdbc.cjjdbc");
driver = bundle.getString("driver");
url = bundle.getString("url");
user = bundle.getString("user");
password = bundle.getString("password");
// 注册驱动只需要执行一次,所以放在静态代码块中,在类加载时执行
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public class Login {
public static void main(String[] args) {
System.out.println("======欢迎登录系统======");
// 获取用户输入
Scanner sc = new Scanner(System.in);
System.out.print("请输入用户名:");
String userName = sc.nextLine();
System.out.print("请输入密码:");
String password = sc.nextLine();
// 连接数据库
Connection connection = null;
Statement statement = null;
ResultSet results = null;
boolean login = false;
String realName = null;
try {
connection = DbUtils.getConnection();
statement = connection.createStatement();
String selectSQL = "select * from t_user where name = '" + userName + "' and password = '" + password + "'";
results = statement.executeQuery(selectSQL);
// 结果集中有数据,则登录成功
if (results.next()) {
login = true;
realName = results.getString("realname");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(connection, statement, results);
}
System.out.println(login ? "登录成功,欢迎" + realName : "登录失败");
}
}
二、SQL 注入
1.演示
先进行操作演示 SQL 注入。
2.说明
- 导致 SQL 注入最根本的原因:先进行 SQL 语句字符串拼接,再进行 SQL 语句的编译。用户输入中包含了 SQL 语句关键字并参与了编译;
- 解决方法:JDBC 为 Statement 接口提供了一个子接口 PreparedStatement,被称为预编译数据库操作对象。其可以对 SQL 语句进行预编译,然后给预编译好的 SQL 语句占位符传值。
3.PreparedStatement
/*
* PrepareStatement 登录
* 1.对 SQL 语句进行预编译
* 2.为 SQL 语句中的占位符传值
* */
public class Login {
public static void main(String[] args) {
System.out.println("======欢迎登录系统======");
// 获取用户输入
Scanner sc = new Scanner(System.in);
System.out.print("请输入用户名:");
String userName = sc.nextLine();
System.out.print("请输入密码:");
String password = sc.nextLine();
// 连接数据库
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet results = null;
boolean login = false;
String realName = null;
try {
connection = DbUtils.getConnection();
// 创建 PrepareStatement 对象
String selectSQL = "select * from t_user where name = ? and password = ?";
preparedStatement = connection.prepareStatement(selectSQL);
// 为 SQL 语句中的占位符传值, 从 1 开始
preparedStatement.setString(1, userName);
preparedStatement.setString(2, password);
System.out.println(selectSQL);
results = preparedStatement.executeQuery();
// 结果集中有数据,则登录成功
if (results.next()) {
login = true;
realName = results.getString("realname");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(connection, preparedStatement, results);
}
System.out.println(login ? "登录成功,欢迎" + realName : "登录失败");
}
}
- 通过使用 PreparedStatement,SQL 注入问题已被解决。因为【123' or '1'='1】虽然含有 SQL 关键字,但是只会被当作普通值传入,并没有参与编译;
- 注意:
- 带有占位符【?】的 SQL 语句称为:预处理 SQL 语句;
- 占位符不能使用引号包裹,若被包裹,它只是一个普通的问号字符;
- 执行 SQL 语句前,必须给占位符传值。
- PreparedStatement 和 Statement 都是执行 SQL 语句的接口,区别是:
- PreparedStatement 预编译 SQL 语句,Statement 直接提交 SQL 语句;
- PreparedStatement 执行速度更快,同一条 SQL 语句一次编译、多次执行,且可以避免 SQL 注入攻击;
- PreparedStatement 会进行类型检查。
三、PreparedStatement 的其他操作
# 初始化
DROP TABLE IF EXISTS EMP;
CREATE TABLE EMP(
EMPNO int(4) not null ,
ENAME VARCHAR(10),
JOB VARCHAR(9),
MGR INT(4),
HIREDATE DATE DEFAULT NULL,
SAL DOUBLE(7,2),
COMM DOUBLE(7,2),
primary key (EMPNO),
DEPTNO INT(2)
);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800, NULL, 20);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600, 300, 30);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250, 500, 30);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975, NULL, 20);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250, 1400, 30);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850, NULL, 30);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450, NULL, 10);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3000, NULL, 20);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7839, 'KING', 'PRESIDENT', NULL, '1981-11-17', 5000, NULL, 10);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500, 0, 30);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1100, NULL, 20);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950, NULL, 30);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000, NULL, 20);
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO ) VALUES ( 7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300, NULL, 10);
1.新增
public class PreparedStatementInsert {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DbUtils.getConnection();
String insertSQL = "insert into emp values(?,?,?,?,?,?,?,?)";
ps = conn.prepareStatement(insertSQL);
ps.setInt(1, 8000);
ps.setString(2, "牛马");
ps.setString(3, "IT");
ps.setInt(4, 7902);
LocalDate date = LocalDate.parse("2000-01-01");
ps.setDate(5, java.sql.Date.valueOf(date));
ps.setDouble(6, 5000);
ps.setDouble(7, 500);
ps.setInt(8, 10);
int count = ps.executeUpdate();
System.out.println("新增" + count + "条记录");
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(conn, ps, null);
}
}
}
2.修改
public class PreparedStatementUpdate {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DbUtils.getConnection();
String updateSQL = "update emp set ename = ? where empno = ?";
ps = conn.prepareStatement(updateSQL);
ps.setString(1, "冯国强");
ps.setInt(2, 8000);
int count = ps.executeUpdate();
System.out.println("修改" + count + "条记录");
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(conn, ps, null);
}
}
}
3.删除
public class PreparedStatementDelete {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DbUtils.getConnection();
String deleteSQL = "delete from emp where job = ?";
ps = conn.prepareStatement(deleteSQL);
ps.setString(1, "IT");
int count = ps.executeUpdate();
System.out.println("删除" + count + "条记录");
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(conn, ps, null);
}
}
}
4.模糊查询
public class PreparedStatementLikeSelect {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DbUtils.getConnection();
String selectSQL = "select ename, job from emp where ename like ?";
ps = conn.prepareStatement(selectSQL);
ps.setString(1, "_O%");
rs = ps.executeQuery();
System.out.println("[ename]\t[job]");
while (rs.next()) {
System.out.println(rs.getString("ename") + "\t" + rs.getString("job"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(conn, ps, rs);
}
}
}
5.分页查询
分页查询可以回顾【MySQL基础关键_006_DQL(五)】。
查询第二页所有员工的姓名,每页显示 3 条数据。
public class PreparedStatementPageSelect {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
int pageSize = 3;
int pageNum = 2;
try {
conn = DbUtils.getConnection();
String selectSQL = "select ename from emp limit ?,?";
ps = conn.prepareStatement(selectSQL);
ps.setInt(1, (pageNum - 1) * pageSize);
ps.setInt(2, pageSize);
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("ename"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(conn, ps, rs);
}
}
}
6.blob 数据
blob(binary large object),二进制大对象。
# 初始化
drop table if exists t_img;
create table t_img(
id bigint primary key auto_increment,
name varchar(10),
img blob
) engine = innoDB;
(1)插入
public class PSBlobInsert {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
FileInputStream fis = null;
try {
conn = DbUtils.getConnection();
String selectSQL = "insert into t_img(name, img) values(?, ?)";
ps = conn.prepareStatement(selectSQL);
ps.setString(1, "JDBC");
fis = new FileInputStream("D:\\test.jpg");
ps.setBlob(2, fis);
int count = ps.executeUpdate();
System.out.println("插入" + count + "条记录");
} catch (SQLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
DbUtils.close(conn, ps, null);
}
}
}
(2)读取
public class PSBlobSelect {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DbUtils.getConnection();
String selectSQL = "select img from t_img where name = ?";
ps = conn.prepareStatement(selectSQL);
ps.setString(1, "JDBC");
rs = ps.executeQuery();
while (rs.next()) {
InputStream in = rs.getBinaryStream("img");
OutputStream out = new FileOutputStream("D:\\testNew.jpg");
byte[] bytes = new byte[1024];
int count = 0;
while ((count = in.read(bytes)) != -1) {
out.write(bytes, 0, count);
}
out.flush();
in.close();
out.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.close(conn, ps, rs);
}
}
}
四、批处理
# 初始化
drop table if exists t_batch;
create table t_batch(
id bigint primary key,
name varchar(10)
);
1.未使用批处理
public class BatchTest {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DbUtils.getConnection();
String insertSQL = "insert into t_batch values(?, ?)";
ps = conn.prepareStatement(insertSQL);
int count = 0;
// 循环插入1万条记录
for (int i = 0; i < 10000; i++) {
ps.setInt(1, i);
ps.setString(2, "牛马" + i);
count += ps.executeUpdate();
}
System.out.println("插入" + count + "条记录");
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(conn, ps, null);
}
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - begin) + "ms");
}
}
2.使用批处理
开启批处理,在 URL 后面添加参数:【rewriteBatchedStatements=true】。
# cjjdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc?rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false
user=root
password=root
# 再次执行,以清空表
drop table if exists t_batch;
create table t_batch(
id bigint primary key,
name varchar(10)
);
public class BatchTest {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DbUtils.getConnection();
String insertSQL = "insert into t_batch values(?, ?)";
ps = conn.prepareStatement(insertSQL);
int count = 0;
// 循环插入1万条记录
for (int i = 0; i < 10000; i++) {
ps.setInt(1, i);
ps.setString(2, "牛马" + i);
// 将SQL语句加入批处理,打包
ps.addBatch();
// 每1000条SQL语句执行一次I/O
if (i % 1000 == 0) {
count += ps.executeBatch().length;
}
}
// 循环结束后再次执行批处理,防止数据丢失
count += ps.executeBatch().length;
System.out.println("插入" + count + "条记录");
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.close(conn, ps, null);
}
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - begin) + "ms");
}
}
可以看到,效率大幅提升,其原因是:减少了磁盘 I/O 开销。