第一章 JDBC概述
1.1 数据持久化
- 持久化:把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘加以“固化”,而持久化的实现过程大多通过各种关系行数据库来完成
- 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、xml数据文件中
1.2 JDBC介绍
- Java DataBase Connectivity Java数据库连接,Java语言操作数据库:
- JDBC:是一个独立于特定数据库管理系统、通用的SQL 数据库存储和操作的公共接口(一组API),使用这个类库可以以一种标准的方法,方便的访问数据库资源
- JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统。这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程
- JDBC的本质:其实是官方(sun)公司定义的一套操作所有关系型数据库的规则,即接口。各大数据库厂商去实现这套接口,提供数据库驱动jar包。我们就可以使用这套接口(JDBC)编程。真正的代码是驱动Jar包的实现类
第二章:获取数据库连接
2.1 要素一:Driver接口实现类
2.1.1 Driver接口介绍
- java.sql.Driver接口是所有JDBC驱动程序需要实现的接口,这个接口是提供给数据库厂商使用的。不同的厂商提供不同的实现
- 在程序中,由驱动程序管理类(java.sql.DriverMangager)去调用这些Driver实现
- Oracle的驱动:oracle.jdbc.driver.OralceDriver
- mySql的驱动:com.mysql.jdbc.Driver
- 使用驱动必须加载jar包
- 上述jar包拷贝到Java工程的一个目录中,习惯上新建一个lib文件夹。
- 在驱动jar上右键–>Build Path–>Add to Build Path
- 注意:如果是Dynamic Web Project(动态的web项目)话,则是把驱动jar放到WebContent(有的开发工具叫
WebRoot)目录中的WEB-INF目录中的lib目录下即可
2.1.2 加载与注册JDBC驱动
- 加载驱动:加载JDBC驱动需调用Class类的静态方法forName(),向其传递要加载的JDBC驱动的类名
- Class.forName(“com.mysql.jdbc.Driver”);
- 注册驱动:DriverManager类是驱动程序管理器类,负责管理驱动程序
- 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动
- 通常不需要用这个方法来注册驱动,因为在Driver接口的驱动程序类的静态代码块中会调用这个方法来注册实例
2.2 要素二:URL
- JDBC URL用于标识一个被注册的驱动程序,驱动程序管理器通过这个URL选择正确的驱动程序,从而建立数据库的连接
- JDBC URL的标准是由三部分组成,各部分间用冒号隔开
- 三部分:协议:子协议:子名称
- 协议:JDBC URL中的协议总是jdbc
- 子协议:子协议用于标识一个数据库驱动程序
环+ 子名称:一种标识数据库的方法,子名称可以以不同的子协议而变化,用子名称的目的视为了定位数据库提供足够的信息。包含主机名(对应服务器端的ip地址),端口号,数据库名
2.2.1 几种常见的JDBC URL
- MySQL的连接URL编写方式
- jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
- jdbc:mysql://localhost:3306/atguigu
- jdbc:mysql://localhost:3306/atguigu?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
2.3 要素三:用户名和密码
- user,password可以用“属性名=属性值”方式告诉数据库
- 可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接
2.4 连接方式举例
2.4.1 连接方式一
@Test
public void testConnection4() {
try {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "abc123";
String driverName = "com.mysql.jdbc.Driver";
//2.加载驱动 (①实例化Driver ②注册驱动)
Class.forName(driverName);
//Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
//DriverManager.registerDriver(driver);
/*
可以注释掉上述代码的原因,是因为在mysql的Driver类中声明有:
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
2.4.2 连接方式2:
@Test
public void testConnection5() throws Exception {
//1.加载配置文件
//class.getClassLoader():获取当前的类加载器
//getResourceAsStream(String name):通过名称查找资源
InputStream is =
ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
//Properties的load把文件读取到集合中使用
pros.load(is);
//2.读取配置信息
//getProperty:读取配置文件中的属性值
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//3.加载驱动
Class.forName(driverClass);
//4.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
其中配置文件声明在工程的src目录下:jdbc.properties
user=root
password=abc123
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
使用配置文件的好处:1.实现了代码和数据的分离,如果需要修改配置文件,直接就可以在配置文件中修改,不需要深入代码。2.如果修改了配置信息,省去重新编译的过程
2.5 编写JdbcUtils类
//工具类:jdbcUtile
package test;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
private static String user;
private static String password;
private static String url;
private static String driverClass;
static{
try {
InputStream inp = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(inp);
user=props.getProperty("user");
password=props.getProperty("password");
url=props.getProperty("url");
driverClass=props.getProperty("driverClass");
Class.forName(driverClass);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
public static void close(Statement stam,Connection cont){
if(stam!=null) {
try {
stam.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (cont!=null) {
try {
cont.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(PreparedStatement pstr, Connection cont){
if(pstr!=null) {
try {
pstr.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (cont!=null) {
try {
cont.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//测试
@Test
public void Test1() throws SQLException {
Connection cont = JdbcUtils.getConnection();
System.out.println(cont);
JdbcUtils.close(null,cont);
}
第三章 使用PreparedStatement实现CRUD操作
3.1 操作和访问数据库
- 数据库连接用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果。其实一个数据库连接就是Socket连接
- 在java.sql包中有3个接口分别定义了对数据库调用的不同方式
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
3.2使用Statement操作数据库的弊端
- 通过调用 Connection 对象的createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。
- Statement 接口中定义了下列方法用于执行 SQL 语句:
- int executeUpdate(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 就可
以了。
3.3 PreparedStatemet的使用
3.3.1 PreparedStatem介绍
- 可以通过调用Connection对象的preparedStatement(String sql)方法来获取PrepareStatement对象
- PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
- PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用PreparedStatement 对象的setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1开始),第二个是设置的 SQL 语句中的参数的值
3.3.2 PreparedStatement vs Statement
- 代码的可读性和可维护性
- PreparedStatement 能最大可能提高性能:
- DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参
数直接传入编译过的语句执行代码中就会得到执行。- PreparedStatement 可以防止 SQL 注入
3.3.3 使用PrparedStatement实现增、删、改操作
package test;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PreparedStatementTest {
@Test
//修改
public void update() throws SQLException {
//获取数据库连接
Connection conn =JdbcUtils.getConnection();
//预编译sql语句
String sql="update user set name='小李'where password=?";
//获取PreparedStatement对象
PreparedStatement pr = conn.prepareStatement(sql);
//填充参数
pr.setInt(1,456);
//执行SQL语句
pr.execute();
//释放连接
JdbcUtils.close(pr,conn);
}
@Test
//查询
public void select() throws SQLException {
//获取数据库连接
Connection conn =JdbcUtils.getConnection();
//预编译sql语句
String sql="select * from user";
//获取PreparedStatement对象
PreparedStatement pr = conn.prepareStatement(sql);
//填充参数
//执行SQL语句
ResultSet resultSet =pr.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString(1));
}
//释放连接
JdbcUtils.close(pr,conn);
}
}
通用的操作
package test;
import Utils.JdbcUtils;
import dao.User;
import org.junit.Test;
import java.sql.*;
public class preparedStatementTest {
@Test
public void selectTest(){
String sql="select * from user";
select(User.class,sql,null);
}
@Test
public void updateTest(){
String sql="update user set name='张杰' where id=?";
update(sql,5);
}
//通用的增删改查操作
public void update(String sql,Object... args){
Connection con=null;
PreparedStatement pstm=null;
try {
con=JdbcUtils.getConnection();
pstm=con.prepareStatement(sql);
if (args!=null) {
for (int i = 0; i < args.length; i++) {
pstm.setObject(i + 1, args[i]);
}
}
pstm.execute();
JdbcUtils.close(con,pstm);
} catch (SQLException e) {
e.printStackTrace();
}
}
//通用查询操作 针对不同的表
public <T> T select(Class<T> clazz,String sql,Object... args){
Connection con=null;
PreparedStatement pstm=null;
try {
con=JdbcUtils.getConnection();
pstm=con.prepareStatement(sql);
if (args!=null) {
for (int i = 0; i < args.length; i++) {
pstm.setObject(i + 1, args[i]);
}
}
ResultSet resultSet = pstm.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int count = metaData.getColumnCount();
while (resultSet.next())
for(int i=0;i<count;i++){
System.out.println(resultSet.getObject(i+1));
}
JdbcUtils.close(con,pstm);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
3.4 ResultSet与ResultSetMetaData
3.4.1 ResultSet
- 查询调用PreparedStatement的executeQuery()方法,查询结果是一个ResultSet对象
- ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面
- ResultSet的next()方法可以将指针移动到下一行,并且检测下一行是否有数据,若有数据返回true。
- 可以调用getXXX(int xxx)或getXXX(String name)获取每一列的值
- 注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
代码实例
String sql="select * from user";
//获取PreparedStatement对象
PreparedStatement pr = conn.prepareStatement(sql);
//填充参数
//执行SQL语句;获取结果集对象
ResultSet resultSet =pr.executeQuery();
//指向下一列,并判断是否有数据
while (resultSet.next()){
//获取数据
`System.out.println(resultSet.getString(1));
}
3.4.2 ResultSetMetaData
- 可用于获取关于ResultSet对象列的类型和属性信息的对象
- 使用ResultSet对象的getMetaData()方法创建ResultSetMetaData对象
- 相关方法
- getColumnName(int column):获取指定列的名称
- getColumnLabel(int column):获取指定列的别名
- getColumnCount():返回当前 ResultSet 对象中的列数。
- getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
- getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
- isNullable(int column):指示指定列中的值是否可以为 null。
- isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
代码实例
package test;
import org.junit.Test;
import java.sql.*;
public class ResultSetMetaDataTest {
@Test
public void Result(){
try {
Connection conn = JdbcUtils.getConnection();
String sql="select * from departments";
PreparedStatement pstm = conn.prepareStatement(sql);
ResultSet res = pstm.executeQuery();
ResultSetMetaData meda = res.getMetaData();
//获取结果中有多少列
Integer column=meda.getColumnCount();
//输出结果的全部数据
while (res.next()){
for (int i=1;i<=column;i++){
System.out.print(res.getString(i));
System.out.print("\t");
}
System.out.print("\n");
}
// System.out.println(meda.getColumnCount());
// System.out.println(meda.getColumnName(1));
// JdbcUtils.close(conn,pstm);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.5 资源的释放
- 释放:ResultSet,Statement,Connection
- 数据库连接(Connection)是非常稀有的资源,用完必须马上释放,如果Connection不能正确的释放资源会导致系统宕机(卡),释放原则是尽量晚创建,尽量早释放
第四章:数据库事务
4.1 数据库事务介绍
- 事务:一组逻辑操作单元,使数据一种状态变换到另一种状态。
- 事务处理(事务操作)::保证所有的事务都作为一种工作单元来执行,当一个事务中执行多个操作的时候,要么所有的事务都被提交(commit),那么这些修改将永久的保存下来,要么 数据库管理系统将放弃所做的所有修改,整个事务回滚(rollback)到最初状态。简单来说就是要么一起成功,要么一起失败
- 为确保数据库中的数据一致性,数据操作应当是离散的成组的逻辑单元;当它全部完成时,数据一致性可以保持。而当这个单元一部分操作失败,整个事务应全部失败。所有从起始点的操作应全部回退到开始状态
4.2 JDBC事务处理
- 数据一旦提交,就不可回滚。
- 数据什么时候意味着提交?
- 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
- 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下
- DBC程序中为了让多个 SQL 语句作为一个事务执行:
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
@Test
public void updateMoney() {
try {
Connection conn = JdbcUtils.getConnection();
//取消数据库连接的自动提交
conn.setAutoCommit(false);
//sql语句
String sql = "update user set money=money-100 where name=?";
String sql1 = "update user set money=money+100 where name=?";
PreparedStatement p1 = conn.prepareStatement(sql);
p1.setString(1, "小文");
p1.execute();
//由于有事务提交,哪怕上面正常成功执行,可也不会改变数据
//模拟错误
System.out.println(10 / 0);
PreparedStatement pro = conn.prepareStatement(sql1);
pro.setString(1, "小李");
pro.execute();
conn.commit();
JdbcUtils.close(conn, p1);
JdbcUtils.close(conn, pro);
} catch (SQLException e) {
e.printStackTrace();
}
}
4.3 事务的ACID属性
- 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
- 隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,
4.3.1
- 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行
- 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
- 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
4.3.2 四种隔离级别
隔离级别 | 描述 |
---|---|
READ UNCOMMITTED(未提交数据) | 允许事务读取未被其它事务提交的变更。脏读、不可重复读和幻读的问题都会出现 |
READ COMMTED(读已提交数据) | 只允许事务读取已经被其它事务提交的变更。可以避免脏读但不可重复读和幻读的问题都会出现 |
REPEATABLE READ(可重复读) | 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其它事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在 |
SERIALZABLE(串行化 |
System.out.println(conn.getTransactionIsolation());
//设置数据库的隔离级别:
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
- Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READCOMMITED 。
- Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。
4.3.3 在MySql中设置隔离级别
- 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation,表示当前的事务隔离级别。
- 查看当前的隔离级别:
SELECT @@tx_isolation;
- 设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;
- 设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
第五章 数据库连接池
- 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接
- 进行sql操作
- 断开数据库连接
- 这种模式开发,存在的问题:
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
- **这种开发不能控制被创建的连接对象数,**系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
5.2 数据库连接池技术
- 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
- 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
5.3 数据库连接池技术的优点
- 资源重用
- 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
- 更快的系统反应速度
- 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均
已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,
从而减少了系统的响应时间- 新的资源分配手段
- 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
- 统一的连接管理,避免数据库连接泄漏
- 在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
5.4 多种开源的数据库连接池
- JDBC 的数据库连接池使用javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
- DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP 是一个开源组织提供的数据库连接池,速度快
- Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
- DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
- DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
- 特别注意:
- 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
- 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
5.4.1 C3P0数据库池
- 获取连接方式1
@Test
public void testC3P0() throws PropertyVetoException, SQLException {
Connection connection = getConnection();
System.out.println(connection);
connection.close();
}
public static Connection getConnection() throws PropertyVetoException, SQLException {
ComboPooledDataSource comp = new ComboPooledDataSource();
comp.setDriverClass("com.mysql.jdbc.Driver");
comp.setJdbcUrl("jdbc:mysql://localhost:3306/test");
comp.setUser("root");
comp.setPassword("root");
Connection conn = comp.getConnection();
return conn;
}
- 获取连接方式2
private static DataSource ds=new ComboPooledDataSource("hello");
public static Connection getConnection2() throws SQLException{
Connection conn = ds.getConnection();
return conn;
}
<?xml version='1.0' encoding='UTF-8'?>
<c3p0-config>
<named-config name="hello">
<!--获取连接的四个消息-->
<property name="user">root</property>
<property name="password">root</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!--当连接数不够的时候,一次向服务器申请多少个连接数-->
<property name="acquireIncrement">5</property>
<!--初始化数据库连接数量-->
<property name="initialPoolSize">root</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">10</property>
<!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
5.4.1 Druid(德鲁伊) 数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。
package com.atguigu.druid;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class TestDruid {
public static void main(String[] args) throws Exception {
Properties pro = new Properties();
pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall
第六章: QueryRunner类与esultSetHandler接口及实现类
6.1 QueryRuner类
- 该类简化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
- QueryRunner类提供了两个构造器:
- 默认的构造器
- 需要一个 javax.sql.DataSource 来作参数的构造器
- 主要方法
- 更新:public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
- 插入:public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值
- 批处理:
- public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句
- public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句
- 查询:public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句 的置换参数。该方法会自行处理 PreparedStatement 和ResultSet 的创建和关闭。
// 测试添加
@Test
public void testInsert() throws Exception {
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
String sql = "insert into customers(name,email,birth)values(?,?,?)";
int count = runner.update(conn, sql, "何成飞", "he@qq.com", "1992-09-08");
System.out.println("添加了" + count + "条记录");
JDBCUtils.closeResource(conn, null);
}
6.2 ResultSetHandler
- 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
- ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
- 接口的主要实现类:
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
ScalarHandler:查询单个值对象
6.3 简单使用
- user类
package com.damin;
public class User {
private Integer id;
private String name;
private String password;
private String address;
private String phone;
public User(Integer id, String name, String password, String address, String phone) {
this.id = id;
this.name = name;
this.password = password;
this.address = address;
this.phone = phone;
}
public User(){}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
- Durid连接池
username=root
password=root
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
initialSize=20
maxActive=20
package com.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static DruidDataSource dataSource=null;
static {
try {
InputStream inp= JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(inp);
dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(prop);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static void close(Connection con){
if (con!=null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- queryRunner操作
package com.queryRunner;
import com.damin.User;
import com.utils.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.Test;
import javax.management.Query;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class queryTest1 {
//创建queryRunner类用于进行操作
QueryRunner qr=new QueryRunner();
@Test
//修改一条数据
public void update(){
try {
//获取数据库连接
Connection conn = JDBCUtils.getConnection();
//编写sql语句
String sql="update user set name='小王' where id=?";
//调用方法执行语句
qr.update(conn,sql,1);
JDBCUtils.close(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
//查询一条数据
@Test
public void queryForOne() throws SQLException {
Connection con = JDBCUtils.getConnection();
String sql="select * from user where name=?";
User user = qr.query(con, sql, new BeanHandler<>(User.class), "张杰");
System.out.println(user);
JDBCUtils.close(con);
}
//查询多条记录
@Test
public void queryForList() throws SQLException {
Connection con = JDBCUtils.getConnection();
String sql="select * from user";
List<User> user = qr.query(con, sql, new BeanListHandler<>(User.class));
for (User u:user){
System.out.println(u);
}
}
}