Java Data Base Connectivity:Java数据库连接,JDBC是Java操作数据库的规范
通过JDBC可以让Java操作数据库
直接写代码操作数据库 直接写代码操作数据库存在的问题:
不知道MySQL数据库的操作方式,解析方式代码繁琐,写起来麻烦MySQL和Oracle等其他数据库的操作方式和解析方式不同,每个数据库都要写一套代码MySQL和Oracle等其他数据库相互切换麻烦JDBC规范定义接口,具体的实现由各大数据库厂商来实现 JDBC是Java访问数据库的标准规范。真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用JDBC接口中的方法即可。数据库驱动由数据库厂商提供。
说出JDBC的概念?
JAVA数据库连接,JDBC是Java操作数据库的规范(接口)说出JDBC的作用?
通过JDBC可以让Java操作数据库学习JDBC四个核心对象
这几个类都是在java.sql包中
DriverManager: 用于注册驱动Connection: 表示数据库的连接Statement: 执行SQL语句的对象ResultSet: 结果集或一张虚拟表JDBC四个核心对象?
DriverManager:注册驱动
Connection:Java程序和数据库之间的连接(桥梁)
Statement:执行SQL语句的对象(小货车)
ResultSet:结果集,保存查询到的数据
我们Java程序需要通过数据库驱动才能连接到数据库,因此需要注册驱动。
学习导入mysql驱动Jar包
学习JDBC注册数据库驱动
在注册驱动前需要先导入驱动的Jar包
我们Java程序需要通过数据库驱动才能连接到数据库,因此需要注册驱动。 MySQL的驱动的入口类是:com.mysql.jdbc.Driver
java.sql.DriverManager类用于注册驱动。提供如下方法注册驱动
static void registerDriver(Driver driver) 向 DriverManager 注册给定驱动程序。1.DriverManager.registerDriver(驱动对象); 传入对应参数即可
通过查询com.mysql.jdbc.Driver源码,我们发现Driver类“主动”将自己进行注册
public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { // 自己自动注册 java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } public Driver() throws SQLException { } }注意:使用DriverManager.registerDriver(new com.mysql.jdbc.Driver());,存在两方面不足
硬编码,后期不易于程序扩展和维护驱动被注册两次使用Class.forName("com.mysql.jdbc.Driver");加载驱动,这样驱动只会注册一次
public class Demo01 { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); // 后期可以将"com.mysql.jdbc.Driver"字符串写在文件中. } }演示:Class.forName("包名.类名");会走这个类的静态代码块
通常开发我们使用Class.forName() 加载驱动。Class.forName("com.mysql.jdbc.Driver");会走Driver类的静态代码块。在静态代码块中注册一次驱动。
总结:注册MySQL驱动使用Class.forName("com.mysql.jdbc.Driver");
导入mysql驱动Jar包
通过JDBC注册数据库驱动?
Class.forName("com.mysql.jdbc.Driver");Connection表示Java程序与数据库之间的连接,只有拿到Connection才能操作数据库。
学习JDBC获取数据库连接
学习获取Statement对象
java.sql.DriverManager类中有如下方法获取数据库连接
static Connection getConnection(String url, String user, String password) 连接到给定数据库 URL ,并返回连接。连接数据库的URL地址格式:协议名:子协议://服务器名或IP地址:端口号/数据库名 MySQL写法:jdbc:mysql://localhost:3306/day16 如果是本地服务器,端口号是默认的3306,则可以简写:jdbc:mysql:///day16
如果数据出现乱码需要加上参数: ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据。 如: jdbc:mysql://localhost:3306/day16?characterEncoding=utf8
1.连接成功 2.连接失败
JDBC获取数据库连接使用哪个API?
DriverManager类中的 Connection getConnection(数据库URL, 数据库账号, 数据库密码);通过JDBC连接mysql的URL写法?
jdbc:mysql://主机名:端口号/数据库名学习JDBC实现对单表数据增、删、改
我们要对数据库进行增、删、改、查,需要使用Statement对象来执行SQL语句。
获取Statement对象
在java.sql.Connection接口中有如下方法获取到Statement对象
Statement createStatement() 创建一个 Statement 对象来将 SQL 语句发送到数据库Statement的API介绍
boolean execute(String sql) 用执行任何SQL语句,如果是查询返回true,如果不是查询语句返回false; 通常不用 int executeUpdate(String sql) 用于执行增删改等语句; 返回影响的行数 ResultSet executeQuery(String sql) 用于执行查询语句; 返回查询到的结果集executeQuery:用于执行查询SQL
executeUpdate:用于执行除查询外的SQL
JDBC实现增删改的步骤
注册驱动获取连接获取小货车执行SQL语句关闭资源Statement对象的用于执行除查询外的SQL语句的方法 int executeUpdate(String sql);
JDBC实现对单表数据查询
ResultSet用于保存执行查询SQL语句的结果。 我们不能一次性取出所有的数据,需要一行一行的取出。
其实ResultSet获取数据的API是有规律的get后面加数据类型。我们统称getXXX()
从ResultSet中能一次取出所有数据吗?
不能,一次只能处理一行
如何通过ResultSet取数据
while (rs.next()) { 获取这行数据 }学习JDBC操作事务
之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作银行转账的事务。
Connection接口中与事务有关的方法
void setAutoCommit(boolean autoCommit) throws SQLException; false:开启事务, ture:关闭事务 void commit() throws SQLException; 提交事务 void rollback() throws SQLException; 回滚事务JDBC中与事务相关的API?
Connection接口中的方法。
setAutoCommit(false); 开启事务commit(); 提交事务rollback(); 回滚事务编写JDBC获取连接与关闭连接工具类
通过上面案例需求我们会发现每次去执行SQL语句都需要注册驱动,获取连接,得到Statement,以及释放资源。发现很多重复的劳动,我们可以将重复的代码定义到某个类的方法中。直接调用方法,可以简化代码。 那么我们接下来定义一个JDBCUtils类。把注册驱动,获取连接,得到Statement,以及释放资源的代码放到这个类的方法中。以后直接调用方法即可。
JDBCUtils.java
public class JDBCUtils { // 1.将固定字符串定义为常量 private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql:///day24"; private static final String USER = "root"; private static final String PASSWORD = "root"; // 2.在静态代码块中注册驱动(只注册一次) // 当这个类加载到内存的时候就走这个静态代码块,再去触发Driver类中的静态代码块,主动注册 static { try { Class.forName(DRIVER_CLASS); } catch (ClassNotFoundException e) {} } // 3.提供一个获取连接的方法static Connection getConneciton(); // 我们面向JDBC编程 public static Connection getConnection() throws SQLException { InputStream is = JDBCUtils.class.getResourceAsStream("/jdbc.properties"); Properties pp = new Properties(); pp.load(is); Connection conn = DriverManager.getConnection(URL, pp); return conn; } // 5.重载关闭方法close(Connection conn, Statement stmt) public static void close(Connection conn, Statement stmt) { close(conn, stmt, null); } // 4.定义关闭资源的方法close(Connection conn, Statement stmt, ResultSet rs) public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) {} } if (stmt != null) { try { stmt.close(); } catch (SQLException e) {} } if (conn != null) { try { conn.close(); } catch (SQLException e) {} } } }编写JDBC工具类的目的是什么?
我们发现JDBC操作数据库,代码都是固定的,都是重复的,封装成工具类,让JDBC使用更加简单。
模拟用户输入账号和密码登录网站
输入正确的账号,密码,显示登录成功 输入错误的账号,密码,显示登录失败创建一个用户表保存用户的账号和密码,并添加一些数据,SQL语句如下:
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 Demo07 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入账号: "); String name = sc.nextLine(); System.out.println("请输入密码: "); String password = sc.nextLine(); }使用SQL根据用户的账号和密码去数据库查询数据
public class Demo07 { public static void main(String[] args) throws Exception { // 让用户输入账号和密码 Scanner sc = new Scanner(System.in); System.out.println("请输入账号: "); String name = sc.nextLine(); System.out.println("请输入密码: "); String password = sc.nextLine(); // 使用SQL根据用户的账号和密码去数据库查询数据 Connection conn = JDBCUtils.getConnection(); Statement stmt = conn.createStatement(); String sql = "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';"; } }如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败
public class Demo07 { public static void main(String[] args) throws Exception { // 让用户输入账号和密码 Scanner sc = new Scanner(System.in); System.out.println("请输入账号: "); String name = sc.nextLine(); System.out.println("请输入密码: "); String password = sc.nextLine(); // 使用SQL根据用户的账号和密码去数据库查询数据 Connection conn = JDBCUtils.getConnection(); Statement stmt = conn.createStatement(); String sql = "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';"; // 如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败 ResultSet rs = stmt.executeQuery(sql); if (rs.next()) { //能进来查询到了数据. String name2 = rs.getString("name"); System.out.println("欢迎您," + name2); } else { //查询不到数据,说明登录失败 System.out.println("账号或密码错误..."); } JDBCUtils.close(conn, stmt, rs); } } }登录案例步骤
创建用户表,保存一些用户的数据
让用户输入账号和密码
将输入的账号和密码,拼接一个查询语句,并执行
如果查询到数据,登录成功
如果查询不到数据,登录失败
学习SQL注入的概念
在我们前面JDBC实现登录案例中,当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了
请输入用户名: hehe 请输入密码: a' or '1'='1问题分析:
// 代码中的SQL语句 "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';"; // 将用户输入的账号密码拼接后 "SELECT * FROM user WHERE name='hehe' AND password='a' or '1'='1';"我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。
要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。需要使用PreparedSatement类解决SQL注入。
什么是SQL注入?
用户输入的数据拼接成SQL语句,改变了SQL语句的含义。
了解PreparedSatement的执行原理
继承结构: [外链图片转存失败(img-1bFr4w7d-1564843431162)(/pstmt01.png)] 我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。
Satement对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。 Statement stmt = conn.createStatement(); stmt.executeUpdate("INSERT INTO users VALUES (1, '张三', '123456');"); stmt.executeUpdate("INSERT INTO users VALUES (2, '李四', '666666');");上面2条SQL语句我们可以看到大部分内容是相同的,只是数据略有不一样。数据库每次执行都编译一次。如果有1万条类似的SQL语句,数据库需要编译1万次,执行1万次,显然效率就低了。
prepareStatement()会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。相当于调用方法多次传入不同的参数。 String sql = "INSERT INTO users VALUES (?, ?, ?);"; // 会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。 PreparedStatement pstmt = conn.prepareStatement(sql); // 设置参数 pstmt.setString(1, 1); pstmt.setInt(2, "张三"); pstmt.setString(3, "123456"); pstmt.executeUpdate(); // 再次设置参数 pstmt.setString(1, 2); pstmt.setInt(2, "李四"); pstmt.setString(3, "66666"); pstmt.executeUpdate();上面预编译好一条SQL,2次传入了不同的参数并执行。如果有1万条类似的插入数据的语句。数据库只需要预编译一次,传入1万次不同的参数并执行。减少了SQL语句的编译次数,提高了执行效率。
示意图
PreparedSatement的好处?
提高效率提高安全性提高SQL可读性学习PreparedSatement相应的API
Statement ↑ 继承 PreparedStatement
在java.sql.Connection有获取PreparedSatement对象的方法
PreparedStatement prepareStatement(String sql) 会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。 "SELECT * FROM user WHERE name=? AND password=?;"; 参数化的SQL在java.sql.PreparedStatement中有设置SQL语句参数,和执行参数化的SQL语句的方法
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 数据操作语言DML语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。PreparedSatement如何设置参数
void setXxx(第几个问号, 问号的具体值);PreparedSatement如何执行SQL
ResultSet executeQuery() int executeUpdate()学习PreparedSatement实现增删改
效果:
将id为2的学生地址改成台湾
// 修改数据: 将id为2的学生地址改成台湾 public static void updateEmployee() throws Exception { Connection conn = JDBCUtils.getConnection(); String sql = "UPDATE employee SET address=? WHERE id=?;"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, "台湾"); pstmt.setInt(2, 2); int i = pstmt.executeUpdate(); System.out.println("影响的行数:" + i); JDBCUtils.close(conn, pstmt); }效果:
删除id为2的员工
// 删除数据: 删除id为2的员工 public static void deleteEmployee() throws Exception { Connection conn = JDBCUtils.getConnection(); String sql = "DELETE FROM employee WHERE id=?;"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 2); int i = pstmt.executeUpdate(); System.out.println("影响的行数:" + i); JDBCUtils.close(conn, pstmt); }效果:
PreparedSatement使用步骤?
获取连接编写参数化SQL,带?的SQL获取PreparedStatement设置参数执行SQL释放资源PreparedSatement实现增删改使用哪个方法?
int executeUpdate();不是任何地方都可以写?,字段值不确定的时候才可以?
使用PreparedSatement改写登录案例
输入正确的账号密码: 输入错误的密码:使用PreparedStatement可以解决SQL注入问题
不要拼接SQL语句,有未知内容使用?先占位,然后使用PreparedStatement设置参数,并执行
使用PreparedSatement实现查询数据
查询id小于8的员工信息,并保存到员工类中
查询id小于8的员工信息,并保存到员工类中
效果:
表对应类
一条记录对应一个对象
字段值对应对象的成员变量的值
PreparedSatement实现查询使用哪个方法?
ResultSet executeQuery();
学习元数据的概念
学习ParameterMetaData元数据使用
什么是元数据:数据库、表、列的定义信息。
ParameterMetaData可用于获取有关PreparedStatement对象中每个参数标记的类型和属性。
select * from user where name=? and password=? // ParameterMetaData可以用来获取?的个数和类型通过PreparedStatement的getParameterMetaData()方法来获取到ParameterMetaData对象
不是所有的数据库驱动都能后去到参数类型(MySQL会出异常)
表示参数的元数据,可以得到SQL中?的个数和类型(mysql得不到)
学习ResultSetMetaData元数据
ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。
通过ResultSet的getMetaData()方法来获取到ResultSetMetaData对象
ResultSetMetaData的作用
表示结果集元数据,可以得到结果集的字段数量,字段名称,字段类型
分层的作用:
解耦:降低层与层之间的耦合性。可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能。可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能。学习连接池的原理和好处
之前JDBC访问数据库的步骤: 创建数据库连接 →运行SQL语句→关闭连接 每次数据库访问执行这样重复的动作
每次创建数据库连接的问题
获取数据库连接需要消耗比较多的资源,而每次操作都要重新获取新的连接对象,执行一次操作就把连接关闭,而数据库创建连接通常需要消耗相对较多的资源,创建时间也较长。这样数据库连接对象的使用率低。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出我们现实生活中每日三餐。我们并不会吃一餐饭就将碗丢掉,而是吃完饭后将碗放到碗柜中,下一餐接着使用。目的是重复利用碗,我们的数据库连接也可以重复使用,可以减少数据库连接的创建次数。提高数据库连接对象的使用率。
连接池的概念: 连接池是创建和管理数据库连接的缓冲池技术。连接池就是一个容器,连接池中保存了一些数据库连接,这些连接是可以重复使用的。
连接池中保存了一些数据库连接,这些连接是可以重复使用的。节省数据库的资源消耗。
javax.sql.DataSource表示数据库连接池,是JDK中提供的一个接口,没有具体的实现,它的实现由连接池的厂商去实现。我们只需要学习这个工具如何使用即可。
public interface DataSource { Connection getConnection(); ... }常用的连接池实现组件有以下这些
阿里巴巴-德鲁伊Druid连接池:Druid是阿里巴巴开源平台上的一个项目C3P0是一个开源的连接池,目前使用它的开源项目有Hibernate,Spring等。DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。连接池的好处? 连接池中会保存一些连接,这些连接可以重复使用,降低数据库的资源消耗
连接池的原理?
1.启动连接池时,连接池会初始化一些连接 2.当有人要用连接时,从连接池中取出一个连接 3.当连接使用完毕后,还回连接池中学习C3P0连接池
C3P0地址:https://sourceforge.net/projects/c3p0/?source=navbar C3P0是一个开源的连接池。Hibernate框架,默认推荐使用C3P0作为连接池实现。 C3P0的jar包:c3p0-0.9.1.2.jar
配置文件的要求:
文件名:c3p0-config.xml放在源代码即src目录下配置文件c3p0-config.xml
<c3p0-config> <!-- 使用默认的配置读取连接池对象 --> <default-config> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">10</property> <property name="checkoutTimeout">2000</property> <property name="maxIdleTime">1000</property> </default-config> <named-config name="itheimac3p0"> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">15</property> <property name="checkoutTimeout">2000</property> <property name="maxIdleTime">1000</property> </named-config> </c3p0-config>com.mchange.v2.c3p0.ComboPooledDataSource类表示C3P0的连接池对象,常用2种创建连接池的方式:
1.无参构造,使用默认配置
2.有参构造,使用命名配置
public ComboPooledDataSource() 无参构造使用默认配置(使用xml中default-config标签中对应的参数) public ComboPooledDataSource(String configName) 有参构造使用命名配置(configName:xml中配置的名称,使用xml中named-config标签中对应的参数) public Connection getConnection() throws SQLException 从连接池中取出一个连接C3P0配置文件名称必须为c3p0-config.xml C3P0命名配置可以有多个
准备数据
CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20), age INT, score DOUBLE DEFAULT 0.0 );配置文件
<c3p0-config> <!-- 使用默认的配置读取连接池对象 --> <default-config> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">10</property> <property name="checkoutTimeout">2000</property> <property name="maxIdleTime">1000</property> </default-config> <named-config name="itheimac3p0"> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">15</property> <property name="checkoutTimeout">2000</property> <property name="maxIdleTime">1000</property> </named-config> </c3p0-config>java代码
public class Demo01 { public static void main(String[] args) throws Exception { // 方式一: 使用默认配置(default-config) // new ComboPooledDataSource(); // ComboPooledDataSource ds = new ComboPooledDataSource(); // 方式二: 使用命名配置(named-config:配置名) // new ComboPooledDataSource("配置名"); ComboPooledDataSource ds = new ComboPooledDataSource("otherc3p0"); // for (int i = 0; i < 10; i++) { // Connection conn = ds.getConnection(); // System.out.println(conn); // } // 从连接池中取出连接 Connection conn = ds.getConnection(); // 执行SQL语句 String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, "张三"); pstmt.setInt(2, 25); pstmt.setDouble(3, 99.5); int i = pstmt.executeUpdate(); System.out.println("影响的行数: " + i); pstmt.close(); conn.close(); // 将连接还回连接池中 } }正常获取连接池中连接
获取连接池中连接超时
使用连接池中的连接往数据库添加数据
注意:配置文件名称必须为:c3p0-config.xml,将配置文件放在src目录下
只需要单独修改配置文件,不用修改代码 多个配置的好处:
可以连接不同的数据库:db1,db2可以使用不同的连接池参数:maxPoolSize可以连接不同厂商的数据库:Oracle或MySQLCP30使用步骤?
导入Jar包:c3p0-0.9.5.2.jar,mchange-commons-java-0.2.12.jar复制c3p0-config.xml,修改参数c3p0-config.xml放到src里面在代码中创建C3P0连接池对象,CombopooledDataSource对象从连接池中获取连接执行SQL语句将连接还回连接池中C3P0常用参数?
initialPoolSize启动连接池时,初始化的连接数量maxPoolSize连接池最大的连接数量checkoutTimeout连接池没有连接时,最大的等待时间,单位是毫秒maxIdleTime连接池中的连接空闲多久会被销毁,默认是0,0表示不销毁学习Druid连接池的使用
DRUID连接池使用的jar包:druid-1.0.9.jar
API介绍
com.alibaba.druid.pool.DruidDataSourceFactory类有创建连接池的方法
public static DataSource createDataSource(Properties properties) 创建一个连接池,连接池的参数使用properties中的数据我们可以看到Druid连接池在创建的时候需要一个Properties对象来设置参数,所以我们使用properties文件来保存对应的参数。 Druid连接池的配置文件名称随便,放到src目录下面方便加载 druid.properties文件内容:
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/day17 username=root password=root initialSize=5 maxActive=10 maxWait=3000导入druid的jar包
在src目录下创建一个properties文件,并设置对应参数
加载properties文件的内容到Properties对象中
创建Druid连接池,使用配置文件中的参数
从Druid连接池中取出连接
执行SQL语句
关闭资源
java代码
public class Demo04 { public static void main(String[] args) throws Exception { // 加载配置文件中的配置参数 InputStream is = Demo04.class.getResourceAsStream("/druid.properties"); Properties pp = new Properties(); pp.load(is); // 创建连接池,使用配置文件中的参数 DataSource ds = DruidDataSourceFactory.createDataSource(pp); // for (int i = 0; i < 10; i++) { // Connection conn = ds.getConnection(); // System.out.println(conn); // } // 从连接池中取出连接 Connection conn = ds.getConnection(); // 执行SQL语句 String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, "王五"); pstmt.setInt(2, 35); pstmt.setDouble(3, 88.5); int i = pstmt.executeUpdate(); System.out.println("影响的行数: " + i); // 执行查询 sql = "SELECT * FROM student;"; ResultSet rs = pstmt.executeQuery(sql); while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); int age = rs.getInt("age"); double score = rs.getDouble("score"); System.out.println("id: " + id + " ,name: " + name + " ,age = " + age + " ,score = " + score); } pstmt.close(); conn.close(); // 将连接还回连接池中 } }Druid使用步骤?
导入Druid的Jar包复制druid.properties到src下,并修改配置在代码中加载properties到Properties对象中创建Druid连接池从Druid连接池中取出连接执行SQL关闭Druid常用的配置参数
参数说明initialSize刚启动连接池时,初始化连接的数量maxActive连接池中最大的连接数量maxWait没有连接时,最大的等待时间Druid连接池基本使用不管是C3P0连接池,配置大致都可以分为2种:1.连接数据库的参数,2.连接池的参数,这2种配置大致参数作用都相同,只是参数名称可能不一样。
编写一个连接池的工具类,简化连接池的使用
DataSourceUtils.java
public class DataSourceUtils { // 1.声明静态数据源成员变量 private static DataSource ds; // 2.创建连接池对象 static { // 加载配置文件中的数据 InputStream is = DataSourceUtils.class.getResourceAsStream("/druid.properties"); Properties pp = new Properties(); try { pp.load(is); // 创建连接池,使用配置文件中的参数 ds = DruidDataSourceFactory.createDataSource(pp); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } // 3. 定义得到数据源的方法 public static DataSource getDataSource() { return ds; } // 4. 定义得到连接对象的方法 public static Connection getConnection() throws SQLException { return ds.getConnection(); } // 5.定义关闭资源的方法 public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) {} } if (stmt != null) { try { stmt.close(); } catch (SQLException e) {} } if (conn != null) { try { conn.close(); } catch (SQLException e) {} } } // 6.重载关闭方法 public static void close(Connection conn, Statement stmt) { close(conn, stmt, null); } }测试类代码
public class Demo03 { public static void main(String[] args) throws Exception { // 拿到连接 Connection conn = DataSourceUtils.getConnection(); // 执行sql语句 String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, "李四"); pstmt.setInt(2, 30); pstmt.setDouble(3, 50); int i = pstmt.executeUpdate(); System.out.println("影响的函数: " + i); // 关闭资源 DataSourceUtils.close(conn, pstmt); } }使用连接池工具类后可以简化代码,我们重点是写SQL去执行。
编写连接池工具类步骤
定义了静态的连接池成员变量在静态代码块中创建连接池对象定义一个方法返回连接池定义一个方法返回连接定义close方法,关闭资源能够理解JDBC的概念
Java数据库连接,是Java操作数据库的规范
能够使用Connection接⼝
Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root"); Statement stmt = conn.createStatement();能够使用Statement接⼝
执行SQL语句的对象,相当于小货车 Statement stmt = conn.createStatement(); int i = stmt.executeUpdate(sql); ResultSet rs = stmt.executeQuery(sql);能够使用ResultSet接⼝
ResultSet接⼝: boolean next(); 将游标向下移动一行,如果有数据返回true,没有数据返回false Xxx getXxx(); 得到字段的值 ResultSet rs = stmt.executeQuery("SELECT * FROM category;"); // 5.ResultSet处理结果 while (rs.next()) { int cid = rs.getInt(1); String cname = rs.getString(2); System.out.println(cid + "::" + cname); }能够使用JDBC实现对单表数据增、删、改、查
// 1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root"); // 3.获取小货车 Statement stmt = conn.createStatement(); // 4.执行SQL语句 // 添加数据 // String sql = "INSERT INTO category (cname) VALUES ('情趣用品');"; // 修改数据 // String sql = "UPDATE category SET cname='计生用品' WHERE cid=2;"; // 删除数据 String sql = "DELETE FROM category WHERE cid=3;"; // int executeUpdate(String sql) int i = stmt.executeUpdate(sql); System.out.println("影响的行数: " + i); // 5.关闭资源 stmt.close(); conn.close();能够使用JDBC操作事务
操作事务的方法在Connection中 void setAutoCommit(false): 开启事务 void commit(); 提交事务 void rollback(); 回滚事务 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:///day16", "root", "root"); // 3.开启事务 // false: 表示关闭自动提交,开始事务 conn.setAutoCommit(false); // 4.获取到Statement stmt = conn.createStatement(); // 5.使用Statement执行SQL // 张三-500,李四+500 stmt.executeUpdate("UPDATE account SET balance = balance - 500 WHERE id=1;"); // 模拟出问题啦 int a = 10 / 0; stmt.executeUpdate("UPDATE account SET balance = balance + 500 WHERE id=2;"); // 没有出问题就提交事务 System.out.println("提交事务"); conn.commit(); } catch (Exception e) { // 6.提交或回滚事务 System.out.println("出问题啦,回滚事务"); try { conn.rollback(); } catch (SQLException ex) { System.out.println("回滚失败"); } } finally { // 7.关闭资源 if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } } }能够完成JDBC实现登录案例
核心去数据中查询是否有用户输入账号和密码 public static void main(String[] args) throws SQLException { // 1.使用数据库保存用户的账号和密码 // 2.让用户输入账号和密码 Scanner sc = new Scanner(System.in); System.out.println("请输入账号:"); String name = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); // 3.使用SQL根据用户的账号和密码去数据库查询数据 String sql = "SELECT * FROM user WHERE name='" + name+ "' AND password='" + password + "';"; // SELECT * FROM user WHERE name='nba' AND password='a' or '1'='1'; System.out.println(sql); Connection conn = JDBCUtils.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); if (rs.next()) { // 查到用户 // 4.如果查询到数据,说明登录成功 System.out.println("恭喜您, " + name + "登录成功!"); } else { // 没有查到用户 // 5.如果查询不到数据,说明登录失败 System.out.println("用户名或密码错误"); } }