第一章 JDBC概述 我们学习了数据库管理软件MySQL,可以方便的管理数据。
那么如何将它俩结合起来呢?即Java程序<==>MySQL,实现数据的存储和处理。
答案:使用JDBC技术,后期可以使用MyBatis等持久层框架(底层仍然使用了JDBC)。
1. JDBC概述 JDBC:Java Database Connectivity,它是代表一组独立于任何数据库管理系统(DBMS)的API,声明在java.sql与javax.sql包中,是SUN(现在Oracle)提供的一组接口规范。由各个数据库厂商来提供实现类,这些实现类的集合构成了数据库驱动jar。
即JDBC技术包含两个部分:
(1)java.sql包和javax.sql包中的API
(2)各个数据库厂商提供的jar
1、之前学习了Java,又学习了MySQL数据库,JDBC 把 Java程序 和 MySQL数据库连起来,Java程序是负责数据的输入,业务的处理,数据的显示,MySQL负责数据的存储和管理。
2、JDBC:Java Database Connectivity
JDBC = JDK核心类库中的一套API(接口+部分工具类) + 数据库厂商提供的驱动jar
Java程序不仅仅能够连接MySQL数据库,可以连接很多数据库(Oracle,SQL Server,DB2,…)。
这就有一个问题:
数据库不同,它们的操作方式会有所不同,因为它们的底层实现方式,实现的语言等都是不同的。那么Java去连接不同的数据库时,就会有不同的API。这样的话,就会导致:
(1)程序员的学习成本增加
(2)如果发生数据库迁移,Java代码就需要“重写”
如果是这样的话,就非常麻烦,可移植性、可维护性等非常差。SUN公司(现在Oracle)就说,必须统一一套API,可以操作各种数据库,但是SUN公司又不同知道所有数据库内部是如何实现的,也无法要求所有的数据库厂商按照统一的标准来开发他们的数据库软件。SUN公司(现在Oracle)就设计了一套接口 + 部分类。然后各个数据库厂商,来提供这些接口的实现类。
==>Java程序中面向接口编程,在程序运行时,又需要引入这些接口的实现类,这些实现类就是数据库驱动jar。
2. Java程序连接MySQL数据库 1 .引入mysql驱动jar
方式一:单独某个模块使用mysql驱动 1 2 3 4 5 (1)在模块路径下建一个文件夹“jdbclibs”,把mysql的驱动jar放到里面 MySQL5.7:mysql-connector-java-5.1.36-bin.jar MySQL8.0:mysql-connector-java-8.0.19.jar (2)在jdbclibs文件夹上右键-->Add as Library... (3)填写库名称、选择这个库应用范围(模块)-->选择使用的具体模块
方式二:项目下多个模块使用mysql驱动 1 2 3 4 5 6 (1)在项目路径下建一个文件夹“jdbclibrary”,把mysql的驱动jar放到里面 mysql-connector-java-5.1.36-bin.jar MySQL8.0:mysql-connector-java-8.0.19.jar (2)项目设置-->libraries--> + ->java-->文件夹“jdbclibs” (3)选择需要这个jar的模块 项目设置-->modules-->模块名-->dependencies--> + - >library->Java -> 库
后期其他模块也要使用mysql驱动,可以直接添加项目的jdbclibrary库即可:
方式三:使用Maven仓库 2. Java代码连接MySQL数据库的步骤 1、模块添加了依赖的mysql驱动相关库 2、在内存中加载驱动类(可选) 最近版本:com.mysql.jdbc.Driver MySQL8.0版本:com.mysql.cj.jdbc.Driver
新版的mysql驱动jar可以省略这步,旧版的mysql驱动jar必须加这一步。 后期使用数据库连接池,或者MyBatis等框架时,在配置文件中加这个驱动类的配置即可 Class.forName(“com.mysql.cj.jdbc.Driver”);
3、连接数据库:通过DriverManager工具类获取数据库连接Connection的对象。 此时的Java程序是MySQL的一个客户端 连接数据库: MySQL服务器主机的IP地址: 端口号 用户名 密码
//Connection ==> 网络编程的Socket
String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";
Connection conn = DriverManager.getConnection(url, "root", "123456");
MySQL8使用时,url需要加参数:serverTimezone=UTC,否则会报错
4、断开连接:使用close方法。
数据库连接完整代码
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 import java.sql.Connection;import java.sql.DriverManager;public class TestConn { public static void main (String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver" ); String url = "jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC" ; String user = "root" ; String passwd = "root" ; Connection conn = DriverManager.getConnection(url, user, passwd); System.out.println("mysql连接成功:conn = " + conn); conn.close(); } }
3 .实现增删改查 预先创建了一个表结构
mysql> desc jdbctest; +——–+———–+——+—–+———+——-+ | Field | Type | Null | Key | Default | Extra | +——–+———–+——+—–+———+——-+ | id | int | YES | | NULL | | | name | char(255) | YES | | NULL | | | passwd | char(255) | YES | | NULL | | +——–+———–+——+—–+———+——-+
步骤: 1、模块添加依赖的mysql驱动相关库 2、在内存中加载驱动类(可选) 3、连接数据库 4、操作数据库
通过Statement或PreparedStatement对象执行SQL:
Statement用于执行静态SQL语句,而PreparedStatement用于执行预编译的SQL语句,通常对于执行多次相似的语句更有效。
(1)通过Connection对象获取Statement或PreparedStatement对象
(2)通过Statement或PreparedStatement对象执行sql
执行增、删、改,使用 int executeUpate()方法,接收执行条数。
执行查询,使用ResultSet executeQuery()方法,接收返回结果集。
5、释放资源(close)
1 .添加数据完整代码 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 package com.penghu.jdbc.part1;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.Statement;public class TestC { public static void main (String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver" ); String url = "jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC" ; String user = "root" ; String passwd = "root" ; Connection conn = DriverManager.getConnection(url, user, passwd); Statement statement = conn.createStatement(); int row1 = statement.executeUpdate("insert into jdbctest values (1,'张三','abc123')" ); String sql = "insert into jdbctest values (2,'李四','abc123')" ; PreparedStatement preparedStatement = conn.prepareStatement(sql); int row2 = preparedStatement.executeUpdate(); System.out.println(row1 > 0 ? "Statement对象执行SQL添加成功" : "添加失败" ); System.out.println(row2 > 0 ? "PreparedStatement对象执行SQL添加成功" : "添加失败" ); statement.close(); preparedStatement.close(); conn.close(); } }
2 .修改数据 1 2 3 4 String sql = "update jdbctest set name ='张三改名' where name='张三'" ;PreparedStatement pst = conn.prepareStatement(sql);int len = pst.executeUpdate();
3. 删除数据 1 2 3 4 String sql = "delete from jdbctest where name='张三改名' || name= '李四'" ;PreparedStatement pst = conn.prepareStatement(sql);int len = pst.executeUpdate();
4. 特殊的,查询数据 对于查询结果: 遍历结果集的方法: boolean next():判断是否还有下一行 getString(字段名或序号),getInt(字段名或序号),getObject(字段名或序号)
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 package com.penghu.jdbc.part1;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;public class TestR { public static void main (String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver" ); String url = "jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC" ; String user = "root" ; String passwd = "root" ; Connection conn = DriverManager.getConnection(url, user, passwd); String sql = "select * from jdbctest where name='张三'" ; PreparedStatement pst = conn.prepareStatement(sql); ResultSet resultSet = pst.executeQuery(); while (resultSet.next()){ String uname = resultSet.getString("name" ); String upasswd = resultSet.getString("passwd" ); System.out.println(uname+"的密码是" +upasswd); } resultSet.close(); pst.close(); conn.close(); } }
第二章 轻松处理各种问题 1. sql拼接问题 1.准备代码和sql 2.问题演示代码 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 49 50 51 52 53 54 55 56 57 58 package com.penghu.jdbc.part2;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;public class TestSQLConcat { public static void main (String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver" ); String url = "jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC" ; String user = "root" ; String passwd = "root" ; Connection conn = DriverManager.getConnection(url, user, passwd); String sql = "insert into jdbctest(id,name,passwd) values (?,?,?)" ; PreparedStatement pst = conn.prepareStatement(sql); pst.setInt(1 , 3 ); pst.setString(2 , "王五" ); pst.setString(3 , "王五的密码" ); String sql2 = "insert into jdbctest(id,name,passwd) values (?,?,?)" ; PreparedStatement pst2 = conn.prepareStatement(sql); pst2.setObject(1 , 4 ); pst2.setObject(2 , "赵六" ); pst2.setObject(3 , "赵六的密码" ); pst.executeUpdate(); pst2.executeUpdate(); Object getObject (字段名) Object getObject (字段的序号) pst.close(); pst2.close(); conn.close(); } }
2. 避免sql注入问题 1.准备代码和sql 2.问题演示代码 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 package com.penghu.jdbc.part2;import org.junit.Test;import java.sql.*;import java.util.Scanner;public class TestSQLInject { public static void main (String[] args) throws Exception { Scanner input = new Scanner (System.in); System.out.print("请输入你要查询的用户编号:" ); String id = input.nextLine(); String url = "jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC" ; String user = "root" ; String passwd = "root" ; Connection conn = DriverManager.getConnection(url, user, passwd); String sql = "select * from jdbctest where id = " + id; System.out.println("sql = " + sql); PreparedStatement pst = conn.prepareStatement(sql); ResultSet rs = pst.executeQuery(); while (rs.next()) { for (int i = 1 ; i < 3 ; i++) { Object object = rs.getObject(i); if (object instanceof Integer) { System.out.print((Integer) object + " " ); } else { System.out.print(object + " " ); } } } rs.close(); pst.close(); conn.close(); input.close(); } }
3 .二进制类型数据赋值 1.准备代码和sql 2.问题演示代码 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 49 ........ String sql = "insert into t_user values(null,?,?,?)" ; PreparedStatement pst = connection.prepareStatement(sql); pst.setObject(1 , username); pst.setObject(2 , password); pst.setObject(3 , new FileInputStream (path)); int len = pst.executeUpdate(); System.out.println(len >0 ? "添加成功" : "添加失败" ); pst.close(); connection.close(); input.close();
4 .获取自增长键值 1.准备代码和sql 2.问题演示代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 String sql = "insert into jdbctest(id,name,passwd) values (?,?,?)" ;PreparedStatement pst = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);pst.setObject(1 , 0 ); pst.setObject(2 , "冯七" ); pst.setObject(3 , "冯七的密码" ); pst.executeUpdate(); ResultSet generatedKeys = pst.getGeneratedKeys();if (generatedKeys.next()) { System.out.println("自增id值" + generatedKeys.getObject(1 )); }
5. 批处理 1.准备代码和sql问题演示代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 String sql = "insert into jdbctest(id,name,passwd) values (?,?,?)" ;PreparedStatement pst = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);for (int i = 1 ; i <= 1000 ; i++) { pst.setObject(1 , 0 ); pst.setObject(2 , "冯七" ); pst.setObject(3 , "冯七的密码" ); pst.addBatch(); } pst.executeBatch();
6 .事务处理 1.准备代码和sql 1 2 3 4 5 6 7 8 演示: update t_department set description = 'xx' where did = 2; update t_department set description = 'yy' where did = 3; 故意把其中一条sql语句写错。 update t_department set description = 'xx' where did = 2; update t_department set description = 'yy' what did = 3; #what是错误的
2.问题演示代码 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 49 50 51 52 53 ........ connection.setAutoCommit(false ); String s1 = "update t_department set description = 'xx' where did = 2" ; String s2 = "update t_department set description = 'yy' what did = 3" ; try (PreparedStatement p1 = connection.prepareStatement(s1); PreparedStatement p2 = connection.prepareStatement(s2);) { p1.executeUpdate(); p2.executeUpdate(); System.out.println("两条更新成功" ); connection.commit(); }catch (SQLException e){ e.printStackTrace(); System.out.println("失败" ); connection.rollback(); }finally { connection.close(); } } }
第三章 数据库连接池 1.什么是数据库连池 连接对象的缓冲区。负责申请,分配管理,释放连接的操作。
2.为什么要使用数据库连接池 (1)不使用数据库连接池,每次都通过DriverManager获取新连接,用完直接抛弃断开,连接的利用率太低,太浪费。 (2)对于数据库服务器来说,压力太大了。我们数据库服务器和Java程序对连接数也无法控制,很容易导致数据库服务器崩溃。
我们就希望能管理连接。
我们可以建立一个连接池,这个池中可以容纳一定数量的连接对象,一开始,我们可以先替用户先创建好一些连接对象,等用户要拿连接对象时,就直接从池中拿,不用新建了,这样也可以节省时间。然后用户用完后,放回去,别人可以接着用。
可以提高连接的使用率。当池中的现有的连接都用完了,那么连接池可以向服务器申请新的连接放到池中。 直到池中的连接达到“最大连接数”,就不能在申请新的连接了,如果没有拿到连接的用户只能等待。
3.市面上有很多现成的数据库连接池技术
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
DBCP 是Apache提供的数据库连接池,速度相对c3p0较快 ,但因自身存在BUG,Hibernate3已不再提供支持
C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
BoneCP 是一个开源组织提供的数据库连接池,速度快
Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池
4、如何使用德鲁伊数据库连接池 1.步骤 (1)引入jar包 和引入mysql驱动jar方式一样 (2)编写配置文件 src下加一个druid.properties文件
2.druid.properties文件 1 2 3 4 5 6 7 driverClassName =com.mysql.cj.jdbc.Driver url =jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC username =root password =root initialSize =5 maxActive =10 maxWait =1000
配置
缺省
说明
name
配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
jdbcUrl
连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username
连接数据库的用户名
password
连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName
根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize
0
初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive
8
最大连接池数量
maxIdle
8
已经不再使用,配置了也没效果
minIdle
最小连接池数量
maxWait
获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements
false
是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements
-1
要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery
用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrow
true
申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn
false
归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle
false
建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis
有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun
不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls
物理连接初始化的时候执行的sql
exceptionSorter
根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters
属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters
类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
(3)创建数据库连接池对象 (4)获取连接
3.使用代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class TestPool { public static void main (String[] args) throws Exception { Properties pro = new Properties (); pro.load(TestPool.class.getClassLoader().getResourceAsStream("druid.properties" )); DataSource ds = DruidDataSourceFactory.createDataSource(pro); Connection conn = ds.getConnection(); System.out.println("使用数据库连接池连接成功:conn = " + conn); conn.close(); } }