JDBC对数据库的操作有很多是固定格式,故可以将固定操作分离出来形成工具类,方便调用,提高开发效率。
PreparedStatement下形成的CRUD通用程序
两种思想面向接口的编程思想
ORM编程思想
两种技术元数据 + 反射
- CRUD
Create(增加)、Retrieve(查询)、Update(修改)、Delete(删除)
作用数据库连接被用于向数据库发送命令和SQL语句,并接收数据库服务器返回的结果。
java.sql包中的3个接口Statement:用于执行静态SQL语句并返回它所生成结果的对象(已弃用)
PreparedStatement:
CallableStatement:
JDBC编写步骤与执行流程- 作用
Statement接口:它的作用就是向数据库中发送一条SQL语句,对数据库进行操作
Statement对象:通过Connection接口的实例对象调用方法实现
问题一:存在拼串操作,繁琐String sql = "SELECT user,password FROM user_table WHERe user = '" + user + "'AND password = '" + password + "'";问题二:存在SQL注入问题
String sql = "SELECt user,password FROM user_table WHERe user = '1' or' AND password = '=' 1' or '1' = '1'";被弃用
由于Statement接口的各种问题,所以它的功能被它的子接口PreparedStatement接口替代
- 作用
和Statement接口作用一样,向数据库中发送SQL语句
PreparedStatement对象:通过Connection接口的实例对象调用prepareStatement()方法得到
具体实现public class PreparedStatementTest {
@Test
public void testPreparedStatement1() {
InputStream is = null;
Connection conn = null;
PreparedStatement pst = null;
try {
// 1.提供数据库连接的四个基本信息
is = PreparedStatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties ps = new Properties();
ps.load(is);
String url = ps.getProperty("url");
String user = ps.getProperty("user");
String password = ps.getProperty("password");
String driver = ps.getProperty("driver");
// 2.注册驱动
Class.forName(driver);
// 3.创建连接
conn = DriverManager.getConnection(url, user, password);
// 4.预编译sql语言,并通过调用Connection对象的prepareStatement方法生成一个PrepareStatement对象
String sql = "insert into customers(name, email, birth) values(?,?,?)";
pst = conn.prepareStatement(sql);
// 5.填充占位符,使用PreparedStatement对象操作
pst.setString(1,"哪吒");
pst.setString(2,"nezha@163.com");
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
TemporalAccessor date_n = formatter.parse("1000-01-01");
pst.setString(3,formatter.format(date_n));
// 6.执行“增”操作
pst.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7.关闭资源
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (pst != null) {
pst.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
优化程序,新建一个JDBC基本操作的工具类,将连接和关闭的固定操作分别放在两个方法中 package jdbcutils;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
public static Connection getConnection() throws Exception {
// 1.提供数据库连接的四个基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties ps = new Properties();
ps.load(is);
String url = ps.getProperty("url");
String user = ps.getProperty("user");
String password = ps.getProperty("password");
String driver = ps.getProperty("driver");
// 2.注册驱动
Class.forName(driver);
// 3.创建连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
public static void closeResource(Connection conn, Statement ps) {
try {
if (ps != null) {
ps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
优化后的修改操作 @Test
public void testUpdate() {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1.获取数据库的连接
conn = JdbcUtils.getConnection();
// 2.预编译sql语句,生成PreparedStatement对象
String sql = "update customers set name = ? where id = ?";
ps = conn.prepareStatement(sql);
// 3.填充占位符,使用通用方法setObject()
ps.setObject(1,"二郎神");
ps.setObject(2,1);
// 4.执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.关闭资源
JdbcUtils.closeResource(conn,ps);
}
}
根据上述迭代,规范出一个使用于某数据库下不同表的 - - 通用增删改方法
// 增删改通用方法,提取了变化的sql语句作为参数1和将填充占位符的操作作为可变参数
@Test
public void commonUpdate(String sql, Object ...args) {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1.获取数据库的连接
conn = JdbcUtils.getConnection();
// 2.预编译sql语句,生成PreparedStatement对象
ps = conn.prepareStatement(sql);
// 3.利用循环及通用方法setObject(),填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
// 4.执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.关闭资源
JdbcUtils.closeResource(conn,ps);
}
}
// 测试通用方法
@Test
public void testCommonUpdate() {
String sql = "update customers set name = ? where id = ?";
commonUpdate(sql,"如来佛",1);
}
- 查询操作的特点
查询需要返回一个结果集,故执行时使用executeQuery( )方法,返回一个ResultSet对象,从而对结果集进行进一步操作。
处理结果集通过ResultSet类中的next( )方法可以实现迭代器作用,从而逐条处理结果集中的记录
next( )方法的作用① 判断下一条记录是否有数据
② 并将指针下移,指向下一条数据
ORM编程思想(Object relational mapping)将查询后的结果集中的数据存储在类的对象中,通过对类的反射操作可实现通用性。
一个数据表对应一个Java类
表中的一条记录对应java类的一个对象
表中的一个列对应Java类中的一个属性
JavaBean存放用于映射查询后的结果集的类
JavaBean 是一种用Java语言写成的可重用组件。为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。
JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性,set和get方法获取。众所周知,属性名称符合这种模式,其他Java 类可以通过自省机制(反射机制)发现和操作这些JavaBean 的属性。
Java与SQL对应数据类型转换表| Java类型 | SQL类型 |
|---|---|
| boolean | BIT |
| byte | TINYINT |
| short | SMALLINT |
| int | INTEGER |
| long | BIGINT |
| String | CHAR、VARCHAR、LONGVARCHAR |
| byte array | BINARY、VARBINARY |
| java.sql.Date | DATE |
| java.sql.Time | TIME |
| java.sql.Timestamp | TIMESTAMP |
从3到6,是实现查询并输出结果集的必备知识,形成通用方法则需要解决下面问题
① sql:查询语句的列的类型及数目不固定,故结果集中的列因不同的sql也不固定
② 如何将不固定的字段存储到JavaBean中对应类的对象的属性中
反射 + 元数据元数据:动态获取结果集中的列的信息(ResultSetmetaData),如
① 列的个数:getColumnCount( )
② 列的别名:getColumnLabel( )
反射:动态操作结果集在JavaBean中对应类的属性,如获取属性名,给属性赋值
参考博文:Java五十九: 注解 Annotation,Java六十九: 反射
针对某一具体的表的查询,且其查询结果集中只有一条记录的通用查询方法@Test
public Customers testCommonRetrieve(String sql, Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.建立连接
conn = JdbcUtils.getConnection();
// 2.预编译sql,生成PreparedStatement对象
ps = conn.prepareStatement(sql);
// 3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
// 4.执行并返回结果集
rs = ps.executeQuery();
// 5.处理结果集
// 5.1 获取该结果集的元数据,通过元数据获取结果集中数据的信息
ResultSetmetaData rsmd = rs.getmetaData();
// 5.2 获取结果集中列的个数
int columnCount = rsmd.getColumnCount();
// 5.3 处理结果集中的一行数据
if (rs.next()) {
// 5.4 处理该行记录的每一个字段
// 5.4.1 创建该行记录在JavaBean中对应类的对象
Customers cust = new Customers();
// 5.4.2 利用通过元素据获得的结果集中列的个数,结合for循环对结果集中的列进行遍历
for (int i = 0; i < columnCount; i++) {
// 5.4.3 通过结果集对象获取该字段的值
Object columnValue = rs.getObject(i + 1);
// 5.4.4 通过元数据获取该字段的别(列)名,该列名与该行记录对应的对象中的属性名相同
String columnLabel = rsmd.getColumnLabel(i+1);
// 5.4.5 通过反射,给Customers对象中对应的属性赋值,实现将结果集中的数据存储到对象中
Field field = Customers.class.getDeclaredField(columnLabel);
// 确保私有属性可以访问
field.setAccessible(true);
// 给该字段对应的属性赋值
field.set(cust,columnValue);
}
return cust;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.关闭资源
JdbcUtils.closeResource(conn,ps,rs);
}
return null;
}
// 测试方法
@Test
public void test1() {
// 将sql语句中的列名更换为与bean文件夹中对应类的属性名相同的别名,特别注意:where后面的条件查询仍然用列名,不能用别名
String sql = "select cust_id id,cust_name name,cust_email ,birth from customers where cust_id = ?";
Customers customers = testCommonRetrieve(sql, 4);
System.out.println(customers);
}
特别注意:where后面的条件查询仍然用列名,不能用别名
针对不同表,且其查询结果集中只有一条记录的通用查询方法使用泛型解决不同表对应bean文件夹中不同类的问题
@Test
public T testCommonRetrieve1(Class clazz, String sql, Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.建立连接
conn = JdbcUtils.getConnection();
// 2.预编译sql,生成PreparedStatement对象
ps = conn.prepareStatement(sql);
// 3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
// 4.执行并返回结果集
rs = ps.executeQuery();
// 5.处理结果集
// 5.1 获取该结果集的元数据,通过元数据获取结果集中数据的信息
ResultSetmetaData rsmd = rs.getmetaData();
// 5.2 获取结果集中列的个数
int columnCount = rsmd.getColumnCount();
// 5.3 处理结果集中的一行数据
if (rs.next()) {
// 5.4 处理该行记录的每一个字段
// 5.4.1 创建该行记录在JavaBean中类的对象,此处用泛型实现
T t = clazz.newInstance();
// 5.4.2 利用通过元素据获得的结果集中列的个数,结合for循环对结果集中的列进行遍历
for (int i = 0; i < columnCount; i++) {
// 5.4.3 通过结果集对象获取该字段的值
Object columnValue = rs.getObject(i + 1);
// 5.4.4 通过元素据获取该字段的别(列)名,该列名与该行记录对应的对象中的属性名相同
String columnLabel = rsmd.getColumnLabel(i+1);
// 5.4.5 通过反射,给Customers对象中对应的属性赋值,实现将结果集中的数据存储到对象中
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;
}
// 测试方法
@Test
public void test2() {
Customers customers = new Customers();
String sql = "select cust_id id,cust_name name,cust_email email,cust_birth birth from customers where cust_id" +
" = ?";
Customers customers1 = testCommonRetrieve1(Customers.class, sql, 4);
System.out.println(customers1);
}
不同表的通过查询方法 使用ArrayList集合存储结果集中的多条查询记录对应存放的对象(5.3,5.4.6),返回值变为List< T >
publicList CommonRetrieve(Class clazz, String sql, Object...args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 1.建立连接 conn = JdbcUtils.getConnection(); // 2.预编译sql,生成PreparedStatement对象 ps = conn.prepareStatement(sql); // 3.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } // 4.执行并返回结果集 rs = ps.executeQuery(); // 5.处理结果集 // 5.1 创建该结果集的元数据,通过元数据获取结果集中数据的信息 ResultSetmetaData rsmd = rs.getmetaData(); // 5.2 获取结果集中列的个数 int columnCount = rsmd.getColumnCount(); // 5.3 创建用于存储结果集中多条记录的集合 ArrayList tSet = new ArrayList<>(); while (rs.next()) { // 5.4 处理该行记录的每一个字段 // 5.4.1 创建该行记录在JavaBean中类的对象,此处用泛型实现 T t = clazz.newInstance(); // 5.4.2 利用通过元素据获得的结果集中列的个数,结合for循环对结果集中的列进行遍历 for (int i = 0; i < columnCount; i++) { // 5.4.3 通过结果集对象获取该字段的值 Object columnValue = rs.getObject(i + 1); // 5.4.4 通过元素据获取该字段的别(列)名,该列名与该行记录对应的对象中的属性名相同 String columnLabel = rsmd.getColumnLabel(i+1); // 5.4.5 通过反射,给Customers对象中对应的属性赋值,实现将结果集中的数据存储到对象中 Field field = clazz.getDeclaredField(columnLabel); // 确保私有属性可以访问 field.setAccessible(true); // 给该字段对应的属性赋值 field.set(t,columnValue); } // 5.4.6 将此次循环产生的对象加入新建的集合中 tSet.add(t); } return tSet; } catch (Exception e) { e.printStackTrace(); } finally { // 6.关闭资源 JdbcUtils.closeResource(conn,ps,rs); } return null; } @Test public void test3() { // sql语句查询出多条结果 String sql = "select cust_id id,cust_name name,cust_email email,cust_birth birth from customers where cust_id" + " > ?"; List list = CommonRetrieve(Customers.class, sql, 1); // 使用Lambda表达式表示 list.forEach(System.out::println); }
- PreparedStatement对查询进行操作:
① 难点在于对结果集的处理:
用到ORM编程思想及新建一个bean文件夹内置一个与表名相同的类,通过类的对象对查询后的结果集数据进行处理
② 通用方法使用了元数据、反射、泛型、ArrayList集合等Java核心知识,其中:
元数据和反射处理不确定列的通用问题,泛型处理不同表的通用问题,ArrayList处理结果集中有多条记录的问题。
面向接口的编程思想① 只需要面向JDBC这套接口进行编程就可以了,在代码的import导入中不会出现任何第三方API
② 类的多态性:
在连接数据库的入口方法中,加载了一个配置文件jdbc.properties,里面的driver=com.mysql.cj.jdbc.Driver就是第三方的API的位置,在该API的Driver类的静态代码块中,自动通过JDK中的sql包下的DriverManager对该第三方API中自身Driver类新建对象进行驱动注册。
通过调用自建工具类中获取连接MySQL的方法,返回一个JDK中的Conection对象。但是Connection接口中的方法是没有实现功能的,后面代码中通过该Connetion对象调用的一系列方法其实都是第三方API中对应的实现类中的方法,而通过这些方法返回的其它对象也是JDK中各接口的对象,从而无需导入任何第三方的API即可完成编程。
通过利用占位符预编译sql语句
可以解决拼串操作和SQL注入问题
可以操作Blob数据
实现更高效的批量操作
只需对预编译的sql语句检验一次,之后即使有上万次的添加也只需添加数据即可,无需再检验



