3天掌握JDBC基础知识【复习笔记】
1 JDBC介绍
- JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
- JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
- JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统(Mysql、Oracle),这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
- 总结如下:
2 获取数据库的连接
2.1 要素一:Driver接口实现类
2.1.1 Driver接口介绍
在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。
- Oracle的驱动:oracle.jdbc.driver.OracleDriver
- mySql的驱动:com.mysql.jdbc.Driver
2.1.2 加载和注册JDBC驱动
- 加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名。
Class.forName(“com.mysql.jdbc.Driver”);
- 注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序
DriverManager.registerDriver(com.mysql.jdbc.Driver)
2.2 要素二:URL
2.3 要素三:用户名和密码
user,password可以用“属性名=属性值”方式告诉数据库
2.4 连接方式举例
@Test
public void testConnection5() throws Exception {
//1.加载配置文件
InputStream is =
ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
//2.读取配置信息
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);
}
3 使用PreparedStatement实现CRUD操作
使用Statement操作数据表存在弊端:
- 问题一:存在拼串操作,繁琐
- 问题二:存在SQL注入问题
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ =‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法。
3.1 PreparedStatement的使用
- 可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象。
- PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句。
3.1.1 使用Ps完成通用的增、删、改操作:
// 通用的增、删、改操作(体现一:增、删、改;体现二:针对于不同的表)
public void Update(String sql, Object ...args){
Connection conn = null;
PreparedStatement ps = null;
try {
// 1.获取数据库的连接
conn = JDBCUtils.getConnection(); // JDBCUtils里面封装了与数据库的连接操作和对资源的关闭操作
// 2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
// 3.填充占位符
for (int i = 0; i < args.length; i++){
ps.setObject(i+1, args[i]);
}
// 4.SQL的执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.资源的关闭
JDBCUtils.closeResource(conn,ps);
}
}
3.1.2 使用ps完成通用的查询操作
- 针对于不用表的通用查询操作,返回一个对象:
// 针对于不同表的通用查询操作,返回一个对象
public <T> T queryForAll(Class<T> clazz, String sql, Object ...args){
//泛型方法
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.建立与数据库的连接
conn = JDBCUtils.getConnection1();
// 2.预编译SQL, 得到PrepareStatement对象
ps = conn.prepareStatement(sql);
// 3.填充占位符
for (int i = 0; i < args.length; i++){
ps.setObject(i+1, args[i]);
}
// 4.执行,并返回结果集: ResultSet
rs = ps.executeQuery();
// 5.1 通过rsmd得到columnCount, columnLabel;通过rs得到列值
ResultSetMetaData rsmd = rs.getMetaData(); // 获取结果集的元数据
int columnCount = rsmd.getColumnCount(); // 获取结果集中的列数
if (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++){
Object columnValue = rs.getObject(i+1); // 获取每个列值
String columnLabel = rsmd.getColumnLabel(i+1); // 获取每个列名
// 5.2 通过反射给book对象指定的columnLabel属性, 赋值为columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
- 针对于不同表的通用查询操作,返回多个对象:
// 针对于不同表的通用查询操作,返回多个对象
public <T> List<T> queryForList(Class<T> clazz, String sql, Object ...args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.建立与数据库的连接
conn = JDBCUtils.getConnection();
// 2.预编译SQL
ps = conn.prepareStatement(sql);
// 3.填充占位符
for (int i = 0; i < args.length; i++){
ps.setObject(i+1, args[i]);
}
// 4.执行,并返回结果集: ResultSet
rs = ps.executeQuery();
// 5.1 通过rsmd得到columnCount, columnLabel;通过rs得到列值
ResultSetMetaData rsmd = rs.getMetaData(); // 获取结果集的元数据
int columnCount = rsmd.getColumnCount(); // 获取结果集中的列数
ArrayList<T> arrayList = new ArrayList<T>();
while (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++){
Object columnValue = rs.getObject(i+1); // 获取每个列值
String columnLabel = rsmd.getColumnLabel(i+1); // 获取每个列名
// 5.2 通过反射给book对象指定的columnLabel属性, 赋值为columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
arrayList.add(t);
}
return arrayList;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.资源的关闭
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
3.2 JDBC API小结
-
两种思想
- 面向接口编程的思想
- ORM思想(object relational mapping)
- 一个数据表对应一个java类
- 表中的一条记录对应java类的一个对象
- 表中的一个字段对应java类的一个属性
-
两种技术
- JDBC结果集的元数据:ResultSetMetaData
- 获取列数:getColumnCount()
- 获取列的别名:getColumnLabel()
- 通过反射,(1)创建对应的运行时类的对象(2)获取运行时类的属性并赋值
- JDBC结果集的元数据:ResultSetMetaData
4 批量执行SQL语句
- addBatch(String):添加需要批量处理的SQL语句或是参数;
- executeBatch():执行批量处理语句;
- clearBatch():清空缓存的数据
举例:
@Test
public void batchInsert() throws Exception {
Connection conn = JDBCUtils.getConnection();
conn.setAutoCommit(false); // 1. 设置为不自动提交数据
String sql = "INSERT INTO goods(g_name) VALUES(?)";
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < 20000; i++){
ps.setString(1,"name_" + i);
// 2. 攒sql
ps.addBatch();
if (i % 500 == 0){
// 3. 执行
ps.executeBatch();
// 4. 清空
ps.clearBatch();
}
}
conn.commit();
JDBCUtils.closeResource(conn,ps);
}
}
5 数据库事务
5.1 JDBC事务处理
- JDBC程序中为了让多个 SQL 语句作为一个事务执行:
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
5.2 事务的ACID属性
- 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
- 隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
6 DAO及相关实现类
6.1 DAO概述及举例
DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
作用:为了实现功能的模块化,更有利于代码的维护和升级。
举例如下:
BaseDao:抽象类,封装了针对于数据表的通用操作,比如增删改的通用操作、查询的通用操作等。
BooksDao:接口,此接口用于规范针对于Books表的常用操作。
BooksDaoImpl:与BooksDao成对出现,定义针对于Books表的具体的操作;继承抽象类BaseDao,实现接口BooksDao。
BooksDaoImplTest:针对于BooksDaoImpl类中实现的方法进行单元测试。
6.2 考虑到事务以后的数据库操作
7 数据库连接池
7.1 JDBC数据库连接池的必要性
-
在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接
- 进行sql操作
- 断开数据库连接
-
这种开发模式,存在的问题如下:
- 数据库的连接资源并没有得到很好的重复利用。 若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,甚至会造成服务器的崩溃。
- 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃
7.2 数据库连接池技术
- 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先放入一定数量的连接,需要的时候取出一个,用完再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中连接数来设定的。
7.3 Druid数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现。它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。
- DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池。
- DataSource 用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
注意:
- 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
- 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
将Druid数据库连接池相关操作定义在JDBCUtils里面:
private static DataSource dataSource;
static {
// 连接池获取一次即可,因此不应该放入某一个类中(每次都要创建一个类的实例),而是直接放在静态代码块中
try {
// 加载配置文件druid.properties,避免写死在代码中
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
// 获取Druid数据库连接池
dataSource = DruidDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
Druid数据库连接池应用举例:
public class DruidTest {
@Test
public void getConnection() throws Exception {
// 加载配置文件druid.properties
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
// 创建Druid数据库连接池,并获取连接
DataSource dataSource = DruidDataSourceFactory.createDataSource(pros);
Connection conn = dataSource.getConnection();
System.out.println(conn);
}
}
其中,src下的配置文件为:【druid.properties】
url=jdbc:mysql://localhost:3306/test01_library
username=root
password=root
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
8 Apache-DBUtils实现CRUD操作
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
8.1 DbUtils
DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。
DbUtils类提供了三个重载的关闭方法close()。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
public static void close(…) throws java.sql.SQLException
8.2 QueryRunner类
该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
QueryRunner类的主要方法
- 增、删、改操作:用来执行一个更新(插入、更新或删除)操作。
public int update(Connection conn, String sql, Object... params) throws SQLException
- 查询:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException:
- 批处理:INSERT,UPDATE, or DELETE语句的批处理操作
public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException
举例:针对一条记录的插入操作
@Test // 针对一条记录的插入操作
public void testInsert(){
Connection conn = null;
try {
QueryRunner query = new QueryRunner();
conn = JDBCUtils.getConnection1();
String sql = "INSERT INTO books(id,name,authors) VALUES(?,?,?)";
int insertCount = query.update(conn, sql, 11, "sheep", "richard");
System.out.println("添加了" + insertCount + "条记录");
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
8.3 ResultSetHandler接口及实现类
-
该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
-
接口的主要实现类:
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- ScalarHandler:查询单个值对象
-
举例:针对多条记录的查询操作
@Test // 针对多条记录的查询操作
public void testQuery2(){
List<Books> list = null;
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection1(); //利用Druid数据库连接池获取连接
String sql = "SELECT * FROM books WHERE id=?";
// BeanListHandler是ResultSetHandler接口的实现类,用于封装类中的多条记录构成的集合
BeanListHandler<Books> handlerBooks = new BeanListHandler<>(Books.class);
list = runner.query(conn, sql, handlerBooks, 9);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 打印集合中的每一个元素
list.forEach(System.out::println);
JDBCUtils.closeResource(conn,null);
}
}