第3节:实现CRUD操作

3.1 操作和访问数据库

  • 数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。

  • 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:

    • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
    • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
    • CallableStatement:用于执行 SQL 存储过程

3.1.1 添加操作

package com.hanker.util;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
//添加数据
public class Demo2 {public static void main(String[] args) {Connection conn  = null;//连接对象Statement stmt = null;//语句对象:执行SQL命令try {//1.加载驱动Class.forName("com.mysql.jdbc.Driver");//2.获取连接String url = "jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8";conn = DriverManager.getConnection(url, "root", "123456");//3.编写SQL语句String sql  = "insert into student values ('11','易烊千玺','1990-09-08','女',50)";//4.创建Statement对象,执行SQL语句stmt = conn.createStatement();int rows = stmt.executeUpdate(sql);if(rows>0) {System.out.println("添加成功");}else {System.out.println("添加失败");}} catch (Exception e) {e.printStackTrace();}finally {//5.释放资源try {if(stmt != null) {stmt.close();//NullPointerException}if(conn != null) {conn.close();}} catch (Exception e2) {e2.printStackTrace();}}}
}

3.1.2 查询操作

package com.hanker.util;import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
//查询数据
public class Demo6 {public static void main(String[] args) {Connection conn = null;Statement  stmt = null;ResultSet  rs   = null;try {//1.加载驱动Class.forName("com.mysql.jdbc.Driver");//2.获取连接String url  = "jdbc:mysql:///db2?useUnicode=true&characterEncoding=utf8";conn = DriverManager.getConnection(url, "root", "123456");//3.编写SQL语句String sql = "select s_id,s_name,s_sex,s_birth,s_weight from student";//4.创建语句对象stmt = conn.createStatement();//5.执行查询rs = stmt.executeQuery(sql);//6.遍历结果集System.out.println("编号\t姓名\t性别\t生日\t\t体重");while(rs.next()) {String sid = rs.getString(1);String sname = rs.getString("s_name");String ssex = rs.getString(3);Date sbirth = rs.getDate("s_birth");int sweight = rs.getInt("s_weight");System.out.println(sid+"\t"+sname+"\t"+ssex+"\t"+sbirth+"\t"+sweight);}} catch (Exception e) {e.printStackTrace();}finally {try {if(rs!=null) rs.close();if(stmt!=null) stmt.close();if(conn!=null) conn.close();} catch (Exception e2) {e2.printStackTrace();}}}
}

3.1.3 删除操作

package com.hanker.util;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;public class Demo4 {/*** 1.自动加载驱动* 2."jdbc:mysql:///db2" 代表的就是 jdbc:mysql://localhost:3306/db2* 3.使用try自动关闭机制* @param args*/public static void main(String[] args) {//1.获取连接String url  = "jdbc:mysql:///db2";//2.sql语句String sql = "delete from student where s_id='09'";try (//此处代码必须实现AutoCloseable接口,自动关闭资源,一般是数据库连接和文件操作Connection conn  = DriverManager.getConnection(url, "root", "123456");//3.创建Statement对象Statement stmt = conn.createStatement();){//4.执行SQL语句int rows  = stmt.executeUpdate(sql);if(rows>0) {System.out.println("删除成功");}else {System.out.println("删除失败");}}catch (Exception e) {e.printStackTrace();}}
}

3.1.4 修改操作

package com.hanker.util;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Scanner;public class Demo5 {public static void main(String[] args) {Scanner input = new Scanner(System.in);Connection conn  = null;Statement stmt  = null;try {// 1.加载驱动Class.forName("com.mysql.jdbc.Driver");// 2. 获取连接String url = "jdbc:mysql:///db2?useUnicode=true&characterEncoding=UTF8";conn  = DriverManager.getConnection(url, "root", "123456");System.out.print("请输入更新用户的ID:");String sid = input.next();System.out.print("请输入更新用户的姓名:");String sname = input.next();System.out.print("请输入更新用户的出生日期:");String sbirth = input.next();System.out.print("请输入更新用户的性别:");String ssex  = input.next();System.out.print("请输入更新用户的体重:");int sweight = input.nextInt();//3.编写SQL语句/*String sql  =  "update student set s_name='" + sname+"',s_birth='"+sbirth+"',s_sex='"+ssex+"',s_weight="+sweight+" where s_id='"+sid+"'";*/StringBuffer buffer  =new StringBuffer("update student set s_name='");buffer.append(sname).append("',s_birth='").append(sbirth).append("',s_sex='").append(ssex).append("',s_weight=").append(sweight).append(" where s_id='").append(sid).append("'");System.out.println(buffer);//4.创建语句stmt = conn.createStatement();//5.执行SQL语句int rows = stmt.executeUpdate(buffer.toString());if (rows>0) {System.out.println("更新成功");} else {System.out.println("更新失败");}} catch (Exception e) {e.printStackTrace();} finally {try {if(stmt!=null) {stmt.close();}if(conn!=null) {conn.close();}if(input!=null) {input.close();}} catch (Exception e2) {e2.printStackTrace();}}}
}

3.1.5 JdbcUtils工具类

以上每次都要编写获取连接和关闭资源的代码,可以抽取到一个工具类中实现。

package com.hanker.util;import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;
/*** Jdbc工具类* @author 孔繁玉*/
public class JdbcUtils {private static String url;private static String username;private static String password;private static String driver;/*** 加载属性文件*/public static void loadFile() {Properties prop  = new Properties();try {//1.加载配置文件     获取类加载器        以字节流的范式获取资源文件InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");prop.load(in);driver = prop.getProperty("driverClass");url= prop.getProperty("url");username = prop.getProperty("user");password = prop.getProperty("password");} catch (Exception e) {e.printStackTrace();} }/*** 获取连接方法* @return*/public static Connection getConnection() {Connection conn = null;try {loadFile();//加载属性文件Class.forName(driver);//加载驱动类conn = DriverManager.getConnection(url, username, password);} catch (Exception e) {e.printStackTrace();}return conn;}/*** 关闭资源方法* @param rs* @param stmt* @param conn*/public static void closeAll(ResultSet rs,Statement stmt,Connection conn) {try {if(rs != null) rs.close();if(stmt != null) stmt.close();if(conn!=null) conn.close();} catch (Exception e2) {e2.printStackTrace();}}
}

结果集和实例类映射关系接口 RowMapper:(暂时用不到)

package com.hanker.dao;
import java.sql.ResultSet;
public interface RowMapper<T> {//处理结果集T mapping(ResultSet rs) throws Exception;
}
//实现类的用法
//关键代码: 解决rs和对象属性的映射关系,如果是OrderDao就是OrderRowMapper
public  class SysUserRowMapper implements RowMapper<List<SysUser>>{@Overridepublic List<SysUser> mapping(ResultSet rs) throws Exception {List<SysUser> list = new ArrayList<>();while(rs.next()) {int id = rs.getInt("id");String username = rs.getString("username");String password = rs.getString("password");SysUser user = new SysUser(id,username,password);list.add(user);}return list;}
}

3.2 使用Statement操作数据表的弊端

  • 通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。

  • Statement 接口中定义了下列方法用于执行 SQL 语句:

    int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE
    ResultSet executeQuery(String sql):执行查询操作SELECT
    
  • 但是使用Statement操作数据表存在弊端:

    • 问题一:存在拼串操作,繁琐
    • 问题二:存在SQL注入问题
  • SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ = ‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法。

  • 对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。

  • 代码演示:

public class StatementTest {// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题@Testpublic void testLogin() {Scanner scan = new Scanner(System.in);System.out.print("用户名:");String userName = scan.nextLine();System.out.print("密   码:");String password = scan.nextLine();//用户名:admin//密码:' or '2'='2// 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);//方法在3.3.5 使用PreparedStatement实现查询操作if (user != null) {System.out.println("登陆成功!");} else {System.out.println("用户名或密码错误!");}}//实现用户登录,系统输入用户账户和密码再到数据库查询,如果查询到说明登录成功;否则登录失败//SQL注入漏洞@Testpublic void testLogin2() {Scanner input = new Scanner(System.in);Connection conn = null;//数据库连接对象PreparedStatement  pstmt = null;//语句对象ResultSet  rs   = null;//结果集对象try {//1.加载驱动Class.forName("com.mysql.jdbc.Driver");//2.获取连接conn  = DriverManager.getConnection("jdbc:mysql:///db2","root","123456");System.out.print("请输入用户名:");String username  = input.nextLine();System.out.print("请输入密码:");String password = input.nextLine();//3.SQL语句 ?占位符String sql = "select * from users where username=? and password=?";//4.创建语句对象pstmt = conn.prepareStatement(sql);pstmt.setString(1, username);pstmt.setString(2, password);System.out.println("sql=="+pstmt);//5.执行查询rs = pstmt.executeQuery();if(rs.next()) {System.out.println("登录成功");}else {System.out.println("登录失败");}} catch (Exception e) {e.printStackTrace();}finally {try {if(rs!=null) rs.close();if(pstmt!=null) pstmt.close();if(conn!=null) conn.close();if(input!=null) input.close();} catch (Exception e2) {e2.printStackTrace();}}}
}

综上:

3.3 PreparedStatement的使用

3.3.1 PreparedStatement介绍

  • 可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象

  • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句

  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

3.3.2 PreparedStatement vs Statement

  • 代码的可读性和可维护性。

  • PreparedStatement 能最大可能提高性能:

    • DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
    • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
    • (语法检查,语义检查,翻译成二进制命令,缓存)
  • PreparedStatement 可以防止 SQL 注入

3.3.3 Java与SQL对应数据类型转换表

Java类型 SQL类型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY , VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP

3.3.4 使用PreparedStatement实现增、删、改操作

 //通用的增、删、改操作(体现一:增、删、改 ; 体现二:针对于不同的表)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 使用PreparedStatement实现查询操作

 // 通用的针对于不同表的查询:返回一个对象 (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(),得到结果集:ResultSetrs = ps.executeQuery();// 5.得到结果集的元数据:ResultSetMetaDataResultSetMetaData 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);// 6.3 强制访问field.setAccessible(true);// 6.4 给属性赋值field.set(t, columnVal);}return t;}} catch (Exception e) {e.printStackTrace();} finally {// 7.关闭资源JDBCUtils.closeResource(conn, ps, rs);}return null;}

说明:使用PreparedStatement实现的查询操作可以替换Statement实现的查询操作,解决Statement拼串和SQL注入问题。

3.4 ResultSet与ResultSetMetaData

3.4.1 ResultSet

  • 查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象

  • ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现

  • ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。

  • ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator对象的 hasNext() 和 next() 方法的结合体。

  • 当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。

    • 例如: getInt(1), getString(“name”)
    • 注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
  • ResultSet 接口的常用方法:

    • boolean next()

    • getString()


3.4.2 ResultSetMetaData

  • 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象

  • ResultSetMetaData meta = rs.getMetaData();

    • getColumnName(int column):获取指定列的名称

    • getColumnLabel(int column):获取指定列的别名

    • getColumnCount():返回当前 ResultSet 对象中的列数。

    • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。

    • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。

    • isNullable(int column):指示指定列中的值是否可以为 null。

    • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

      问题1:得到结果集后, 如何知道该结果集中有哪些列 ? 列名是什么?

​ 需要使用一个描述 ResultSet 的对象, 即 ResultSetMetaData

问题2:关于ResultSetMetaData

  1. 如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可
  2. 获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法
  3. 获取 ResultSet 每一列的列的别名是什么:调用 ResultSetMetaData 的getColumnLabel() 方法

3.5 资源的释放

  • 释放ResultSet, Statement,Connection。
  • 数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
  • 可以在finally中关闭,保证及时其他代码出现异常,资源也一定能被关闭。

3.6 JDBC API小结

  • 两种思想

    • 面向接口编程的思想

    • ORM思想(object relational mapping)

      • 一个数据表对应一个java类
      • 表中的一条记录对应java类的一个对象
      • 表中的一个字段对应java类的一个属性

    sql是需要结合列名和表的属性名来写。注意起别名。

  • 两种技术

    • JDBC结果集的元数据:ResultSetMetaData

      • 获取列数:getColumnCount()
      • 获取列的别名:getColumnLabel()
    • 通过反射,创建指定类的对象,获取指定的属性并赋值

3.7 章节练习

练习题1:从控制台向数据库的表customers中插入一条数据,表结构如下:

练习题2:创立数据库表 examstudent,表结构如下:

向数据表中添加如下数据:

代码实现1:插入一个新的student 信息

请输入考生的详细信息

Type:
IDCard:
ExamCard:
StudentName:
Location:
Grade:

信息录入成功!

代码实现2:在 eclipse中建立 java 程序:输入身份证号或准考证号可以查询到学生的基本信息。结果如下:

代码实现3:完成学生信息的删除功能


第25章 JDBC核心技术第3节相关推荐

  1. 第25章 JDBC核心技术第4节:操作BLOB类型字段

    4.1 MySQL BLOB类型 MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据. 插入BLOB类型的数据必须使用PreparedStatement, ...

  2. 【云原生进阶之容器】第一章Docker核心技术1.6节——UnionFS

    <重识云原生系列>专题各章首节索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第三章云存储第1节--分布式云存储总述 第四章云网络第一节--云网络技术发展简 ...

  3. 【云原生进阶之容器】第一章Docker核心技术1.1节——Docker综述

    1 Docker简述 1.1 什么是Docker Docker是一个开源的软件项目,让用户程序部署在一个相对隔离的环境运行,借此在Linux操作系统上提供一层额外的抽象,以及操作系统层虚拟化的自动管理 ...

  4. 《HTML5 2D游戏编程核心技术》——第1章,第1.3节特别功能

    本节书摘来自华章出版社<HTML5 2D游戏编程核心技术>一书中的第1章,第1.3节特别功能,作者[美] 戴维·吉尔里,更多章节内容可以访问云栖社区"华章计算机"公众号 ...

  5. 《HTML5 2D游戏编程核心技术》——第2章,第2.3节使用CSS背景

    **本节书摘来自华章出版社<HTML5 2D游戏编程核心技术>一书中的第2章,第2.3节,作者[美] 戴维·吉尔里,更多章节内容可以访问云栖社区"华章计算机"公众号查看 ...

  6. 《HTML5 2D游戏编程核心技术》——第1章,第1.1节Snail Bait游戏

    本节书摘来自华章出版社<HTML5 2D游戏编程核心技术>一书中的第1章,第1.1节,作者[美] 戴维·吉尔里,更多章节内容可以访问云栖社区"华章计算机"公众号查看. ...

  7. 【重识云原生】第六章容器6.1.8节——Docker核心技术UnionFS

    <重识云原生系列>专题索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第二章计算第2节--主流虚拟化技术之VMare ESXi 第二章计算第3节--主流虚拟 ...

  8. 《HTML5 2D游戏编程核心技术》——第1章,第1.8节练习

    本节书摘来自华章出版社<HTML5 2D游戏编程核心技术>一书中的第1章,第1.8节练习,作者[美] 戴维·吉尔里,更多章节内容可以访问云栖社区"华章计算机"公众号查看 ...

  9. 深入浅出JDBC核心技术

    文章目录 JDBC核心技术--课程笔记 第1章:JDBC概述 1.1 数据的持久化 1.2 Java中的数据存储技术 1.3 JDBC介绍 1.4 JDBC体系结构 1.5 JDBC程序编写步骤 第2 ...

最新文章

  1. CentOS 7部署OpenStack(9)—部署dashboard
  2. tensorflow loss nan 解决办法
  3. BGP水平分割的疑惑
  4. 机器人 陆梅东_机器人知识与实践比赛获奖 - 上海徐汇区青少年活动中心
  5. C++析构器详解【C++析构器】
  6. Netty系列之一开始使用
  7. convirt2.5在虚拟机上安装笔记
  8. 为什么腾讯视频下载不了_腾讯视频如何设置背景
  9. python 高阶函数一 概念
  10. html5学习新的知识总结(一)
  11. Git学习系列(七)Bug和Feature分支管理详解
  12. 集中云数据加密能否填补安全漏洞?
  13. 转载:C#7.0新特性(VS2017可用)
  14. 微信团购小程序怎么做?一般要多少钱?
  15. 网站如何设置一个小图标
  16. mysql 数据转移历史表_mysql 历史数据表迁移方案
  17. 三极管为什么可以放大电流?
  18. 解决Thymeleaf报Property or field ‘replyIdContent‘ cannot be found on null的错问题
  19. 萌新必看——10种客户端存储哪家强,一文读尽!
  20. 新路程------imx6 wtd摘要

热门文章

  1. 鸟哥的服务器《十三》Web服务器
  2. 试验解析抖音无水印视频【PHP版】
  3. 5、使用bean的scop属性来配置bean的作用域
  4. Chrome侧边栏书签插件 Candy Bookmarks Sidebar
  5. 【解决方案】Ubuntu设置Matlab桌面启动快捷方式
  6. 红黑树的插入与验证——附图详解
  7. Linux即时通讯软件都有哪些?政企要怎么挑选?
  8. 企业核心-不是技术而是人才
  9. iphone手机音频AAC视频H264推流(一) iphone手机推流最佳方案
  10. 华为智慧屏“两年”,从技术创新到引领电视产业变革