Explicación detallada de JDBC (muchos ejemplos)

Directorio de artículos

JDBC

El concepto de JDBC

1. El concepto de jdbc

JDBC (Java DataBaseConnectivity java database connection) es una API de Java para ejecutar sentencias SQL, que puede proporcionar acceso unificado a varias bases de datos relacionales.Está compuesta por un conjunto de clases e interfaces escritas en lenguaje Java.

2. La esencia de jdbc

De hecho, es un conjunto de especificaciones (interfaces) proporcionadas oficialmente por java. ¡Se utiliza para ayudar a los desarrolladores a darse cuenta rápidamente de la conexión de diferentes bases de datos relacionales!

Inicio rápido con JDBC

Pasos de inicio rápido para jdbc:

1. Importar el paquete jar

2. Registre el controlador

3. Obtenga la conexión a la base de datos

4. Obtener el objeto ejecutor

5. Ejecute la instrucción sql y devuelva el resultado.

6. Procesamiento de resultados

7. Liberar recursos

Código de muestra:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBC_index1 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");//MySQL5以后可直接省略
        //获取数据库连接
        Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/cadastre","root","XXXXXX");
        //获取执行者对象
        Statement stat=con.createStatement();
        //执行sql语句并返回结果
        String sql="select * from 网易云热评";
        ResultSet re=stat.executeQuery(sql);
        //处理结果
        while (re.next()){
    
    
      System.out.println(re.getString("userId")+"\t"+re.getString("nickname")+"\t"+re.getString("content"));
        }
        //释放资源
        con.close();
    }
}

Explicación detallada de la función JDBC

1. Objeto de gestión de unidades DriverManager

(1) Registrar controlador: (el controlador puede omitirse directamente después de mysql5)
1. Registrar un controlador dado: staticvoid registerDriver(Driver driver);

2. Escriba código usando: Class.forName(“com.mysql.jdbc.Driver”);

3. Hay un bloque de código estático en la clase com.mysql.jdbc.Driver

(2) Obtenga la conexión a la base de datos:
1. Obtenga el objeto de conexión a la base de datos: static ConnectiongetConnection(Stringurl, String user, String password);

2. Valor devuelto: objeto de conexión de base de datos de conexión

3.
URL del parámetro: especifique la ruta de conexión. Sintaxis: jdbc:mysql://dirección IP (nombre de dominio): número de puerto/nombre de la base de datos

usuario: nombre de usuario

contraseña: contraseña

2. Objeto de conexión de base de datos de conexión

1. Obtenga el objeto ejecutor:
Obtenga el objeto ejecutor común: Sentencia createStatement0
Obtenga el objeto ejecutor precompilado: PreparedStatement prepareStatement(String sql);

2. Administrar transacciones
Iniciar una transacción: setAutoCommit(boolean autoCommit); si el parámetro es falso, iniciar una transacción

Confirmar la transacción: commit();

Transacción de reversión: reversión ();

3. Liberar recursos
Liberar inmediatamente el objeto de conexión a la base de datos: void close();

3. La declaración ejecuta el objeto de la declaración sql

(1) Ejecutar sentencia DML: int executeUpdate(String sql);

Valor devuelto int: Devuelve el número de filas afectadas.

Parámetro sql: se pueden ejecutar declaraciones de inserción, actualización y eliminación.

(2) Ejecute la instrucción DQL: ResultSet executeQuery(String sql);

Valor devuelto ResultSet: encapsula el resultado de la consulta.

Parámetro sql: se puede ejecutar la instrucción select.

(3) Liberar recursos
Liberar inmediatamente el objeto de conexión a la base de datos: void close();

4. Objeto de conjunto de resultados ResultSet

1. Determine si todavía hay datos en el conjunto de resultados: boolean next();

tiene datos devuelve verdadero y mueve el índice una fila hacia abajo. Ningún dato devuelve falso.

2. Obtenga los datos en el conjunto de resultados: XXX getXxx("nombre de la columna"); XXX representa el tipo de datos (para obtener una determinada columna de datos, el tipo de datos de esta columna).

Ejemplo: String getString(“nombre”);int getInt("edad");

3. Liberar recursos

Libere inmediatamente el objeto del conjunto de resultados: void close();

caso JDBC

requisitos del caso

Use JDBC para completar la operación CRUD en la tabla de estudiantes

preparación de datos

1. Preparación de datos de la base de datos

-- 创建数据库
create DATABASE db14;

-- 使用数据库
use db14

-- 创建student表
CREATE TABLE student
(
   id int PRIMARY KEY,
	 sname VARCHAR(20),
   age int,
	 brithday date
);

-- 添加数据
INSERT into student VALUES(1,'张飞',23,'1999-08-11'),(2,'李四',23,'1998-08-11'),(3,'王五',23,'1997-08-11'),(4,'关羽',23,'1995-08-11');

2. Crea una clase de estudiante

package Com.Stuclass;

import java.util.Date;

public class Student {
    
    
    private Integer id;
    private String sname;
    private Integer age;
    private Date birthday;

    public Student(Integer id, String sname, Integer age, Date birthday) {
    
    
        this.id = id;
        this.sname = sname;
        this.age = age;
        this.birthday = birthday;
    }

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getSname() {
    
    
        return sname;
    }

    public void setSname(String sname) {
    
    
        this.sname = sname;
    }

    public Integer getAge() {
    
    
        return age;
    }

    public void setAge(Integer age) {
    
    
        this.age = age;
    }

    public Date getBirthday() {
    
    
        return birthday;
    }

    public void setBirthday(Date birthday) {
    
    
        this.birthday = birthday;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "id=" + id +
                ", sname='" + sname + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
}

realización de funciones

1. Consultar toda la información de los estudiantes

public ArrayList<Student> findAll() {
    
    
        ArrayList<Student> list=new ArrayList<>();
        Connection con=null;
        try {
    
    
            con= DriverManager.getConnection(conName,name,password);
            Statement statement = con.createStatement();
            String sql="select * from student";
            ResultSet resultSet = statement.executeQuery(sql);
            while (resultSet.next()){
    
    
                Integer id=resultSet.getInt("id");
                String sname=resultSet.getString("sname");
                Integer age=resultSet.getInt("age");
                Date birthday=resultSet.getDate("brithday");
                Student s=new Student(id,sname,age,birthday);
                list.add(s);
            }
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        return list;
    }

2. Consulta la información del estudiante a través de la identificación

public Student findById(Integer id) {
    
    
        Connection con=null;
        Student s = null;
        try{
    
    
            con=DriverManager.getConnection(conName,name,password);
            String sql="select * from student where id=?";
            PreparedStatement pstate = con.prepareStatement(sql);
            pstate.setInt(1,id);
            ResultSet resultSet = pstate.executeQuery();
            while (resultSet.next()){
    
    
                s=new Student(id,resultSet.getString("sname"),resultSet.getInt("age"),resultSet.getDate("brithday"));
            }
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        return s;
    }

3. Agregar registros de estudiantes

public int insert(Integer id, String sname, Integer age, String  birthday) {
    
    
        Connection con=null;
        int re=0;
        Date date=new Date();//需要new一个Date对象
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); //设置日期格式  yyyy-MM-dd-HH-mm-ss这个是完整的
        try {
    
    
            date = dateFormat.parse(birthday);
        } catch (ParseException e) {
    
    
            e.printStackTrace();
        }
        try {
    
    
            con= JDBCUtils.getConnect();
            String sql="insert into student values(?,?,?,?)";
            PreparedStatement p=con.prepareStatement(sql);
            p.setInt(1,id);
            p.setString(2,sname);
            p.setInt(3,age);
            p.setDate(4, new java.sql.Date(date.getTime()));
            re = p.executeUpdate();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        return re;
    }

4. Modificar la información del estudiante

 public int update(String name1,Integer age) {
    
    
        Connection con=null;
        int result=0;
        String sql="update student " +
                "set age=? " +
                "where sname=?";
        try {
    
    
            con= JDBCUtils.getConnect();
            PreparedStatement p = con.prepareStatement(sql);
            p.setInt(1,age);
            p.setString(2,name1);
            result = p.executeUpdate();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        return result;
    }

5. Eliminar registros de estudiantes

public int delete(Integer id) {
    
    
        Connection con=null;
        int result=0;
        try {
    
    
            con= JDBCUtils.getConnect();
            String sql="delete from student where id=?";
            PreparedStatement p = con.prepareStatement(sql);
            p.setInt(1,id);
            result = p.executeUpdate();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        return result;
    }

Se recomienda utilizar herramientas

Información de configuración: (cree un archivo config.properties en el directorio src)

driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/db14
username=root
password=XXXXXXx

Herramientas:

package Com.Stuclass.utils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/*
* JDBC工具类
* */
public class JDBCUtils {
    
    
//    1、构造方法私有
    private JDBCUtils(){
    
    }
//    2、声明所需要的配置变量
    private static String driverClass;
    private static String url;
    private static String username;
    private static String password;
    private static Connection con;

//    3、提供静态代码块,读取配置文件信息为变量赋值,注册驱动
     static {
    
    
        try {
    
    
//            赋值
            InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("config.properties");
            Properties pro=new Properties();
            pro.load(is);
            driverClass=pro.getProperty("driverClass");
            url=pro.getProperty("url");
            username=pro.getProperty("username");
            password=pro.getProperty("password");
//            注册驱动
            Class.forName(driverClass);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        }
}

//        获取数据库连接
    public static Connection getConnect(){
    
    
        try {
    
    
            con= DriverManager.getConnection(url,username,password);
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }
        return con;
    }
//    关闭连接
    public static void close(Connection con, Statement state, ResultSet rs){
    
    
        if (con!=null){
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        if (state!=null){
    
    
            try {
    
    
                state.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        if (rs!=null){
    
    
            try {
    
    
                rs.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
    }
    public static void close(Connection con, Statement state){
    
    
        if (con!=null){
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        if (state!=null){
    
    
            try {
    
    
                state.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Ataque de inyección SQL

Ataque al sistema explotando las vulnerabilidades de las sentencias SQL

Por ejemplo:

De acuerdo con los principios normales, todo el contenido que ingresamos en la contraseña debe considerarse como la composición de la contraseña.

Pero ahora el objeto Statement ejecuta parte de la contraseña como condición de consulta al ejecutar la sentencia SQL

Solución:

Usar sentencias SQL precompiladas

Gestión de transacciones JDBC

Cómo gestiona JDBC las transacciones

Clase funcional para la gestión de transacciones: Conexión

​ Iniciar una transacción: setAutoCommit(boolean autoCommit); si el parámetro es falso, iniciar una transacción.

Confirmar la transacción: commit();

Transacción de reversión: reversión ();

grupo de conexiones de base de datos

1. Antecedentes de la conexión a la base de datos
La conexión a la base de datos es un recurso crítico, limitado y costoso, que es particularmente importante en las aplicaciones web multiusuario. La administración de las conexiones a la base de datos puede afectar significativamente los indicadores de rendimiento de toda la aplicación. , el grupo de conexiones a la base de datos se propone para este problema

2. Grupo de conexiones de bases de datos
El grupo de conexiones de bases de datos es responsable de asignar, administrar y liberar conexiones de bases de datos, lo que permite que una aplicación reutilice una conexión de base de datos existente en lugar de restablecer una. Esta tecnología puede mejorar significativamente el rendimiento de las operaciones de la base de datos.

Grupo de conexiones de base de datos personalizado

1. Descripción general de la interfaz DataSource
interfaz javax.sql.DataSource: fuente de datos (grupo de conexión de base de datos).

La especificación (interfaz) del grupo de conexiones de la base de datos proporcionada oficialmente por Java. Si desea completar la tecnología del grupo de conexiones de la base de datos, debe implementar la interfaz DataSource.

Función principal: obtener el objeto de conexión de la base de datos: Connection getConnection();

2. Grupo de conexiones de base de datos personalizado

Defina una clase que implemente la interfaz DataSource.

Defina un contenedor para almacenar varios objetos de conexión.

Defina un bloque de código estático, obtenga 10 conexiones y guárdelas en el contenedor a través de la clase de herramientas JDBC.

Anule el método getConnection para obtener una conexión del contenedor y devolverla.

Defina el método getSize para obtener el tamaño del contenedor y devolverlo.

Preparar:

La clase de herramienta JDBCutil anterior y los archivos de configuración relacionados

Ejemplo:

package JDBCplus.Demo1;
/*
* 自定义数据库连接池
* */
import JDBCplus.JDBCUtils;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

public class MyDataSource implements DataSource {
    
    
//    1、准备容器  注意必须是线程安全的
    private static List<Connection> pool= Collections.synchronizedList(new ArrayList<>());
//    2、通过工具类获取10个连接对象
    static {
    
    
    for (int i = 0; i < 10; i++) {
    
    
        Connection con= JDBCUtils.getConnect();
        pool.add(con);
    }
}
//    3、重写getConnection方法

    @Override
    public Connection getConnection() throws SQLException {
    
    
        if (pool.size()>0){
    
    
            return pool.remove(0);
        }else {
    
    
            throw new RuntimeException("连接池已空");
        }
    }
//    4、定义getSize方法获取连接池大小
    public int getSize(){
    
    
        return pool.size();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
    
    
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
    
    
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
    
    
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
    
    
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
    
    

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
    
    

    }

    @Override
    public int getLoginTimeout() throws SQLException {
    
    
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    
    
        return null;
    }
}

Prueba de uso:

package JDBCplus.Demo1;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Test {
    
    
    public static void main(String[] args) throws SQLException {
    
    
        MyDataSource myDataSource = new MyDataSource();
        Connection con=myDataSource.getConnection();
        String sql="select * from student";
        Statement statement = con.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);
        while (resultSet.next()){
    
    
            System.out.println(resultSet.getInt("id")+" "+resultSet.getString("sname"));
        }
        resultSet.close();
        statement.close();
        con.close();
    }
}

imagen-20221013150926078

enlace de retorno

Formas de devolver la conexión a la base de datos:

Herencia

Patrón de diseño de decorador

Patrón de diseño del adaptador

Proxy dinámico

Conexión de retorno: método de herencia

1. El método de herencia devuelve la idea de conexión de base de datos.

Al imprimir el objeto de conexión, se encuentra que la clase de implementación de conexión obtenida por DriverManager es JDBC4Connection, luego podemos personalizar una clase, heredar la clase JDBC4Connection y reescribir el método close() para completar la devolución del objeto de conexión.

2. El método de herencia devuelve los pasos de implementación de la conexión a la base de datos.

1 Defina una clase que herede JDBC4Connection.

2 Defina las variables miembro del objeto de conexión Connection y el objeto contenedor del conjunto de conexiones.

3 Complete la asignación de variables miembro a través del método de construcción parametrizado.

4 Vuelva a escribir el método de cierre y agregue el objeto de conexión al grupo.

3. El modo de herencia devuelve el problema de la conexión a la base de datos.

Al observar el método para obtener la conexión de la clase de herramientas JDBC, se encuentra que, aunque hemos personalizado una subclase, hemos completado la operación de devolver la conexión. Pero lo que adquiere DriverManager es el objeto JDBC4Connection, no nuestro objeto de subclase, y no podemos modificar las funciones de las clases en el paquete de controladores como un todo, ¡así que el método de herencia no funcionará!

(La clase JDBC4Connection no se encuentra aquí en mi paquete MySQL y no se puede mostrar)

Conexiones de retorno - Patrón de diseño de decorador

1. El patrón de diseño de decoración devuelve la idea de conexión de base de datos.

Podemos personalizar una clase para implementar la interfaz de conexión.

De esta forma, tiene el mismo comportamiento que JDBC4Connection y reescribe el método close() para completar el retorno de la conexión. Otras funciones también pueden llamar a los métodos originales de la clase de implementación del paquete de controladores mysql.

2. El patrón de diseño de decoración devuelve los pasos de implementación de la conexión de la base de datos.

1. Defina una clase que implemente la interfaz Connection

2. Defina las variables miembro del objeto de conexión de conexión y el objeto contenedor del grupo de conexiones

3. Completar la asignación de variables miembro a través del método de construcción parametrizado

4. Vuelva a escribir el método close() y agregue el objeto de conexión al grupo

5. Para los métodos restantes, solo necesita llamar al objeto de conexión del paquete del controlador mysql para completar

6. En el grupo de conexiones personalizado, envuelva el objeto de conexión obtenido con un objeto de conexión personalizado

3. El modo de diseño de decoración devuelve los problemas existentes en la conexión de la base de datos.

Después de implementar la interfaz de conexión, hay una gran cantidad de métodos que deben reescribirse en la clase personalizada

Código de muestra:

package JDBCplus.Demo2;

import java.sql.*;
import java.util.*;
import java.util.concurrent.Executor;

public class MyConnection2 implements Connection {
    
    
//    定义连接对象和连接池对象
    private Connection con;
    private List<Connection> pool;

//    构造方法赋值
    public MyConnection2(Connection con,List<Connection> pool){
    
    
        this.con=con;
        this.pool=pool;
    }
//     重写close方法
    @Override
    public void close() throws SQLException {
    
    
        pool.add(con);
    }

    @Override
    public Statement createStatement() throws SQLException {
    
    
        return con.createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
    
    
        return con.prepareStatement(sql);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
    
    
        return con.prepareCall(sql);
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
    
    
        return con.nativeSQL(sql);
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
    
    
        con.setAutoCommit(false);
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
    
    
        return con.getAutoCommit();
    }

    @Override
    public void commit() throws SQLException {
    
    
        con.commit();
    }

    @Override
    public void rollback() throws SQLException {
    
    
        con.rollback();
    }



    @Override
    public boolean isClosed() throws SQLException {
    
    
        return con.isClosed();
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
    
    
        return con.getMetaData();
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
    
    
         con.setReadOnly(false);
    }

    @Override
    public boolean isReadOnly() throws SQLException {
    
    
        return con.isReadOnly();
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
    
    
        con.setCatalog(catalog);
    }

    @Override
    public String getCatalog() throws SQLException {
    
    
        return con.getCatalog();
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
    
    
        con.setTransactionIsolation(level);
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
    
    
        return con.getTransactionIsolation();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
    
    
        return con.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
    
    
        con.clearWarnings();
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
    
    
        return con.createStatement(resultSetType,resultSetConcurrency);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    
    
        return con.prepareStatement(sql,resultSetType,resultSetConcurrency);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    
    
        return con.prepareCall(sql,resultSetType,resultSetConcurrency);
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
    
    
        return con.getTypeMap();
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
    
    
        con.setTypeMap(map);
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
    
    
        con.setHoldability(holdability);
    }

    @Override
    public int getHoldability() throws SQLException {
    
    
        return con.getHoldability();
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
    
    
        return con.setSavepoint();
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
    
    
        return con.setSavepoint(name);
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
    
    
        con.rollback();
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
    
    
        con.releaseSavepoint(savepoint);
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    
    
        return con.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    
    
        return con.prepareStatement(sql,resultSetType,resultSetConcurrency);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    
    
        return con.prepareCall(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
    
    
        return con.prepareStatement(sql,autoGeneratedKeys);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
    
    
        return con.prepareStatement(sql,columnIndexes);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
    
    
        return con.prepareStatement(sql,columnNames);
    }

    @Override
    public Clob createClob() throws SQLException {
    
    
        return con.createClob();
    }

    @Override
    public Blob createBlob() throws SQLException {
    
    
        return con.createBlob();
    }

    @Override
    public NClob createNClob() throws SQLException {
    
    
        return con.createNClob();
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
    
    
        return con.createSQLXML();
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
    
    
        return con.isValid(timeout);
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
    
    
        con.setClientInfo(name,value);
    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
    
    
        con.setClientInfo(properties);
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
    
    
        return con.getClientInfo(name);
    }

    @Override
    public Properties getClientInfo() throws SQLException {
    
    
        return con.getClientInfo();
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
    
    
        return con.createArrayOf(typeName,elements);
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
    
    
        return con.createStruct(typeName,attributes);
    }

    @Override
    public void setSchema(String schema) throws SQLException {
    
    
        con.setSchema(schema);
    }

    @Override
    public String getSchema() throws SQLException {
    
    
        return con.getSchema();
    }

    @Override
    public void abort(Executor executor) throws SQLException {
    
    
        con.abort(executor);
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
    
    
        con.setNetworkTimeout(executor,milliseconds);
    }

    @Override
    public int getNetworkTimeout() throws SQLException {
    
    
        return con.getNetworkTimeout();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
    
    
        return con.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
    
    
        return con.isWrapperFor(iface);
    }
}

Cambiar MyDataSouce (método de grupo de conexión de base de datos):

//    3、重写getConnection方法

    @Override
    public Connection getConnection() throws SQLException {
    
    
        if (pool.size()>0){
    
    
            Connection con = pool.remove(0);
            MyConnection2 myCon=new MyConnection2(con,pool);
            return myCon;
        }else {
    
    
            throw new RuntimeException("连接池已空");
        }
    }

Código de prueba:

package JDBCplus.Demo1;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Test {
    
    
    public static void main(String[] args) throws SQLException {
    
    
        MyDataSource myDataSource = new MyDataSource();
        System.out.println(myDataSource.getSize());
        Connection con=myDataSource.getConnection();
        System.out.println(myDataSource.getSize());
        System.out.println(con.getClass());
        String sql="select * from student";
        Statement statement = con.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);
        while (resultSet.next()){
    
    
            System.out.println(resultSet.getInt("id")+" "+resultSet.getString("sname"));
        }
        resultSet.close();
        statement.close();
        con.close();
        System.out.println(myDataSource.getSize());
    }
}
imagen-20221013164305855

Se encuentra que la clase principal es una clase personalizada y el tamaño del grupo de conexiones es 10 después del uso

Conexión de retorno - Patrón de diseño del adaptador

1. El patrón de diseño del adaptador devuelve la idea de conexión de base de datos.

Podemos proporcionar una clase de adaptador que implemente la interfaz de conexión e implemente todos los métodos (excepto el método de cierre

La clase de conexión personalizada solo necesita heredar esta clase de adaptador y reescribir el método de cierre que debe mejorarse

2. El patrón de diseño del adaptador devuelve los pasos de implementación de la conexión de la base de datos.

1 Defina una clase de adaptador que implemente la interfaz Connection.

2 Defina las variables miembro del objeto de conexión Connection.

3 Complete la asignación de variables miembro a través del método de construcción parametrizado.

4 Vuelva a escribir todos los métodos (excepto cerrar) y llame al objeto de conexión del paquete del controlador mysq para completar.

5 Defina una clase de conexión y herede la clase de adaptador.

6 Defina las variables miembro del objeto Conexión de conexión y el objeto contenedor de grupo de conexiones, y asigne valores a través de la construcción parametrizada.

7 Vuelva a escribir el método close() para devolver la conexión.

8 En el grupo de conexiones personalizado, envuelva el objeto de conexión adquirido con un objeto de conexión personalizado.

3. El patrón de diseño del adaptador devuelve los problemas existentes en la conexión de la base de datos.

Aunque la clase de conexión personalizada es muy concisa, la clase de adaptador todavía la escribimos nosotros mismos, lo que es más problemático

Código de muestra:

Clase de adaptador: (anula todos los métodos excepto cerrar, clase abstracta)

package JDBCplus.Dome3;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/*
* 适配器类
* */
public abstract class MyAdapter implements Connection{
    
    
    private Connection con;
    public MyAdapter(Connection con){
    
    
        this.con=con;
    }
    @Override
    public Statement createStatement() throws SQLException {
    
    
        return con.createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
    
    
        return con.prepareStatement(sql);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
    
    
        return con.prepareCall(sql);
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
    
    
        return con.nativeSQL(sql);
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
    
    
        con.setAutoCommit(false);
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
    
    
        return con.getAutoCommit();
    }

    @Override
    public void commit() throws SQLException {
    
    
        con.commit();
    }

    @Override
    public void rollback() throws SQLException {
    
    
        con.rollback();
    }



    @Override
    public boolean isClosed() throws SQLException {
    
    
        return con.isClosed();
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
    
    
        return con.getMetaData();
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
    
    
        con.setReadOnly(false);
    }

    @Override
    public boolean isReadOnly() throws SQLException {
    
    
        return con.isReadOnly();
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
    
    
        con.setCatalog(catalog);
    }

    @Override
    public String getCatalog() throws SQLException {
    
    
        return con.getCatalog();
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
    
    
        con.setTransactionIsolation(level);
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
    
    
        return con.getTransactionIsolation();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
    
    
        return con.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
    
    
        con.clearWarnings();
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
    
    
        return con.createStatement(resultSetType,resultSetConcurrency);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    
    
        return con.prepareStatement(sql,resultSetType,resultSetConcurrency);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    
    
        return con.prepareCall(sql,resultSetType,resultSetConcurrency);
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
    
    
        return con.getTypeMap();
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
    
    
        con.setTypeMap(map);
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
    
    
        con.setHoldability(holdability);
    }

    @Override
    public int getHoldability() throws SQLException {
    
    
        return con.getHoldability();
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
    
    
        return con.setSavepoint();
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
    
    
        return con.setSavepoint(name);
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
    
    
        con.rollback();
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
    
    
        con.releaseSavepoint(savepoint);
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    
    
        return con.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    
    
        return con.prepareStatement(sql,resultSetType,resultSetConcurrency);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    
    
        return con.prepareCall(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
    
    
        return con.prepareStatement(sql,autoGeneratedKeys);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
    
    
        return con.prepareStatement(sql,columnIndexes);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
    
    
        return con.prepareStatement(sql,columnNames);
    }

    @Override
    public Clob createClob() throws SQLException {
    
    
        return con.createClob();
    }

    @Override
    public Blob createBlob() throws SQLException {
    
    
        return con.createBlob();
    }

    @Override
    public NClob createNClob() throws SQLException {
    
    
        return con.createNClob();
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
    
    
        return con.createSQLXML();
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
    
    
        return con.isValid(timeout);
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
    
    
        con.setClientInfo(name,value);
    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
    
    
        con.setClientInfo(properties);
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
    
    
        return con.getClientInfo(name);
    }

    @Override
    public Properties getClientInfo() throws SQLException {
    
    
        return con.getClientInfo();
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
    
    
        return con.createArrayOf(typeName,elements);
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
    
    
        return con.createStruct(typeName,attributes);
    }

    @Override
    public void setSchema(String schema) throws SQLException {
    
    
        con.setSchema(schema);
    }

    @Override
    public String getSchema() throws SQLException {
    
    
        return con.getSchema();
    }

    @Override
    public void abort(Executor executor) throws SQLException {
    
    
        con.abort(executor);
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
    
    
        con.setNetworkTimeout(executor,milliseconds);
    }

    @Override
    public int getNetworkTimeout() throws SQLException {
    
    
        return con.getNetworkTimeout();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
    
    
        return con.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
    
    
        return con.isWrapperFor(iface);
    }
}

Clase de conexión personalizada:

package JDBCplus.Dome3;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/*
* 自定义连接类
* */
public class MyConnection3 extends MyAdapter {
    
    
    //    定义连接对象和连接池对象
    private Connection con;
    private List<Connection> pool;

    //    构造方法赋值
    public MyConnection3(Connection con,List<Connection> pool) {
    
    
        super(con);
        this.con=con;
        this.pool=pool;
    }

    @Override
    public void close() throws SQLException {
    
    
        pool.add(con);
    }
}

Cambios en el grupo de conexiones de la base de datos:

//    3、重写getConnection方法

    @Override
    public Connection getConnection() throws SQLException {
    
    
        if (pool.size()>0){
    
    
            Connection con = pool.remove(0);
            MyConnection3 myCon=new MyConnection3(con,pool);
            return myCon;
        }else {
    
    
            throw new RuntimeException("连接池已空");
        }
    }

Código de prueba:

(igual que el patrón de diseño del decorador)

imagen-20221013170409082

objetivo alcanzado

proxy dinámico

Proxy dinámico:

Mejore un método sin cambiar el método del objeto de destino

composición:

Objeto proxy: objeto real

Objeto proxy: un objeto en la memoria

Requerir:

El objeto proxy debe implementar la misma interfaz que el objeto proxy

Ejemplo:

clase de estudiante:

package proxy;

public class Student implements StudentInterface{
    
    
    @Override
    public void eat(String name){
    
    
        System.out.println("学生吃"+name);
    }
    @Override
    public void study(){
    
    
        System.out.println("在家自学");
    }
}

Interfaz de interfaz de estudiante:

package proxy;

public interface StudentInterface {
    public void eat(String name);
    public void study();
}

prueba:

package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Student student = new Student();
        student.study();
        student.eat("米饭");
        /*
        * 要求不改动Student的代码,更改study方法的输出内容
        * */
        //参数一:类加载器,参数二:接口类型class数组,参数三:代理规则
        StudentInterface proxy= (StudentInterface) Proxy.newProxyInstance(student.getClass().getClassLoader(), new Class[]{
    
    StudentInterface.class}, new InvocationHandler() {
    
    
            /*
            * 执行student类中所有的方法都会经过invoke方法
            * 对method方法判断,修改想要的方法
            * */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                if(method.getName().equals("study")){
    
    
                    System.out.println("不去学习");
                    return null;
                }else {
    
    
                    return method.invoke(student,args);
                }
            }
        });
        proxy.eat("米饭");
        proxy.study();
    }
}

resultado de la operación:

imagen-20221013173931028

modificado con éxito

El proxy dinámico devuelve la conexión de la base de datos:

1. La idea de devolver la conexión a la base de datos en el modo proxy dinámico.

Podemos usar Proxy para completar el proxy de los objetos de clase de implementación de Connection

En el proceso de proxy, se considera que si se ejecuta el método de cierre, la conexión se devolverá al grupo.

Si es otro método, simplemente llame a la función original del objeto de conexión

2. Los pasos de implementación para devolver la conexión a la base de datos en el modo proxy dinámico.

Defina una clase que implemente la interfaz DataSource

Defina un contenedor para guardar múltiples objetos de conexión Connection

Defina un bloque de código estático, obtenga 10 conexiones y guárdelas en el contenedor a través de la clase de herramientas JDBC

Anule el método getConnection para obtener una conexión desde el contenedor

A través del proxy Proxy, si es un método cerrado, la conexión se devolverá al grupo. Si es otro método, llame a la función original

Defina el método getSize para obtener el tamaño del contenedor y devolver

Darse cuenta de reescribir el método getConnection en MyDataSource (implementado usando proxy dinámico)

//   3、重写getConnection方法   使用动态代理方式实现
    @Override
    public Connection getConnection() throws SQLException {
    
    
        if (pool.size()>0){
    
    
            Connection con = pool.remove(0);
            Connection myCon = (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[]{
    
    Connection.class}, new InvocationHandler() {
    
    
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                    if (method.getName().equals("close")){
    
    
                        pool.add(con);
                        return null;
                    }else {
    
    
                        return method.invoke(con,args);
                    }
                }
            });
            return myCon;
        }else {
    
    
            throw new RuntimeException("连接池已空");
        }
    }

Utilice el código de prueba anterior:

imagen-20221014160000515

Grupo de conexiones de base de datos de código abierto

Grupo de conexiones de base de datos C3P0

1. Los pasos para usar el grupo de conexiones de la base de datos C3PO.

Importe el paquete jar.

Importe el archivo de configuración al directorio src.

Cree un objeto de grupo de conexiones C3PO.

Obtenga una conexión de base de datos para usar.

Nota: El archivo de configuración de C3PO se cargará automáticamente, pero debe llamarse c3p0-config.xml o c3pO-config.properties

Descargue el paquete jar:

Sitio web oficial: https://sourceforge.net/projects/c3p0/files/latest/download

Otro disco de red de Baidu: Enlace de descarga del disco de red de Baidu: https://pan.baidu.com/s/1o9cBkMVb_kZmAksZjjoZYg Contraseña: c7pr

Importe el paquete jar:

imagen-20221014163706222

Importe el archivo de configuración al directorio src:

imagen-20221014163828508

Ejemplo de uso:

package C3P0;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Test1 {
    
    
    public static void main(String[] args) throws SQLException {
    
    
//        1、创建c3p0数据库连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
//        2、通过连接池获取数据库连接
        Connection con = dataSource.getConnection();
//        3、执行数据库操作
        String sql="select * from student";
        PreparedStatement p = con.prepareStatement(sql);
        ResultSet re = p.executeQuery();
        while (re.next()){
    
    
            System.out.println(re.getInt("id")+"\t"+re.getString("sname"));
        }

    }
}
imagen-20221014163917795

Grupo de conexiones de la base de datos de Druid

1. Introducción

Druid (Druid) es un grupo de conexión de base de datos desarrollado por Alibaba conocido como monitoreo. Druid es actualmente el mejor grupo de conexión de base de datos . En términos de función, rendimiento y escalabilidad, supera a otros grupos de conexiones de bases de datos. Al mismo tiempo, se agrega el monitoreo de registros, que puede monitorear bien la ejecución de las conexiones de grupos de bases de datos y SQL. Druid ha implementado más de 600 aplicaciones en Alibaba y ha pasado la prueba rigurosa de implementación a gran escala en el entorno de producción durante más de un año.

1. Pasos para usar el grupo de conexiones de la base de datos de Druid:

Importe el paquete jar.

Escriba un archivo de configuración y colóquelo en el directorio src.

Cargue los archivos de configuración a través de la colección de propiedades.

Obtenga el objeto del grupo de conexiones de la base de datos a través de la clase de fábrica del grupo de conexiones de Druid.

Obtenga una conexión de base de datos para usar.

Nota: Druid no cargará automáticamente el archivo de configuración, debemos cargarlo manualmente, pero el nombre del archivo se puede personalizar.

Descargue el paquete jar:

Disco de red Baidu de otras personas:: https://pan.baidu.com/s/1U9v5c_DindqXva92JeRVJQ Contraseña: nquq

Importe el paquete jar:

imagen-20221014171619816

Escribir archivo de configuración:

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/db14
username=root
password=xxxxxx
initialSize=5
maxActive=10
maxWait=3000

Código de muestra:

package Druid;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

public class test1 {
    
    
    public static void main(String[] args) throws Exception {
    
    
//        获取配置文件的流对象
        InputStream is = test1.class.getClassLoader().getResourceAsStream("druid.properties");
//        通过properties集合,加载配置文件
        Properties properties = new Properties();
        properties.load(is);
//        通过Druid连接池工厂类获取连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//        连接池中获取连接对象
        Connection con = dataSource.getConnection();
//        执行数据库操作
        String sql="select * from student";
        PreparedStatement p = con.prepareStatement(sql);
        ResultSet re = p.executeQuery();
        while (re.next()){
    
    
            System.out.println(re.getInt("id")+"\t"+re.getString("sname"));
        }
    }
}
imagen-20221014174345716

Clase de herramienta creada en base al grupo de conexiones de la base de datos de Druid

código:

package Druid;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DruidUtils {
    
    
//    私有化构造方法
    private DruidUtils(){
    
    }
//    声明数据源变量
    private static DataSource dataSource;
//    静态代码块完成配置文件加载和获取数据库连接对象
    static {
    
    
    InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
    Properties properties = new Properties();
    try {
    
    
        properties.load(is);
        dataSource = DruidDataSourceFactory.createDataSource(properties);
    }catch (Exception e) {
    
    
        e.printStackTrace();
    }
}
//   获取数据库连接
    public static Connection getConnection(){
    
    
        Connection con=null;
        try {
    
    
            con = dataSource.getConnection();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }
        return con;
    }
//   获取数据库连接池
    public static DataSource getDataSource(){
    
    
        return dataSource;
    }

    //    关闭连接
    public static void close(Connection con, Statement state, ResultSet rs){
    
    
        if (con!=null){
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        if (state!=null){
    
    
            try {
    
    
                state.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        if (rs!=null){
    
    
            try {
    
    
                rs.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
    }
    public static void close(Connection con, Statement state){
    
    
        if (con!=null){
    
    
            try {
    
    
                con.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        if (state!=null){
    
    
            try {
    
    
                state.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Ejemplo de uso:

package Druid;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class test2 {
    
    
    public static void main(String[] args) throws SQLException {
    
    
        Connection con=DruidUtils.getConnection();
        String sql="select * from student";
        PreparedStatement p = con.prepareStatement(sql);
        ResultSet re = p.executeQuery();
        while (re.next()){
    
    
            System.out.println(re.getInt("id")+"\t"+re.getString("sname"));
        }
    }
}
imagen-20221014180438643

Supongo que te gusta

Origin blog.csdn.net/qq_54353206/article/details/127325477
Recomendado
Clasificación