[Java] Resumo JDBC

JDBC

JDBC é um conjunto de APIs que utilizam a linguagem Java para operar bancos de dados relacionais.O nome completo é (Java DataBase Connectivity) Java database connection.

insira a descrição da imagem aqui

Essência JDBC:

  • Um conjunto de regras definidas pelo oficial (empresa sun) para operar todas as bases de dados relacionais, nomeadamente a interface
  • Vários fornecedores de banco de dados implementam essa interface e fornecem pacotes jar baseados em banco de dados
  • Podemos usar essa programação de interface (JDBC), o código real a ser executado é a classe de implementação no pacote jar do driver

Idea adiciona pacote de driver MySQL

1. Primeiro baixe o pacote mysqljar apropriado do repositório Maven

2. Crie uma pasta lib e adicione o pacote jar do driver MySQL 1

insira a descrição da imagem aqui

3. Defina a pasta lib para a biblioteca

insira a descrição da imagem aqui

Uso de JDBC

使用步骤

  1. Crie um projeto e importe o pacote jar
  2. Registre o motoristaClass.forName("com.mysql.jdbc.Driver");
  3. conecte-seConnection conn = DriverManager.getConnection(url,username,password);
  4. Definir instruções SQLString sql ="update...";
  5. Obtenha o objeto SQL de execuçãoStatement stmt =conn.createStatement();
  6. executar SQLstmt.executeUpdate(sql);
  7. Processe o resultado retornado
  8. liberar recursos

\

insira a descrição da imagem aqui

使用示例

 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();
     }
 }
复制代码

banco de dados de teste

 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 comum JDBC

Drivermanager (classe de gerenciamento de driver)

efeito:

1. Registre o motorista

 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!");
         }
     }
 }
复制代码

O pacote de driver após o MySQL5 pode omitir a etapa de registro do driver, que carregará automaticamente a classe do driver no arquivo META->INF/services/java.sql.Driver no pacote jar.

insira a descrição da imagem aqui

2. Obtenha conexão com o banco de dados

 static Connection getConnection(String url, String user, String password)
复制代码

Parâmetros :

insira a descrição da imagem aqui Demonstração detalhada :

 String url = "jdbc:mysql:///db1";//省略127.0.0.1:3306
复制代码

Quando a versão chegar a 5.7 e superior, o MySQL irá sugerir que você use uma conexão SSL mais segura, que requer configuração complexa e reduz o desempenho em 20%, então normalmente não usamos essa configuração e definimos parâmetros para desativar sua dica de avisos.

 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
复制代码

Conexão (objeto de conexão do banco de dados)

efeito:

1. Obtenha o objeto que executa o SQL

Existem três métodos: (usar principalmente os dois primeiros, o terceiro raramente é usado)

  • Objeto SQL de execução comum
 Statement createStatement()
复制代码
  • Executar objeto SQL para SQL pré-compilado
 PreparedStatement prepareStatement(String sql)
复制代码
  • O objeto que executa o procedimento armazenado
 CallableStatement prepareCall(String sql)
复制代码

2. Gerenciar assuntos

insira a descrição da imagem aqui

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();
    }
}
复制代码

declaração de banco de dados

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);
复制代码

Instrução (executar instrução SQL)

1. Execute instruções DML e DDL

int executeUpdate(sql)
复制代码

Valor de retorno: o número de linhas afetadas pela instrução DML (após a execução da instrução DDL, ela pode retornar 0 se a execução for bem-sucedida)

实际使用:不返回影响的行数,而是返回执行是否成功,对于一些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();
 }
复制代码

insira a descrição da imagem aqui

数据库语句

 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,提高性能。

insira a descrição da imagem aqui

  • 当我们在Java中写完SQL语句后,Java代码会将SQL传给MySQL进行检查SQL语法和编译SQL,这两步的执行时间比执行SQL可能更久。
  • 对于Statement对象来说,要执行SQL语句需要每次将其导入Statement对象,如上图,MySQL会分别对两句SQL进行检查和编译;
  • 而对于PreparedStatement对象来说,MySQL只会对SQL语句检查、编译一次,如果sql模板一样,则只需要进行一次检查、编译。
  1. PreparedStatement预编译功能开启:在url后加useServerPrepStmts=true

     String url = "jdbc:mysql:///db1?serverTimezone=UTC&useServerPrepStmts=true";
    复制代码
  2. 配置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注入测试

insira a descrição da imagem aqui

数据库连接池

insira a descrição da imagem aqui

  • 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
  • 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏

数据库连接池的实现

  • 官方(SUN)提供的数据库连接池标准接口DataSource,由第三方组织实现此接口,其功能是获取连接 Connection getConnection()
  • 常见的数据库连接池:DBCP、C3PO、Druid
  • 其中Druid(德鲁伊)连接池是阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一。

Druid连接池

  1. 导入jar包 druid-1.1.12.jar
  2. 定义配置文件
  3. 加载配置文件
  4. 获取数据库连接池对象
  5. 获取连接
 //加载配置文件
 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将强制回收该连接,单位秒。
logAbandonado Quando o druid recicla a conexão à força, se deve gravar o rastreamento de pilha no log verdadeiro
testWhileIdle Quando um programa solicita uma conexão, quando o pool aloca uma conexão, ele primeiro verifica se a conexão é válida. (eficiente) verdadeiro
validaçãoConsulta Instrução SQL para verificar se a conexão no pool ainda está disponível, drui se conectará ao banco de dados para executar o SQL, se
Se retornar normalmente, significa que a conexão está disponível, caso contrário, significa que a conexão está indisponível
testOnBorow Quando o programa solicita uma conexão, é realizada uma verificação de validade da conexão (ineficiente, afetando o desempenho) falso
testOnReturn Quando o programa retorna a conexão, a verificação de validade da conexão é realizada (ineficiente, afetando o desempenho) falso
Declarações semipreparadas Cache SQL iniciado pelos dois métodos a seguir: verdadeiro
public PreparedStatement prepareStatement(String sql)
public PreparedStatement prepareStatement(String sql,
int resultSetType, int resultSetConcurrency)
maxPoolPrepareStatementPerConnectionSize Quantidade máxima de SQL para armazenar em cache por conexão 20
filtros Os plug-ins são configurados aqui. Os plug-ins mais usados ​​são: status, parede, slf4j
Estatísticas de monitoramento: filter:stat
Monitoramento de log: filtro: log4j ou slf4j
Defesa contra injeção de SQL: filter:wall
connectPropriedades Propriedades de conexão. Por exemplo, definir algumas configurações de estatísticas do pool de conexões.
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
Por exemplo, defina algumas propriedades de conexão de banco de dados:

Resumo das perguntas frequentes

Ocorre um problema ao conectarjava.sql.SQLException: No timezone mapping entry for 'UTC?useSSL=false'

Solução: adicione-o ao parâmetro url?serverTimezone=UTC

 String url = "jdbc:mysql:///db1?serverTimezone=UTC";
复制代码

Acho que você gosta

Origin juejin.im/post/7085248046157529125
Recomendado
Clasificación