02-JDBC unit 02-JDBC JDBC概述 什么是JDBC?为什么要学习JDBC? JDBC(Java Database Connectivity) Java数据库连接
其实就是利用Java语言/程序连接并访问数据库的一门技术
之前我们可以通过CMD或者navicat等工具连接数据库
但在企业开发中,更多的是通过程序(Java程序)连接并访问数据库,通过Java程序访问数据库,就需要用到JDBC这门技术。
5fa0f308f5ff06beac3c7f18614c9ff3
如何通过JDBC程序访问数据库? 1、提出需求:
创建一个 jt_db 数据库,在库中创建一个account表,并插入三条记录,然后利用Java程序查询出account表中所有的记录,并将查询的结果打印在控制台上。
2、开发步骤:
(1)准备数据, 创建jt_db库, 创建account表
1 2 3 4 5 6 7 8 9 10 11 drop database if exists jt_db; create database jt_db charset utf8; use jt_db; create table account( id int primary key auto_increment, name varchar(50), money double ); insert into account values(null, 'tom', 1000); insert into account values(null, 'andy', 1000); insert into account values(null, 'tony', 1000);
如果已经执行过课前资料中的”SQL脚本文件”,此步骤可以跳过。
(2)创建JAVA工程:
image-20200318153229937
(3)导入jar包——mysql驱动包:
image-20200318153627820
6ba3042ef2947f314836128a5c19e2c2
(4)创建类并实现JDBC程序(六个步骤)
dccd4a8163b4835b74e77f945d106683
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static void main (String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver" ); Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8" , "root" , "root" ); Statement stat = conn.createStatement(); String sql = "select * from account" ; ResultSet rs = stat.executeQuery( sql ); while ( rs.next() ) { int id = rs.getInt("id" ); String name = rs.getString("name" ); double money = rs.getDouble("money" ); System.out.println(id+" : " +name+" : " +money); } rs.close(); stat.close(); conn.close(); System.out.println("TestJdbc.main()...." ); }
3、执行结果:
a37ab7234bb1beb988f4b9c01c8cee2f
JDBC API总结 1、注册数据库驱动
Class.forName(“com.mysql.jdbc.Driver”);
所谓的注册驱动,就是让JDBC程序加载mysql驱动程序,并管理驱动
驱动程序实现了JDBC API定义的接口以及和数据库服务器交互的功能,加载驱动是为了方便使用这些功能。
2、获取连接之数据库URL
1 2 3 Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8" , "root" , "root" );
DriverManager.getConnection() 用于获取数据连接,返回的Connection连接对象是JDBC程序连接数据库至关重要的一个对象。
参数2 和参数3 分别是所连接数据库的用户名和密码。
参数1 :”jdbc:mysql://localhost:3306/jt_db” 是连接数据库的URL,用于指定访问哪一个位置上的数据库服务器及服务器中的哪一个数据库,其写法为:
7edb0faa5fac5f29cd19411909c708cb
当连接本地数据库,并且端口为3306,可以简写为如下形式:
3、Statement传输器对象
1 2 Statement stat = conn.createStatement(); 该方法返回用于向数据库服务器发送sql语句的Statement传输器对象
该对象上提供了发送sql的方法:
1 2 3 4 executeQuery(String sql) -- 用于向数据库发送查询类型的sql语句,返回一个ResultSet对象中 executeUpdate(String sql) -- 用于向数据库发送更新(增加、删除、修改)类型的sql语句,返回一个int值,表示影响的记录行数
4、ResultSet结果集对象
ResultSet对象用于封装sql语句查询的结果,也是一个非常重要的对象。该对象上提供了遍历数据及获取数据的方法。
(1)遍历数据行的方法
next() – 使指向数据行的箭头向下移动一行,并返回一个布尔类型的结果,true表示箭头指向了一行数据,false表示箭头没有指向任何数据(后面也没有数据了)
(2)获取数据的方法
1 2 3 4 5 6 7 8 getInt(int columnIndex) getInt(String columnLable) getString(int columnIndex) getString(String columnLable) getDouble(int columnIndex) getDouble(String columnLable) getObject(int columnIndex) getObject(String columnLable)
5、释放资源
1 2 3 rs.close(); stat.close(); conn.close();
此处释放资源必须按照一定的顺序释放,越晚获取的越先关闭。所以先关闭 rs对象,再关闭stat对象,最后关闭conn对象。
另,为了避免上面的程序抛出异常,释放资源的代码不会执行,应该把释放资源的代码放在finally块中.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 try { ... }catch (Exception e){ ... }finally { if (rs != null ) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } finally { rs = null ; } } if (stat != null ) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } finally { stat = null ; } } if (conn != null ) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } finally { conn = null ; } } }
增删改查 1、新增:往account表中添加一个名称为john、money为3500的记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void testInsert () { Connection conn = null ; Statement stat = null ; ResultSet rs = null ; try { conn = JdbcUtil.getConn(); stat = conn.createStatement(); String sql = "insert into account values(null, 'john', 3500)" ; int rows = stat.executeUpdate( sql ); System.out.println( "影响行数: " +rows ); } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.close(conn, stat, rs); } }
2、修改:将account表中名称为john的记录,money修改为1500
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void testUpdate () { Connection conn = null ; Statement stat = null ; ResultSet rs = null ; try { conn = JdbcUtil.getConn(); stat = conn.createStatement(); String sql = "update account set money=1500 where name='john'" ; int rows = stat.executeUpdate( sql ); System.out.println( "影响行数: " +rows ); } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.close(conn, stat, rs); } }
3、查询:查询account表中名称为john的记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Test public void testFindById () { Connection conn = null ; Statement stat = null ; ResultSet rs = null ; try { conn = JdbcUtil.getConn(); stat = conn.createStatement(); String sql = "select * from account where id=1" ; rs = stat.executeQuery( sql ); if ( rs.next() ) { int id = rs.getInt("id" ); String name = rs.getString("name" ); double money = rs.getDouble("money" ); System.out.println( id+" : " +name+" : " +money); } } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.close(conn, stat, rs); } }
4、删除:删除account表中名称为john的记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void testDelete () { Connection conn = null ; Statement stat = null ; ResultSet rs = null ; try { conn = JdbcUtil.getConn(); stat = conn.createStatement(); String sql = "delete from account where name='john'" ; int rows = stat.executeUpdate( sql ); System.out.println( "影响行数: " +rows ); } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.close(conn, stat, rs); } }
内容补充:使用单元测试测试上面的方法
单元测试:不用创建新的类,也不用提供main函数,也不用创建类的实例,就可以直接执行一个方法。能使用单元测试测试的方法需要满足以下几点:
1 2 3 4 (1)方法必须是公共的 (2)方法必须是非静态的 (3)方法必须是无返回值的 (4)方法必须是无参数的
PreparedStatement 在上面的增删改查的操作中,使用的是Statement传输器对象,而在开发中我们用的更多的传输器对象是PreparedStatement对象,PreparedStatement是Statement的子接口,比Statement更加安全,并且能够提高程序执行的效率。
Statement 父对象
PreparedStatement 子对象
模拟用户登录案例 (1)准备数据
1 2 3 4 5 6 7 8 use jt_db;create table user ( id int primary key auto_increment, username varchar (50 ), password varchar (50 ) ); insert into user values (null ,'张三' ,'123' );insert into user values (null ,'李四' ,'234' );
(2)创建LoginUser 类,提供 main 方法 和 login 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public static void main (String[] args) { Scanner sc = new Scanner(System.in); System.out.println( "请登录:" ); System.out.println( "请输入用户名:" ); String user = sc.nextLine(); System.out.println( "请输入密码:" ); String pwd = sc.nextLine(); login( user, pwd ); } private static void login (String user, String pwd) { Connection conn = null ; Statement stat = null ; ResultSet rs = null ; try { conn = JdbcUtil.getConn(); stat = conn.createStatement(); String sql = "select * from user where username='" +user+"' and password='" +pwd+"'" ; rs = stat.executeQuery(sql); System.out.println( sql ); if ( rs.next() ) { System.out.println("恭喜您登录成功!" ); }else { System.out.println("登录失败, 用户名或密码不正确!" ); } } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.close(conn, stat, rs); } }
执行时,输入:
1 2 3 4 5 6 7 请登录: 请输入用户名: 张飞'#' 请输入密码: select * from user where username='张飞'#'' and password='' 恭喜您登录成功了!
或输入 :
1 2 3 4 5 6 7 请登录: 请输入用户名: 张飞' or '1=1 请输入密码: select * from user where username='张飞' or '1=1' and password='' 恭喜您登录成功了!
或输入 :
1 2 3 4 5 6 7 请登录: 请输入用户名: 请输入密码: ' or '2=2 select * from user where username='' and password='' or '2=2' 恭喜您登录成功了!
SQL注入攻击 通过上面的案例,我们发现在执行时,不输入密码只输入用户名也可以登陆成功。这就是SQL注入攻击。
SQL注入攻击产生的原因: 由于后台执行的SQL语句是拼接而来的:
1 select * from user where username='"+user+"' and password='"+pwd+"'
其中的参数是用户提交过来的,如果用户在提交参数时,在参数中掺杂了一些SQL关键字(比如or)或者特殊符号(#、– 、’ 等),就可能会导致SQL语句语义的变化,从而执行一些意外的操作(用户名或密码不正确也能登录成功)!
防止SQL注入攻击 如何防止SQL注入攻击?
(1)使用正则表达式对用户提交的参数进行校验。如果参数中有(# – ‘ or等)这些符号就直接结束程序,通知用户输入的参数不合法
(2)使用PreparedStatement对象来替代Statement对象。
下面通过第二种方式解决SQL注入攻击:添加loginByPreparedSatement方法,在方法中,使用PreparedStatement来代替Statement作为传输器对象使用,代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private static void login (String user, String pwd) { Connection conn = null ; PreparedStatement ps = null ; ResultSet rs = null ; try { conn = JdbcUtil.getConn(); String sql = "select * from user where username=? and password=?" ; ps = conn.prepareStatement( sql ); ps.setString( 1 , user ); ps.setString( 2 , pwd ); rs = ps.executeQuery(); if ( rs.next() ) { System.out.println("恭喜您登录成功!" ); }else { System.out.println("登录失败, 用户名或密码不正确!" ); } } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.close(conn, ps, rs); } }
再次执行程序,按照上面的操作登录。此时,已经成功的防止了SQL注入攻击问题了。
PreparedStatement对象是如何防止SQL注入攻击的:
使用PreparedStatement对象是先将SQL语句的骨架发送给服务器编译,编译之后SQL语句的骨架和语义就不会再被改变了,再将SQL语句中的参数发送给服务器,即使参数中再包含SQL关键字或者特殊符号,也不会导致SQL的骨架或语义被改变,只会被当作普通的文本来处理!
使用PreparedStatement对象可以防止SQL注入攻击
而且通过方法设置参数更加的方便且不易出错!
还可以从某些方面提高程序执行的效率!
单元测试补充: 加了@Test注解的方法,可以通过单元测试(junit)框架测试该方法。底层会创建该方法所在类的实例,通过实例调用该方法
1 2 3 4 @Test public void testInsert () { System.out.println("TestPreparedStatement.testInsert()" ); }
能够使用@Test单元测试测试的方法必须满足如下几个条件:
1 2 3 4 (1)方法必须是公共的 (2)方法必须是非静态的 (3)方法必须是无返回值的 (4)方法必须是无参数的
数据库连接池 什么是连接池 池:指内存中的一片空间(容器,比如数组、集合)
连接池:就是将连接存放在容器中,供整个程序共享,可以实现连接的复用,减少连接创建和关闭的次数,从而提高程序执行的效率!
d30da01edb7ccc007425c91328373339
为什么要使用连接池 1、传统方式操作数据库
ed6ceb2908a9c2a66c3336a651056ce0
1 2 3 Connection conn = DriverManager.getConnection( url, user, pwd ); //创建连接对象 conn.close(); //关闭连接, 销毁连接
在传统方式中,每次用户需要连接访问数据库时,都是创建一个连接对象,基于这个连接对象访问数据库,用完连接后,会将连接关闭(conn.close)。
由于每次创建连接和关闭连接非常的耗时间而且耗资源因此效率低下。
2、使用连接池操作数据库
b7566731966b48e3d918bbd205d30030
可以在程序一启动时,就创建一批连接放在一个连接池中(容器),当用户需要连接时,就从连接池中获取一个连接对象,用完连接后,不要关闭,而是将连接再还回连接池中,这样一来,用来用去都是池中的这一批连接,实现了连接的服用,减少了连接创建和关闭的次数,从而提高了程序执行的效率!
如何使用C3P0连接池 dbcp/c3p0/druid
使用C3P0连接池开发步骤:
1、导入开发包
d9837a8227847f3e22aa28a9e5bf6aa5
2、创建数据库连接池(对象)
1 ComboPooledDataSource cpds = new ComboPooledDataSource();
3、设置连接数据库的基本信息
(1)方式一:(不推荐) 直接将参数通过 pool.setXxx方法设置给c3p0程序
这种方式直接将参数写死在了程序中,后期一旦参数发生变化,就要修改程序,要重新编译项目、重新发布项目,非常麻烦。
1 2 3 4 5 pool.setDriverClass( "com.mysql.jdbc.Driver" ); pool.setJdbcUrl( "jdbc:mysql:///jt_db?characterEncoding=utf-8" ); pool.setUser( "root" ); pool.setPassword( "root" );
(2)方式二:将连接参数提取到properties文件中(推荐)
在类目录下(开发时可以放在src或者类似的源码目录下),添加一个c3p0.properties文件,配置内容如下:
1 2 3 4 c3p0.driverClass =com.mysql.jdbc.Driver c3p0.jdbcUrl =jdbc:mysql:///jt_db?characterEncoding=utf-8 c3p0.user =root c3p0.password =root
这种方式由于是c3p0到指定的位置下寻找指定名称的properties文件,所以文件的位置必须是放在src或其他源码根目录下,文件名必须是c3p0.properties。
(3)方式三:(推荐)
在类目录下(开发时可以放在src或者类似的源码目录下),添加一个c3p0-config.xml文件,配置内容如下:
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8"?> <c3p0-config > <default-config > <property name ="driverClass" > com.mysql.jdbc.Driver</property > <property name ="jdbcUrl" > jdbc:mysql:///jt_db?characterEncoding=utf-8</property > <property name ="user" > root</property > <property name ="password" > root</property > </default-config > </c3p0-config >
这种方式由于是c3p0到指定的位置下寻找指定名称的xml文件,所以文件的位置必须是放在src或其他源码根目录下,文件名必须是c3p0-config.xml。
4、从连接池中获取一个连接对象并进行使用
1 Connection conn = pool.getConnection();
5、用完连接后将连接还回连接池中
1 2 3 4 5 6 JdbcUtil.*close*(conn, ps, rs);
扩展:切换工作空间,修改默认编码 切换新的工作空间,设置工作空间编码为utf-8 (1)切换到新的工作空间目的: 若旧的工作空间内容过多,可能会导致eclipse不编译,甚至进入休眠状态。
(2)设置工作空间编码为utf-8,此后在当前工作空间下创建的项目编码也默认是utf-8。
eabc894e645afd2837f6fdbcd735eb89
设置JSP文件的编码为utf-8
8c92de12e98e8b773abbb41133baef4e
扩展:JDBC实现学生信息管理系统 准备数据 建库建表语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 create database if not exists jt_db charset utf8;use jt_db; drop table if exists stu;create table stu( id int , name varchar (50 ), gender char (2 ), addr varchar (50 ), score double ); insert into stu values (1001 ,'张三' ,'男' , '北京' , 86 );
功能实现 运行程序控制台提示如下:
4b9ad722dca6065b01a5202f85e2d89c
输入a:查询所有学生信息
输入b:添加学生信息
输入c:根据id修改学生信息
输入d:根据id删除学生信息
查询所有学生信息 在控制台中输入操作代码”a”,效果如下:
55a1ab054846de0d92525b6292f7b930
添加学生信息 在控制台中输入操作代码”b”,效果如下:
c9f2e18f2a41668cd39ddaccd712290c
根据id修改学生信息 在控制台中输入操作代码”c”,效果如下:
40de75c8a4f08fc4a4b581b358be6a87
根据id删除学生信息 在控制台中输入操作代码”d”,效果如下:
9eda8bae1797eced35a1e4907009db83