Java Data Base Connectivity (Java数据库连接) JDBC 是Java访问数据库的 标准规范 JDBC的作用: JDBC是用于执行SQL语句的Java API(Java语言通过JDBC可以操作数据库)
JDBC规范定义接口,具体的实现由各大数据库厂商来实现 JDBC是Java访问数据库的标准规范。 真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。 每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。 所以我们只需要会调用JDBC接口中的方法即可。 数据库驱动由数据库厂商提供。 JDBC的好处: 我们只需要会调用JDBC接口中的方法即可,使用简单 使用同一套Java代码,进行少量的修改就可以访问其他JDBC支持的数据库
JDBC会用到的包:
1. java.sql:JDBC访问数据库的基础包,在JavaSE中的包。如:java.sql.Connection 2. javax.sql: JDBC访问数据库的扩展包 3. 数据库的驱动,各大数据库厂商来实现。如:MySQL的驱动:com.mysql.jdbc.Driver
JDBC的核心API
DriverManager类 1)管理和注册数据库驱动 2)得到数据库连接对象 Connection接口 一个连接对象,可用于创建Statement和PreparedStatement对象 Statement接口 一个SQL语句对象,用于将SQL语句发送给数据库服务器。 PreparedStatemen接口 一个SQL语句对象,是Statement的子接口 ResultSet接口 用于封装数据库查询的结果集,返回给客户端Java程序
JDBC访问数据库的步骤
1)加载和注册驱动com.mysql.jdbc.Driver 2)创建连接对象Connection 3)通过Connection对象得到Statement是一个语句对象代表一条SQL语句 4)服务器得到SQL语句以后,执行,将执行的结果集ResultSet发回给客户端 5)释放资源,关闭连接。一个服务器的连接数是有限,使用完以后一定要关闭连接对象。
注册驱动
我们Java程序需要通过数据库驱动才能连接到数据库,因此需要注册驱动。 MySQL的驱动的入口类是:com.mysql.jdbc.Driver 注册MySQL驱动使用 Class.forName("com.mysql.jdbc.Driver");//jdk5后可省略 Class.forName(实现类) 加载指定类,类如果加载,调用静态代码块中的语句。//主要为了运行静态代码块中的语句 // 抛出ClassNotFoundException 类找不到的异常 ,注意不是SQLException。
获取连接
static Connection getConnection(String url, String user, String password) 连接到给定数据库 URL ,并返回连接。 DriverManager.getConnection(url, user, password); 传入对应参数即可 参数说明 1. String url :连接数据库的URL,用于说明连接数据库的位置 2. String user :数据库的账号 3. String password :数据库的密码 连接数据库的URL地址格式: 协议名:子协议://服务器名或IP地址:端口号/数据库名?参数=参数值 MySQL写法: jdbc:mysql://localhost:3306/test 如果是本地服务器,端口号是默认的3306,则可以简写: jdbc:mysql:///test // 注意事项 如果数据出现乱码需要加上参数: ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据。 如: jdbc:mysql://localhost:3306/test?characterEncoding=utf8
public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); // 连接到MySQL // url: 连接数据库的URL // user: 数据库的账号 // password: 数据库的密码 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root", "root"); System.out.println(conn); }
使用配置文件保存数据库账号密码
API介绍 java.sql.DriverManager 类中有如下方法获取数据库连接 static Connection getConnection(String url, Properties info) 试图建立到给定数据库 URL 的连接。 使用步骤 1. 在src目录下创建名为jdbc.properties的文件,内容如下: 2. 将jdbc.properties内容加载到Properties对象中 3. 调用 getConnection 传入url和Properties对象 注意事项 Demo01.class.getResourceAsStream("/jdbc.properties"); 会找到src/jdbc.properties加载,并返回InputStream 对象。
案例代码
public static void main(String[] args) throws Exception { test02(); } // 将src/jdbc.properties文件中的内容加载到Properties对象中 private static void test02() throws Exception { InputStream is = Demo.class.getResourceAsStream("/jdbc.properties"); Properties pp = new Properties(); pp.load(is); Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql:///test", pp); System.out.println(conn); }
JDBC实现对单表数据增、删、改、查 即执行DML操作
我们要对数据库进行增、删、改、查,需要使用 Statement 对象来执行SQL语句。
JDBC实现对单表数据增、删、改
注意:在MySQL中,只要不是查询就是修改。 Statement createStatement() 创建一个 Statement 对象来将 SQL 语句发送到数据库 boolean execute(String sql) 此方法可以执行任意sql语句。返回boolean值,表示是否返回ResultSet结果集。仅当执行select语句,且有返回结果时返回true, 其它语句都返回false; int executeUpdate(String sql) 根据执行的DML(INSERT、UPDATE、DELETE)语句,返回受影响的行数 ResultSet executeQuery(String sql) 根据查询语句返回结果集,只能执行SELECT语句 //executeUpdate:用于执行增删改 //executeQuery:用于执行查询
‐‐ 创建分类表 CREATE TABLE category ( cid INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(100) ); ‐‐ 初始化数据 INSERT INTO category (cname) VALUES('家电'); INSERT INTO category (cname) VALUES('服饰'); INSERT INTO category (cname) VALUES('化妆品');
public static void main(String[] args) { Connection conn = null; Statement stmt = null; try { //1.加载和注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 得到连接对象 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","root"); //3. 得到语句对象 stmt = conn.createStatement(); //4. 发送SQL语句给服务器 String sql = "CREATE TABLE Student (id int PRIMARY KEY auto_increment, name VARCHAR(20) not null, gender boolean, birthday date)"; boolean b = stmt.execute(sql); System.out.println(b); //false } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } finally { //5. 释放资源 if (stmt!=null) { try { stmt.close(); //关闭 } catch (SQLException e) { e.printStackTrace(); } } if (conn!=null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
public class Demo { public static void main(String[] args) throws Exception { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 获取连接 Connection conn = DriverManager.getConnection("jdbc:mysql:///test", "root", "root"); System.out.println(conn); // String sql = "SELECT * FROM category;"; // 从连接中拿到一个Statement对象 Statement stmt = conn.createStatement(); // 1.插入记录 String sql = "INSERT INTO category (cname) VALUES ('手机');"; int i = stmt.executeUpdate(sql); System.out.println("影响的行数:" + i); // 2.修改记录 sql = "UPDATE category SET cname='汽车' WHERE cid=4;"; i = stmt.executeUpdate(sql); System.out.println("影响的行数:" + i); // 3.删除记录 sql = "DELETE FROM category WHERE cid=1;"; i = stmt.executeUpdate(sql); System.out.println("影响的行数:" + i); // 释放资源 stmt.close(); conn.close(); } }
JDBC实现对单表数据查询 即执行DQL操作
ResultSet 用于保存执行查询SQL语句的结果。 我们不能一次性取出所有的数据,需要一行一行的取出。 ResultSet的原理: 1. ResultSet内部有一个指针,刚开始记录开始位置 2. 调用next方法, ResultSet内部指针会移动到下一行数据 3. 我们可以通过ResultSet得到一行数据 getXxx得到某列数据 //第一次取出数据要先调用next方法 注意: 1. 如果光标在第一行之前,使用rs.getXXX()获取列值,报错:Before start of result set 2. 如果光标在最后一行之后,使用rs.getXXX()获取列值,报错:After end of result set ResultSet获取数据的API 其实ResultSet获取数据的API是有规律的get后面加数据类型。 我们统称 getXXX()
常用数据类型转换表 SQL类型 Jdbc对应方法 返回类型 BIT(1)bit(n) getBoolean() boolean TINYINT getByte() byte SMALLINT getShort() short INT getInt() int BIGINT getLong() long CHAR,VARCHAR getString() String DATE getDate() java.sql.Date 只表示日期 TIME getTime() java.sql.Time 只表示时间 TIMESTAMP getTimestamp() java.sql.Timestamp 同时有日期和时间 注:java.sql.Date、java.sql.Time、java.sql.Timestamp(时间戳),三个共同父类是:java.util.Date
使用JDBC查询数据库中的数据的步骤
1. 注册驱动 2. 获取连接 3. 获取到Statement 4. 使用Statement执行SQL 5. ResultSet处理结果 6. 关闭资源
public class Test01 { public static void main(String[] args) throws Exception { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获取连接 Connection conn = DriverManager.getConnection("jdbc:mysql:///test", "root", "root"); //获取到Statement Statement stmt = conn.createStatement(); String sql = "SELECT * FROM category;"; //使用Statement执行SQL //ResultSet处理结果 ResultSet rs = stmt.executeQuery(sql); // 内部有一个指针,只能取指针指向的那条记录 while (rs.next()) { // 指针移动一行,有数据才返回true // 取出数据 int cid = rs.getInt("cid"); String cname = rs.getString("cname"); System.out.println(cid + " == " + cname); } // 关闭资源 rs.close(); stmt.close(); conn.close(); } }
JDBC获取连接与关闭连接工具类实现
我们发现每次去执行SQL语句都需要注册驱动,获取连接,得到Statement,以及释放资源。 发现很多重复的劳动,我们可以将重复的代码定义到某个类的方法中。 直接调用方法,可以简化代码。 那么我们接下来定义一个 JDBCUtil 类。 把注册驱动,获取连接,得到Statement,以及释放资源的代码放到这个类的方法中。 以后直接调用方法即可。
什么时候需要创建自己的工具类?
如果有一些代码重复,而且不同的地方要用到。同时代码变化又比较少,做成一个工具类。
编写JDBC工具类步骤
1. 将固定字符串定义为常量 2. 在静态代码块中注册驱动(只注册一次) 3. 提供一个获取连接的方法 static Connection getConneciton(); 4.定义关闭资源的方法 close(Connection conn, Statement stmt); close(Connection conn, Statement stmt, ResultSet rs;
/** * JDBC工具类 */ public class JdbcUtils { //1) 可以把几个字符串定义成常量:用户名,密码,URL,驱动类 private static final String USER = "root"; private static final String PASSWORD = "root"; private static final String URL = "jdbc:mysql://localhost:3306/test"; private static final String DRIVER = "com.mysql.jdbc.Driver"; //2)注册驱动,为了兼容以前的程序 static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 得到数据库的连接 * @return */ public static Connection getConnection() { try { return DriverManager.getConnection(URL,USER,PASSWORD); } catch (SQLException e) { throw new RuntimeException(e); } } /** * 关闭所有打开的资源 */ public static void close(Connection conn, Statement stmt) { if (stmt!=null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn!=null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs!=null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } close(conn,stmt); } }
JDBC实现登录案例
模拟用户输入账号和密码登录网站
输入正确的账号,密码,显示登录成功
输入错误的账号,密码,显示登录失败
-- 数据库数据 CREATE TABLE USER ( id INT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(50), PASSWORD VARCHAR(50) ); INSERT INTO USER (NAME, PASSWORD) VALUES('admin', '123'), ('test', '123'), ('gm', '123');
public class DemoLogin { public static void main(String[] args) { //1)从控制台上输入的用户名和密码 Scanner scanner = new Scanner(System.in); System.out.println("请输入用户名:"); String name = scanner.nextLine(); System.out.println("请输入密码:"); String password = scanner.nextLine(); //2)调用下面写的登录方法来实现登录 login(name, password); } //3)写一个登录的方法 private static void login(String name, String password) { Connection conn = null; Statement statement = null; ResultSet rs = null; try { // a)通过工具类得到连接 conn = JdbcUtils.getConnection(); // b)创建语句对象,使用拼接字符串的方式生成SQL语句 statement = conn.createStatement(); // c)查询数据库,如果有记录则表示登录成功,否则登录失败 String sql = "select * from user where name='" + name + "' and password='" + password + "'"; System.out.println(sql); rs = statement.executeQuery(sql); if (rs.next()) { System.out.println("欢迎您,登录成功!" + name); } else { System.out.println("登录失败"); } } catch (SQLException e) { e.printStackTrace(); } // d)释放资源 finally { JdbcUtils.close(conn,statement,rs); } } } //当我们输入密码为a' or '1'='1,我们发现我们账号和密码都不对竟然登录成功了
SQL注入问题
让用户输入的密码和SQL语句进行字符串拼接。 用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义, 以上问题称为SQL注入。 要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。
PreparedSatement预编译对象
PreparedSatement的执行原理
我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。 和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。 Satement 对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。 PreparedSatement的好处 1)prepareStatement()会先将SQL语句发送给数据库预编译。 PreparedStatement会引用着预编译后的结果。 可以多次传入不同的参数给PreparedStatement对象并执行。减少SQL编译次数,提高效率。 2)安全性更高,没有SQL注入的隐患 3)提高了程序的可读性
PreparedSatement使用步骤
1. 编写SQL语句,未知内容使用?占位: "SELECT * FROM user WHERE name=? AND password=?"; 2. 获得PreparedStatement对象 3. 设置实际参数 4. 执行参数化SQL语句 5. 关闭资源 void setXxx(int 参数1,参数2) Xxx表示不同的数据类型 参数1:表示用来代替第几个占位符,从1开始计数。 参数2:表示要替换的真实值
PreparedStatement prepareStatement(String sql) 会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。 void setDouble(int parameterIndex, double x) 将指定参数设置为给定 Java double 值。 void setFloat(int parameterIndex, float x) 将指定参数设置为给定 Java REAL 值。 void setInt(int parameterIndex, int x) 将指定参数设置为给定 Java int 值。 void setLong(int parameterIndex, long x) 将指定参数设置为给定 Java long 值。 void setObject(int parameterIndex, Object x) 使用给定对象设置指定参数的值。 void setString(int parameterIndex, String x) 将指定参数设置为给定 Java String 值。 ResultSet executeQuery() 在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的ResultSet对象。 int executeUpdate() 在此 PreparedStatement 对象中执行 SQL 语句, 该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句, 比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL语句。
public class DemoLogin { public static void main(String[] args) { //1)从控制台上输入的用户名和密码 Scanner scanner = new Scanner(System.in); System.out.println("请输入用户名:"); String name = scanner.nextLine(); System.out.println("请输入密码:"); String password = scanner.nextLine(); //2)调用下面写的登录方法来实现登录 login(name, password); } //使用PreparedStatement接口 private static void login(String name, String password) { //1)编写SQL语句,未知内容使用?占位 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); //2)获得PreparedStatement对象 // prepareStatement()会先将SQL语句发送给数据库预编译。 ps = conn.prepareStatement("SELECT * FROM user WHERE name=? AND password=?"); //3)设置实际参数 // 指定?的值 // parameterIndex: 第几个?,从1开始算 // x: 具体的值 ps.setString(1, name); ps.setString(2, password); //4)执行参数化SQL语句 rs = ps.executeQuery(); if (rs.next()) { System.out.println("登录成功," + name); } else { System.out.println("登录失败"); } } catch (SQLException ex) { ex.printStackTrace(); } //5)关闭资源 finally { JdbcUtils.close(conn, ps, rs); } } }
// 添加数据: 向Employee表添加3条记录 public static void addEmployee() throws Exception { Connection conn = JDBCUtils.getConnection(); String sql = "INSERT INTO employee VALUES (NULL, ?, ?, ?);"; // prepareStatement()会先将SQL语句发送给数据库预编译。 PreparedStatement pstmt = conn.prepareStatement(sql); // 设置参数 pstmt.setString(1, "刘德华"); pstmt.setInt(2, 57); pstmt.setString(3, "香港"); int i = pstmt.executeUpdate(); System.out.println("影响的行数:" + i); // 再次设置参数 pstmt.setString(1, "张学友"); pstmt.setInt(2, 55); pstmt.setString(3, "澳门"); i = pstmt.executeUpdate(); System.out.println("影响的行数:" + i); // 再次设置参数 pstmt.setString(1, "黎明"); pstmt.setInt(2, 52); pstmt.setString(3, "香港"); i = pstmt.executeUpdate(); System.out.println("影响的行数:" + i); JDBCUtils.close(conn, pstmt); }
批处理
如果有大量的sql语句需要执行的时候,如果一条一条执行效率太低, 这种情况我们可以使用批处理去执行一大批的sql语句,提高了执行的效率。
普通的插入方式:
/* 使用普通的方式一条一条sql进行执行。 插入10000条数据 */ public void insert1() throws Exception{ long startTime = System.currentTimeMillis(); Connection connection = JDBCUtil.getConnection(); //准备sql String sql = "insert into emp(name,salary) values(?,?)"; //创建预编译对象 PreparedStatement pst = connection.prepareStatement(sql); for(int i = 0 ; i<1000 ; i++){ pst.setString(1, i+"号"); pst.setDouble(2, 800+i); //执行 pst.execute(); } //关闭资源 JDBCUtil.close(connection, pst, null); long endTime = System.currentTimeMillis(); System.out.println("耗时:"+(endTime - startTime)); }
批处理的插入方式:
/* 批处理 addBatch() 添加到批处理中 executeBatch() 把批处理前的所有sql执行。 */ public void batInsert() throws SQLException{ long startTime = System.currentTimeMillis(); Connection connection = JDBCUtil.getConnection(); //准备sql String sql = "insert into emp(name,salary) values(?,?)"; //创建预编译对象 PreparedStatement pst = connection.prepareStatement(sql); for(int i =0 ; i <1010 ; i++){ pst.setString(1, i+"号"); pst.setDouble(2, 800+i); //把该sql语句添加到批处理中, pst其实内部维护了一个容器。可以存放很多的sql语句的。 pst.addBatch(); } //一次性执行刚刚所有添加的sql pst.executeBatch(); //关闭资源 JDBCUtil.close(connection, pst, null); long endTime = System.currentTimeMillis(); System.out.println("耗时:"+(endTime - startTime)); }
addBatch() 添加到批处理中 executeBatch() 执行批处理。 clearBatch(); 清空批处理 注意: 批处理默认mysql也是关闭的,如果需要打开那么需要在连接参数添加参数: MySQL的批处理也需要通过参数来打开:rewriteBatchedStatements=true