Capítulo 3: Usando PreparedStatement para implementar operações CRUD
3.1 Operação e acesso ao banco de dados
-
A conexão com o banco de dados é usada para enviar comandos e instruções SQL para o servidor de banco de dados e aceitar os resultados retornados pelo servidor de banco de dados. Na verdade, uma conexão de banco de dados é uma conexão Socket.
-
Existem três interfaces no pacote java.sql que definem diferentes maneiras de chamar o banco de dados:
- Instrução: um objeto usado para executar uma instrução SQL estática e retornar o resultado gerado.
- PrepatedStatement: A instrução SQL é pré-compilada e armazenada neste objeto.Este objeto pode ser usado para executar a instrução de forma eficiente várias vezes.
- CallableStatement: usado para executar procedimentos armazenados SQL
3.2 Desvantagens de usar a declaração para manipular tabelas de dados
-
Crie o objeto chamando o método createStatement () do objeto Connection. Este objeto é usado para executar instruções SQL estáticas e retornar os resultados da execução.
-
Os métodos a seguir são definidos na interface de instrução para executar instruções SQL:
int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE ResultSet executeQuery(String sql):执行查询操作SELECT
-
No entanto, existem desvantagens em usar a instrução para manipular tabelas de dados:
- Pergunta 1: há uma operação de ortografia complicada
- Problema 2: há um problema de injeção de SQL
-
A injeção de SQL é o uso de certos sistemas que não verificam totalmente os dados de entrada do usuário e injetam segmentos de instrução SQL ilegais ou comandos nos dados de entrada do usuário (como: SELECT usuário, senha FROM user_table WHERE user = 'a' OR 1 = 'AND password =' OR '1' = '1'), de modo a usar o mecanismo SQL do sistema para concluir ações maliciosas.
-
Para Java, para evitar injeção de SQL, basta usar PreparedStatement (estendido de Instrução) para substituir Instrução.
-
Demonstração de código:
public class StatementTest {
// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
@Test
public void testLogin() {
Scanner scan = new Scanner(System.in);
System.out.print("用户名:");
String userName = scan.nextLine();
System.out.print("密 码:");
String password = scan.nextLine();
// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
+ "'";
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
// 使用Statement实现对数据表的查询操作
public <T> T get(String sql, Class<T> clazz) {
T t = null;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加载配置文件
InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
// 2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
// 3.加载驱动
Class.forName(driverClass);
// 4.获取连接
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement();
rs = st.executeQuery(sql);
// 获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
// 获取结果集的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// //1. 获取列的名称
// String columnName = rsmd.getColumnName(i+1);
// 1. 获取列的别名
String columnName = rsmd.getColumnLabel(i + 1);
// 2. 根据列名获取对应数据表中的数据
Object columnVal = rs.getObject(columnName);
// 3. 将数据表中得到的数据,封装进对象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return null;
}
}
Resumindo:
3.3 Uso de PreparedStatement
3.3.1 Introdução ao PreparedStatement
-
O objeto PreparedStatement pode ser obtido chamando o método readyStatement (String sql) do objeto Connection
-
A interface PreparedStatement é uma subinterface de Statement, que representa uma instrução SQL pré-compilada
-
Os parâmetros na instrução SQL representados pelo objeto PreparedStatement são representados por pontos de interrogação (?). Chame o método setXxx () do objeto PreparedStatement para definir esses parâmetros. O método setXxx () tem dois parâmetros, o primeiro parâmetro é o SQL instrução a ser definida O índice do parâmetro em (começando em 1), o segundo é o valor do parâmetro na instrução SQL definida
3.3.2 Declaração Preparada vs Declaração
-
A legibilidade e manutenção do código.
-
PreparedStatement pode maximizar o desempenho:
- A instrução DBServer pré-compilada fornece otimização de desempenho. Como a instrução preparada pode ser chamada repetidamente, o código de execução da instrução após ser compilado pelo compilador DBServer é armazenado em cache, portanto, desde que seja a mesma instrução preparada na próxima vez que for chamada, não precisa ser compilada, contanto que os parâmetros sejam passados diretamente em A instrução compilada será executada no código de execução.
- Na instrução da instrução, mesmo que seja a mesma operação, mas porque o conteúdo dos dados é diferente, a instrução inteira em si não pode ser correspondida e não há significado de armazenar a instrução em cache. O fato é que nenhum banco de dados armazenará o código de execução depois a compilação da declaração comum. Dessa forma, a instrução de entrada deve ser compilada toda vez que for executada.
- (Verificação de sintaxe, verificação semântica, tradução em comando binário, cache)
-
PreparedStatement pode evitar injeção de SQL
3.3.3 Java e SQL tabela de conversão de tipo de dados correspondente
Tipo Java | Tipo SQL |
---|---|
boleano | MORDEU |
byte | TINYINT |
baixo | PEQUENO |
int | INTEIRO |
longo | BIGINT |
Fragmento | CHAR, VARCHAR, LONGVARCHAR |
matriz de bytes | BINARY, VAR BINARY |
java.sql.Date | ENCONTRO |
java.sql.Time | TEMPO |
java.sql.Timestamp | TIMESTAMP |
3.3.4 Use PreparedStatement para adicionar, excluir e modificar operações
//通用的增、删、改操作(体现一:增、删、改 ; 体现二:针对于不同的表)
public void update(String sql,Object ... args){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.获取PreparedStatement的实例 (或:预编译sql语句)
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}
//4.执行sql语句
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally{
//5.关闭资源
JDBCUtils.closeResource(conn, ps);
}
}
3.3.5 Use PreparedStatement para implementar operações de consulta
// 通用的针对于不同表的查询:返回一个对象 (version 1.0)
public <T> T getInstance(Class<T> clazz, String sql, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.获取数据库连接
conn = JDBCUtils.getConnection();
// 2.预编译sql语句,得到PreparedStatement对象
ps = conn.prepareStatement(sql);
// 3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 4.执行executeQuery(),得到结果集:ResultSet
rs = ps.executeQuery();
// 5.得到结果集的元数据:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 6.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// 遍历每一个列
// 获取列值
Object columnVal = rs.getObject(i + 1);
// 获取列的别名:列的别名,使用类的属性名充当
String columnLabel = rsmd.getColumnLabel(i + 1);
// 6.2使用反射,给对象的相应属性赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7.关闭资源
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
Descrição: a operação de consulta implementada por PreparedStatement pode substituir a operação de consulta implementada por Statement e resolver o problema de stringing de declaração e injeção de SQL.
3.4 ResultSet 与 ResultSetMetaData
3.4.1 ResultSet
-
A consulta precisa chamar o método executeQuery () de PreparedStatement e o resultado da consulta é um objeto ResultSet
-
O objeto ResultSet encapsula o conjunto de resultados da operação do banco de dados na forma de uma tabela lógica, e a interface ResultSet é implementada pelo fornecedor do banco de dados
-
O que o ResultSet retorna é na verdade uma tabela de dados. Existe um ponteiro para a frente do primeiro registro na tabela de dados.
-
O objeto ResultSet mantém um cursor apontando para a linha de dados atual.Inicialmente, o cursor está antes da primeira linha e pode ser movido para a próxima linha por meio do método next () do objeto ResultSet. Chame o método next () para verificar se a próxima linha é válida. Se for válido, o método retorna verdadeiro e o ponteiro se move para baixo. É equivalente a uma combinação dos métodos hasNext () e next () do objeto Iterator.
-
Quando o ponteiro aponta para uma linha, você pode obter o valor de cada coluna chamando getXxx (int index) ou getXxx (int columnName).
- Por exemplo: getInt (1), getString ("nome")
- Nota: Todos os índices na API Java relevante envolvida na interação entre Java e o banco de dados começam em 1.
-
Métodos comuns da interface ResultSet:
-
próximo booleano ()
-
getString ()
-
…
-
3.4.2 ResultSetMetaData
-
Um objeto que pode ser usado para obter informações sobre os tipos e propriedades de colunas no objeto ResultSet
-
ResultSetMetaData meta = rs.getMetaData ();
-
getColumnName (int column): obtém o nome da coluna especificada
-
getColumnLabel (int column): obtém o alias da coluna especificada
-
getColumnCount (): Retorna o número de colunas no objeto ResultSet atual.
-
getColumnTypeName (int column): Recupere o nome do tipo específico do banco de dados da coluna especificada.
-
getColumnDisplaySize (int column): Indica a largura padrão máxima da coluna especificada, em caracteres.
-
isNullable (coluna int): indica se o valor na coluna especificada pode ser nulo.
-
isAutoIncrement (int column): indica se numerar automaticamente as colunas especificadas para que essas colunas ainda sejam somente leitura.
-
Pergunta 1: Depois de obter o conjunto de resultados, como posso saber quais colunas estão no conjunto de resultados? Qual é o nome da coluna?
Precisa usar um objeto que descreva o ResultSet, ou seja, ResultSetMetaData
Pergunta 2: Sobre ResultSetMetaData
- Como obter ResultSetMetaData : basta chamar o método getMetaData () de ResultSet
- Obtenha quantas colunas estão no ResultSet: chame o método getColumnCount () de ResultSetMetaData
- Obtenha o alias de cada coluna do ResultSet: chame o método getColumnLabel () de ResultSetMetaData
3.5 Liberação de recursos
- Libere o ResultSet, a instrução e a conexão.
- A conexão com o banco de dados (Connection) é um recurso muito raro e deve ser liberado imediatamente após o esgotamento.Se a conexão não puder ser encerrada a tempo e corretamente, o sistema ficará inativo. O princípio de usar o Connection é criar o mais tarde possível e liberar o mais cedo possível.
- Ele pode ser fechado finalmente, para garantir que outros códigos sejam anormais no tempo, e o recurso deve ser fechado.
3.6 Resumo da API JDBC
-
Dois pensamentos
-
Idéias de programação orientada a interface
-
Idéia ORM (mapeamento relacional de objetos)
- Uma tabela de dados corresponde a uma classe java
- Um registro na tabela corresponde a um objeto da classe java
- Um campo na tabela corresponde a um atributo da classe java
SQL precisa ser escrito em combinação com nomes de colunas e nomes de atributos de tabelas. Preste atenção aos apelidos.
-
-
Duas tecnologias
- Metadados do conjunto de resultados JDBC: ResultSetMetaData
- Obtenha o número de colunas: getColumnCount ()
- Obtenha o alias da coluna: getColumnLabel ()
- Por meio da reflexão, crie um objeto da classe especificada, obtenha os atributos especificados e atribua valores
- Metadados do conjunto de resultados JDBC: ResultSetMetaData