JDBC
- 概述
- 步骤
- 项目创建
- 流程
- 代码改进
- 使用Statement的问题:SQL注入
- (1)SQL注入
- (2)PreparedStatement
- 1、防止SQL注入
- 2、批处理
- 事务
- 连接池
- 建立数据库连接
- 实现
- 日志
概述
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。我们通常说的JDBC是面向关系型数据库的。
JDBC
步骤
- 加载驱动Driver
- 创建数据库连接Connection
- 创建SQL命令发送器Statement
- 发送SQL命令,获取结果
- 处理结果
- 关闭资源(结果集ResultSet、Statement、Connection)
项目创建
流程
-
导入jar包【数据库厂商实现JDBC相关接口的驱动jar包】
MySQL_jar
-
准备信息
String url="jdbc:mysql://127.0.0.1:3306/${database_name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
String user="${user}";
String password="${password}";
String sql="${sql}";
- Driver(加载、注册) -> Connection【使用准备信息】-> 获取Statement -> 执行SQL ->处理结果集 -> 释放资源
Driver driver = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(driver);
Connection connection = DriverManager.getConnection(url, user,password );
Statement statement = connection.createStatement();
// 返回
// int:影响行数(增、删、改)
// ResultSet:查询结果集
statement.executeUpdate(sql);
xxx.close();// 后获取的先关闭
代码改进
(1)使用注解加载Driver
private static String driver ="com.mysql.cj.jdbc.Driver";
Class.forName(driver);
(2)使用try-with-resources
释放资源
try (Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement()) {
Class.forName(driver);
statement.execute(sql));
} catch (Exception e) {
e.printStackTrace();
}
(3)省略Class.forName(driver);
在调用 getConnection 方法时,DriverManager 会试着从初始化时加载的那些驱动程序以及使用与当前 applet 或应用程序相同的类加载器显式加载的那些驱动程序中查找合适的驱动程序。
(4)配置文件:参数优化
jdbc.properties
## key=value
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mytestdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
user=root
password=123456
initSize=1
maxSize=1
PropertiesUtil
工具类:
public class PropertiesUtil {
private Properties properties;
public PropertiesUtil(String path){
properties=new Properties();
InputStream inputStream = this.getClass().getResourceAsStream(path);
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public String getProperties(String key){
return properties.getProperty(key);
}
}
JDBC修改:
PropertiesUtil propertiesUtil=new PropertiesUtil("/jdbc.properties");
driver=propertiesUtil.getProperties("driver");
url=propertiesUtil.getProperties("url");
user=propertiesUtil.getProperties("user");
password=propertiesUtil.getProperties("password");
initSize=Integer.parseInt(propertiesUtil.getProperties("initSize"));
maxSize=Integer.parseInt(propertiesUtil.getProperties("maxSize"));
使用Statement的问题:SQL注入
(1)SQL注入
SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序。
(2)PreparedStatement
1、防止SQL注入
PreparedStatement
使用?
作为参数的占位符。
String sql="select * from account where username = ? and password = ?";
//在获取PreparedStatement对象时已经传入SQL语句
try(Connection connection =DriverManager.getConnection(url, user,password);
PreparedStatement preparedStatement=connection.prepareStatement(sql);){
Class.forName(driver);
/*
* 如果SQL语句中有?作为参数占位符号,那么要在执行CURD之前先设置参数
* 通过set***(问号的编号,数据) 方法设置参数
* */
//设置参数
preparedStatement.setString(1,username );
preparedStatement.setString(2,pwd );
//执行CURD,这里不需要再传入SQL语句
resultSet = preparedStatement.executeQuery();
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭resultSet
if(null != resultSet){
try {
resultSet.close();
} catch (SQLException e){
e.printStackTrace();
}
}
}
2、批处理
- statement语句对象实现批处理有如下问题:
- 缺点:采用硬编码效率低,安全性较差。
- 原理:硬编码,每次执行时相似SQL都会进行编译
- PreparedStatement+批处理:
- 优点:语句只编译一次,减少编译次数。提高了安全性(阻止了SQL注入)
- 原理:相似SQL只编译一次,减少编译次数
- 注意: 需要设置批处理开启
&rewriteBatchedStatements=true
【url】
for (int i = 1; i <= 10663; i++) {
preparedStatement.setString(1, "name");
preparedStatement.setString(2, "loc");
preparedStatement.addBatch();// 将修改放入一个批次中
if(i%1000==0){
preparedStatement.executeBatch();
preparedStatement.clearBatch();// 清除批处理中的数据
}
}
/*
* 整数数组中的元素代表执行的结果代号
* SUCCESS_NO_INFO -2
* EXECUTE_FAILED -3
* */
/*int[] ints = */
preparedStatement.executeBatch();
preparedStatement.clearBatch();
事务
在逻辑上一组不可分割的操作。由多个sql语句组成,多个sql语句要么全都执行成功,要么都不执行。
ACID(原子性 一致性 隔离性 持久性)
如何让多个数据库操作成为一个整体:实现要么全都执行成功,要么全都不执行。
在JDBC中,事务操作是自动提交。
【一条对数据库的DML(insert、update、delete)代表一项事务操作。操作成功后,系统将自动调用commit()提交,否则自动调用rollback()回滚。】
java.sql.Connection
:
- 禁止自动提交:
setAutoCommit(false)
- 提交:
commit()
- 回滚:
rollback()
【异常捕获时调用】 - 设置回滚点:
setSavepoint()
-->savepoints{集合中存储}
–>获取需要的回滚点–>rollback(savepoint)
连接池
建立数据库连接
- 传统连接:
- 调用
Class.forName()
方法加载数据库驱动;调用DriverManager.getConnection()
方法建立连接。 - 问题:
- 每次执行DML / DQL 都要创建一次Connection对象,执行完毕后Connection对象都要被销毁。
- 没有复用,消耗系统资源
- 调用
- 连接池:
- 预先创建多个数据库连接对象,将连接对象保存在连接池中。【请求到来->从池中取出连接对象;请求完毕->客户程序调用close()将对象放回池中】
- 复用->减少资源消耗;多线程请求,缓解压力
实现
(1)定义连接池
public class MyConnectionPool {
private static String driver ="com.mysql.cj.jdbc.Driver";
private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
private static String user="root";
private static String password="root";
private static int initSize=1;
private static int maxSize=1;
private static LinkedList<Connection> pool;
static{
// 加载驱动
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 私有的初始化一个链接对象的方法
private static Connection initConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// 初始化pool
pool=new LinkedList<Connection>();
// 创建5个链接对象
for (int i = 0; i <initSize ; i++) {
Connection connection = initConnection();
if(null != connection){
pool.add(connection);
System.out.println("初始化连接"+connection.hashCode()+"放入连接池");
}
}
}
// 共有的向外界提供链接对象的
public static Connection getConnection(){
Connection connection =null;
if(pool.size()>0){
connection= pool.removeFirst();// 移除集合中的第一个元素
System.out.println("连接池中还有连接:"+connection.hashCode());
}else{
connection = initConnection();
System.out.println("连接池空,创建新连接:"+connection.hashCode());
}
return connection;
}
// 共有的向连接池归还连接对象的方法
public static void returnConnection(Connection connection){
if(null != connection){
try {
if(!connection.isClosed()){
if(pool.size()<maxSize){
try {
connection.setAutoCommit(true);// 调整事务状态
System.out.println("设置连接:"+connection.hashCode()+"自动提交为true");
} catch (SQLException e) {
e.printStackTrace();
}
pool.addLast(connection);
System.out.println("连接池未满,归还连接:"+connection.hashCode());
}else{
try {
connection.close();
System.out.println("连接池满了,关闭连接:"+connection.hashCode());
} catch (SQLException e) {
e.printStackTrace();
}
}
}else{
System.out.println("连接:"+connection.hashCode()+"已经关闭,无需归还");
}
} catch (SQLException e) {
e.printStackTrace();
}
}else{
System.out.println("传入的连接为null,不可归还");
}
}
}
日志
log4j2
官网