JDBC
JDBC es un conjunto de API que utilizan el lenguaje Java para operar bases de datos relacionales. El nombre completo es (Java DataBase Connectivity) conexión de base de datos Java.
Esencia JDBC:
- Un conjunto de reglas definidas por el oficial (compañía sol) para operar todas las bases de datos relacionales, a saber, la interfaz
- Varios proveedores de bases de datos implementan esta interfaz y proporcionan paquetes jar controlados por bases de datos.
- Podemos usar esta programación de interfaz (JDBC), el código real para ejecutar es la clase de implementación en el paquete jar del controlador
Idea agrega paquete de controladores MySQL
1. Primero descargue el paquete mysqljar apropiado del repositorio de Maven
2. Cree una carpeta lib y agregue el paquete jar del controlador MySQL 1
3. Establezca la carpeta lib en la biblioteca
uso de JDBC
使用步骤
- Crea un proyecto e importa el paquete jar
- Registrar el conductor
Class.forName("com.mysql.jdbc.Driver");
- conectarse
Connection conn = DriverManager.getConnection(url,username,password);
- Definir sentencias SQL
String sql ="update...";
- Obtener el objeto SQL de ejecución
Statement stmt =conn.createStatement();
- ejecutar SQL
stmt.executeUpdate(sql);
- Procesar el resultado devuelto
- liberar recursos
\
使用示例
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* JDBC快速入门
*/
public class JDBC_Test {
public static void main(String[] args) throws Exception {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");//固定的类名
//获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";//jdbc:mysql://+ IP + Port /数据库名
String username = "root";
String password = "123";
Connection conn = DriverManager.getConnection(url, username, password);
//定义sql语句
String sql = "update account set money =20300 where id =1";
//获取执行sql的对象 statement
Statement stmt = conn.createStatement();
//执行sql
int count = stmt.executeUpdate(sql);//返回受影响的行数
//受处理结果
System.out.println(count);
//释放资源
stmt.close();
conn.close();
}
}
复制代码
base de datos de prueba
create database db1;
create table account(
`id` int primary key,
`name` varchar(4) not null,
`money` int not null
);
Insert into account values (1,"ning",10000);
复制代码
API común de JDBC
Drivermanager (clase de gestión de controladores)
efecto:
1. Registre el controlador
Class.forName("com.mysql.jdbc.Driver");
复制代码
com.mysql.jdbc.Driver源码
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
复制代码
El paquete de controladores después de MySQL5 puede omitir el paso de registrar el controlador, lo que cargará automáticamente la clase de controlador en el archivo META->INF/services/java.sql.Driver en el paquete jar.
2. Obtener conexión a la base de datos
static Connection getConnection(String url, String user, String password)
复制代码
Parámetros :
Demostración detallada :
String url = "jdbc:mysql:///db1";//省略127.0.0.1:3306
复制代码
Cuando la versión llegue a la 5.7 o superior, MySQL le sugerirá que use una conexión SSL más segura, que requiere una configuración compleja y reduce el rendimiento en un 20 %, por lo que generalmente no usamos esta configuración y establecemos parámetros para desactivar su sugerencia de advertencia.
String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
复制代码
Conexión (objeto de conexión de base de datos)
efecto:
1. Obtener el objeto que ejecuta el SQL
Hay tres métodos: (principalmente use los dos primeros, el tercero rara vez se usa)
- Ejecutar objeto SQL ordinario
Statement createStatement()
复制代码
- Ejecutar objeto SQL para SQL precompilado
PreparedStatement prepareStatement(String sql)
复制代码
- El objeto que ejecuta el procedimiento almacenado.
CallableStatement prepareCall(String sql)
复制代码
2. Administrar asuntos
import java.sql.*;
public class JDBC_Test {
public static void main(String[] args) throws Exception {
//获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";//jdbc:mysql://+ IP + Port /数据库名
String username = "root";
String password = "123";
Connection conn = DriverManager.getConnection(url, username, password);
//定义sql语句
String sql1 = "update account set money =money+500 where id =1";
String sql2 = "update account set money =money-500 where id =2";
Statement stmt = null;
//获取执行sql的对象 statement
stmt = conn.createStatement();
try {
//开启事务
conn.setAutoCommit(false);//将自动提交事务关闭
//执行sql
int count1 = stmt.executeUpdate(sql1);//返回受影响的行数
System.out.println(count1);
//出现意外
// System.out.println(3 / 0);
int count2 = stmt.executeUpdate(sql2);//返回受影响的行数
System.out.println(count2);
//提交事务
conn.commit();
} catch (SQLException throwables) {
//回滚事务
conn.rollback();
throwables.printStackTrace();
}
//释放资源
stmt.close();
conn.close();
}
}
复制代码
declaración de la base de datos
CREATE DATABASE IF NOT EXISTS db1;
USE db1;
CREATE TABLE account ( `id` INT, `name` VARCHAR ( 10 ), `money` INT, PRIMARY KEY ( id ) );
INSERT INTO account VALUES (1,"阿画",20);
INSERT INTO account VALUES (2,"苗苗",5000);
复制代码
Sentencia (ejecutar sentencia SQL)
1. Ejecutar sentencias DML y DDL
int executeUpdate(sql)
复制代码
Valor de retorno: el número de filas afectadas por la instrucción DML (después de ejecutar la instrucción DDL, puede devolver 0 si la ejecución es exitosa)
实际使用:不返回影响的行数,而是返回执行是否成功,对于一些DDL语句(比如定义数据库),不出现异常就算成功。
String sql = "update account set money =20300 where id =1";
Statement stmt = conn.createStatement();
int count = stmt.executeUpdate(sql);
if (count > 0) {
System.out.println("执行成功");
} else {
System.out.println("执行失败");
}
复制代码
2.执行DQL语句
ResultSet executeQuery(sql)
复制代码
返回值: ResultSet 结果集对象
ResultSet(结果集对象)
封装DQL语句的查询结果
其封装了二维表,内部存有游标,游标默认指向数据的上一行(即表头行)
获取查询结果:
boolen next()
复制代码
将光标从当前位置向下移动一行 ,判断当前行是否为有效行(即有数据的行) 返回值:
- true: 有效行,当前行有数据
- false:无效行,当前行没有数据
XXx getXxx(参数):获取数据
复制代码
Xxx
:数据类型;如: int getInt(参数) ; String getString(参数) 参数:
- int:列的编号,从1开始
- String:列的名称
使用步骤
String sql = "select * from account ";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt(1);//ID
String name = rs.getString(2);//name
int money = rs.getInt(3);//money
/*和上面的输出结果一样
int id = rs.getInt(“id”);//ID
String name = rs.getString("name");//name
int money = rs.getInt("money");//money
*/
System.out.println(id);
System.out.println(name);
System.out.println(money);
}
rs.close();
复制代码
使用案例:将ResultSet封装进ArrayList中
定义需要封装的类
class customers {
String CustomerID;
String CompanyName;
String Address;
String City;
public String getCustomerID() {
return CustomerID;
}
public void setCustomerID(String customerID) {
CustomerID = customerID;
}
public String getCompanyName() {
return CompanyName;
}
public void setCompanyName(String companyName) {
CompanyName = companyName;
}
public String getAddress() {
return Address;
}
public void setAddress(String address) {
Address = address;
}
public String getCity() {
return City;
}
public void setCity(String city) {
City = city;
}
@Override
public String toString() {
return "customers{" +
"CustomerID='" + CustomerID + '\'' +
", CompanyName='" + CompanyName + '\'' +
", Address='" + Address + '\'' +
", City='" + City + '\'' +
'}';
}
}
复制代码
将查询到的结果装入集合容器中
Collection<customers> rsl = new ArrayList<>();
String sql = "select * from customers ";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
customers customers = new customers();
customers.setAddress(rs.getString("Address"));
customers.setCity(rs.getString("city"));
customers.setCustomerID(rs.getString("CustomerID"));
customers.setCompanyName(rs.getString("CompanyName"));
rsl.add(customers);
}
rs.close();
rsl.forEach(new Consumer<customers>() {
@Override
public void accept(customers customers) {
System.out.println(customers);
}
});
复制代码
PreparedStatement(预编译SQL语句)
继承自Statement
,作用是预编译SQL语句并执行,预防SQL注入问题。
什么是SQL注入
SQL注入是对用户输入数据的合法性没有判断或过滤不严,通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。
@Test
public void testLogin_Inject() throws Exception {
//获取连接
String url = "jdbc:mysql:///db1?serverTimezone=UTC";//jdbc:mysql://+ IP + Port /数据库名
String username = "root";
String password = "123";
Connection conn = DriverManager.getConnection(url, username, password);
String name = "dadsadadad";
String pwd = "' or '1' = '1";
String sql = "select * from tb_user where username='" + name + "'and password ='" + pwd + "'";
System.out.println(sql);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
System.out.println("login success");
} else {
System.out.println("login fail");
}
//释放资源
stmt.close();
conn.close();
}
复制代码
数据库语句
CREATE TABLE `tb_user` (
`username` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
1.防止SQL注入
1.获取PreparedStatement对象
new Connection().prepareStatement(String sql)
造成SQL注入的原因还是在拼接字符串时被钻了篡改语义的空子,所以PreparedStatement干脆不拼接字符串,直接使用?占位符代替参数值。
//SQL语句中的参数值,这里使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
//通过Connection对象获取获取,并传入对应的SQL语句
PreparedStatement pstmt = conn.prepareStatement(sql);
复制代码
2.设置参数值
PreparedStatement对象 : setXxx(参数1,参数2):给?赋值
使用?占位符代替参数拼接后,我们使用setXxx来设置参数:
Xxx为数据类型,如setInt(参数1,参数2)
参数1为 ?的位置编号(即第几个?),参数2为?的值
3.执行SQL
不需要再传递sql,直接使用PreparedStatement对象调用,
pstmt.executeUpdate();
pstmt.executeQuery();
@Test
public void testLogin_Inject() throws Exception {
//获取连接
String url = "jdbc:mysql:///db1?serverTimezone=UTC";//jdbc:mysql://+ IP + Port /数据库名
String username = "root";
String password = "' or '1' ='1";//这里使用SQL注入也会失败
Connection conn = DriverManager.getConnection(url, username, password);
String name = "zhangsan";
String pwd = "1234";
String sql = "select * from tb_user where username=? and password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
//设置?的值
pstmt.setString(1, name);
pstmt.setString(2, pwd);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
//释放资源
rs.close();
pstmt.close();
conn.close();
}
复制代码
预防机制原理:
在设置?的值时会对其转义,原本的单引号'
会被转义为'
,即原本的SQL注入就变成了'' or '1' ='1'
,无法起到原先的作用。
2.预编译SQL
PreparedStatement能预编译SQL,提高性能。
- 当我们在Java中写完SQL语句后,Java代码会将SQL传给MySQL进行检查SQL语法和编译SQL,这两步的执行时间比执行SQL可能更久。
- 对于Statement对象来说,要执行SQL语句需要每次将其导入Statement对象,如上图,MySQL会分别对两句SQL进行检查和编译;
- 而对于PreparedStatement对象来说,MySQL只会对SQL语句检查、编译一次,如果sql模板一样,则只需要进行一次检查、编译。
-
PreparedStatement预编译功能开启:在url后加
useServerPrepStmts=true
String url = "jdbc:mysql:///db1?serverTimezone=UTC&useServerPrepStmts=true"; 复制代码
-
配置MySQL执行日志(重启mysq|服务后生效)
log-output=FILE general-log=1 general_log_file="D:\mysql.log" #日志的位置 slow-query-log=1 slow_query_log_file="D:\mysql_slow.log" long_query_time=2 复制代码
使用之前的SQL注入测试
数据库连接池
- 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
- 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏
数据库连接池的实现
- 官方(SUN)提供的数据库连接池标准接口DataSource,由第三方组织实现此接口,其功能是获取连接
Connection getConnection()
- 常见的数据库连接池:DBCP、C3PO、Druid
- 其中Druid(德鲁伊)连接池是阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一。
Druid连接池
- 导入jar包 druid-1.1.12.jar
- 定义配置文件
- 加载配置文件
- 获取数据库连接池对象
- 获取连接
//加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("C:\Users\Aaa\Desktop\Agust\src\druid.properties"));
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取数据库连接
Connection conn = dataSource.getConnection();
System.out.println(conn);
复制代码
配置文件
#druid.properties
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=123
#初始化连接数量
initialSize=5
#最大连接数
maxActive=10
#最大等待时间
maxWait=3000
复制代码
Druid配置详解
属性 | 说明 | 建议值 |
---|---|---|
url | 数据库的jdbc连接地址。一般为连接oracle/mysql。示例如下: | |
mysql : jdbc:mysql://ip:port/dbname?option1&option2&… | ||
oracle : jdbc:oracle:thin:@ip:port:oracle_sid | ||
username | 登录数据库的用户名 | |
password | 登录数据库的用户密码 | |
initialSize | 启动程序时,在连接池中初始化多少个连接 | 10-50已足够 |
maxActive | 连接池中最多支持多少个活动会话 | |
maxWait | 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池 | 100 |
没有可用连接,单位毫秒,设置-1时表示无限等待 | ||
minEvictableIdleTimeMillis | 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将 | 见说明部分 |
回收该连接,要小于防火墙超时设置 | ||
net.netfilter.nf_conntrack_tcp_timeout_established的设置 | ||
timeBetweenEvictionRunsMillis | 检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查 | |
keepAlive | 程序没有close连接且空闲时长超过 minEvictableIdleTimeMillis,则会执 | true |
行validationQuery指定的SQL,以保证该程序连接不会池kill掉,其范围不超 | ||
过minIdle指定的连接个数。 | ||
minIdle | 回收空闲连接时,将保证至少有minIdle个连接. | 与initialSize相同 |
removeAbandoned | 要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该 | false,当发现程序有未 |
连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。 | 正常close连接时设置为true | |
removeAbandonedTimeout | 设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此 | 应大于业务运行最长时间 |
值后,druid将强制回收该连接,单位秒。 | ||
registroAbandonado | Cuando el druida recicla a la fuerza la conexión, ya sea para registrar el seguimiento de la pila en el registro | verdadero |
prueba mientras está inactivo | Cuando un programa solicita una conexión, cuando el grupo asigna una conexión, primero verifica si la conexión es válida. (eficiente) | verdadero |
consulta de validación | instrucción SQL para verificar si la conexión en el grupo aún está disponible, drui se conectará a la base de datos para ejecutar el SQL, si | |
Si regresa normalmente, significa que la conexión está disponible, de lo contrario, significa que la conexión no está disponible | ||
testOnBorrow | Cuando el programa solicita una conexión, se realiza una verificación de validez de la conexión (ineficiente, que afecta el rendimiento) | falso |
testOnReturn | Cuando el programa devuelve la conexión, se realiza la verificación de validez de la conexión (ineficiente, que afecta el rendimiento) | falso |
la mitad de declaraciones preparadas | Cache SQL iniciado por los siguientes dos métodos: | verdadero |
declaración preparada pública preparar declaración (String sql) | ||
public PreparedStatement prepareStatement(String sql, | ||
int resultSetType, int resultSetConcurrency) | ||
maxPoolPrepareStatementPerConnectionSize | Cantidad máxima de SQL para almacenar en caché por conexión | 20 |
filtros | Los complementos se configuran aquí. Los complementos comúnmente utilizados son: | estadística, pared, slf4j |
Supervisión de estadísticas: filter:stat | ||
Supervisión de registro: filtro: log4j o slf4j | ||
Defensa contra la inyección de SQL: filter:wall | ||
conectarPropiedades | Propiedades de conexión. Por ejemplo, establecer alguna configuración de estadísticas del grupo de conexiones. | |
druida.stat.mergeSql=verdadero;druida.stat.slowSqlMillis=5000 | ||
Por ejemplo, establezca algunas propiedades de conexión de la base de datos: | ||
Resumen de preguntas frecuentes
El problema ocurre al conectarjava.sql.SQLException: No timezone mapping entry for 'UTC?useSSL=false'
Solución: añádelo al parámetro url?serverTimezone=UTC
String url = "jdbc:mysql:///db1?serverTimezone=UTC";
复制代码