文章目录
一、JDBC
JDBC(Java Database Connectivity,Java数据库连接) 是一套用于执行SQL操作的Java API,应用程序可以通过这套API连接到关系数据库,并使用SQL操作完成对数据库中数据的CRUD等操作,方便地管理数据库资源。
数据的持久化:将内存中的数据保存到可掉电式存储设备中以供使用。(如关系数据库、磁盘文件、XML文件)
1.JDBC API两种思想
(1)面向接口编程的思想
(2)ORM思想(object relational mapping,对象关系映射)
① 一个数据表对应一个java类
② 表中的一条记录对应java类的一个对象
③ 表中的一个字段对应java类的一个属性
sql是需要结合列名和表的属性名来写。注意起别名。
2.JDBC API两种技术
(1) JDBC结果集的元数据:ResultSetMetaData
获取列数:getColumnCount()
获取列的别名:getColumnLabel()
(2)通过反射,创建指定类的对象,获取指定的属性并赋值
如果没有JDBC,Java程序访问数据库是这样的:
有了JDBC以后,Java程序是这样访问数据库的:
3.JDBC接口(API)的两个层次
① 面向接口的API: Java APl,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)
② 面向数据库的API: Java Driver API,供数据库开发商开发数据库驱动时使用。
4.JDBC常用API
JDBC API主要位于java.sql和javax.sql包下:
(1)Driver接口
每个驱动程序类必须实现的接口。 使用时必须将所使用的数据库驱动程序或类库加载到项目的classpath中(这里是指数据库的驱动的jar包)。
(2)DriverManager类
DriverManager类用于加载JDBC驱动并且创建与数据库的连接。定义了两个重要的静态方法:
static Connection getConnection(String url, String user, String password) :尝试建立与给定数据库URL的连接,返回连接对象
static void registerDriver(Driver driver):向DriverManager中注册给定的JDBC驱动程序
通常不适用registerDriver(Driver driver)这种方式注册驱动,因为在驱动类com.mysql,.jdbc.Driver中有一段静态代码块,是向DriverManager注册一个Driver实例。通常加载数据库驱动使用Class类的静态方法forName()来实现。
(3)Connection接口
Connection接口代表Java程序和数据库的连接对象,只有获得该连接对象后,才能访问数据库,操作数据库。
(4)Statement接口
Statement接口用于执行静态SQL语句,返回一个结果对象。
(存在的弊端:① 字符串拼接,繁琐 ② 存在SQL注入问题,对数据库中的表恶意操作)
SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1= 'AND password = 'OR ‘1’=‘1’),从而利用系统的SQL引擎完成恶意行为的做法。
如下,尽管输入的是不规范的SQL语句,但是也能查询到数据中表的数据:
(5)PreparedStatement接口
PreparedStatement是Statement的子接口,用于执行预编译的SQL语句。扩展了带有参数的SQL语句的执行操作,应用程序中的SQL语句可以使用占位符“?”来代替其参数,然后通过setXxx()方法为SQL语句的参数赋值。
部分方法:
(6)ResultSet接口
ResultSet用于保存JDBC执行查询时返回的结果集,该结果集封装在一个逻辑表格中。
部分方法:
5.JDBC程序编写步骤
二、获取数据库连接
Statement接口存在SQL注入问题,所以不使用,我们通常使用PreparedStatement接口来实现对数据库的操作。
1.配置文件:jdbc.properties
(mysql 8及以上com.mysql.cj.jdbc.Driver需要使用cj,jdbc:mysql://localhost:3306/test此处test是需要操作的数据库的名称)
user=root
password=12345678
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
2.将数据库连接的4个基本信息声明在配置文件中,通过读取配置文件获取连接
//方式5:最终版;将数据库连接的4个基本信息声明在配置文件中,通过读取配置文件获取连接
//实现了数据与代码的分离,解耦;如果需要修改配置文件信息,可以避免程序重新打包(部署到tomcat服务器上时)
@Test
public void test05() throws Exception {
//1.读取配置文件(使用类的加载器读取配置文件)
InputStream resource = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(resource);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//2.加载驱动
Class clazz = Class.forName(driverClass);
//3.获取连接
Connection connect = DriverManager.getConnection(url, user, password);
System.out.println(connect);
}
三、对数据的增删改操作
1.数据的操作都会涉及读取配置文件、加载驱动、获取连接和关闭连接等操作,所以可以将这些步骤定义在一个工具类中,避免代码冗余。 如下:
/**
* 数据库表中数据操作的工具类
* @author wds
* @date 2021-12-11 10:30
*/
//数据库操作的工具类
public class JdbcUtils {
//获取数据库的连接
public static Connection getConnection() throws Exception {
//1.读取配置文件(使用系统加载器读取配置文件)
InputStream resource = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(resource);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//2.加载驱动
Class clazz = Class.forName(driverClass);
//3.获取连接
Connection connect = DriverManager.getConnection(url, user, password);
return connect;
}
//关闭连接(Statement和Connection)
public static void closeConnection(Statement ps, Connection connect){
try {
if(ps!=null){
ps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(connect!=null){
connect.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//关闭连接(Statement和Connection)
public static void closeConnection(Statement ps, Connection connect, ResultSet rs){
try {
if(ps!=null){
ps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(connect!=null){
connect.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(rs!=null){
rs.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
2.将实现增、删、改的通用操作声明在一个方法中,直接调用方法实现对数据库操作
(若使用的表是使用SQL中的关键字声明的,则需要使用``将数据库表名括起来使用)
因为对数据增删改操作只需要返回操作是否成功,使用通用的方法可以定义,如下:
public class PreparedStatementTest {
@Test
public void deleteTest(){
//1.对customers表进行删除操作
// String sql="delete from customers where id = ?";
// updateTable(sql,3);
//2.对order表进行更新操作
//此处的表名order和数据库关键字(order by)一样,必须使用``括起来使用
String sql = "update `order` set order_name = ? where order_id = ?";
updateTable(sql,"DD","2");
System.out.println("sql操作执行成功...");
}
//实现数据库“增、删、改”的通用操作
public void updateTable(String sql,Object...args) {
Connection connect = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
connect = JdbcUtils.getConnection();
//2.预编译sql语句,返回prepareStatement的实例
ps = connect.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.closeConnection(ps,connect);
}
}
}
四、对数据的查询操作
Java与SQL对应数据类型关系表
对于数据的查询操作,ORM思想至关重要:
ORM(Object Relation Mapping):对象关系映射
- 数据库中的一张表对应一个java Bean
- 数据库中表的一个字段对应一个Java属性
- 数据库中表的一条记录对应一个Java对象
数据表查询的流程:
public class Order {
private int orderId;
private String orderName;
private Date orderDate;
...
}
public class Customer {
private int id;
private String name;
private String email;
private Date birth;
...
}
1.单条记录的查询
public class PreparStatement {
@Test
public void test(){
/*
针对于表的字段名与类的属性名不相同的情况:
1.必须声明sql时,使用类的属性名来命名字段的别名
2.使用ResultSetMetaData时,需要使用getColumnLabel()来替换getColumnName(),获取列的别名。
说明:如果sql中没有给字段其别名,getColumnLabel()获取的就是列名(此处是指数据库表的字段名和类的属性名一致时)
*/
String sql = "select id,name,email,birth from customers where id = ? ";
Customer customer = queryForTable(Customer.class, sql, 1);
System.out.println(customer);
sql = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
Order order = queryForTable(Order.class, sql, 1);
System.out.println(order);
}
//针对不同数据表的通用的单行查询操作
public <T> T queryForTable(Class<T> clazz, String sql,Object...args) {
Connection connect = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//1.获取数据库的连接
connect = JdbcUtils.getConnection();
//2.预编译Sql
ps = connect.prepareStatement(sql);
for(int i=0;i<args.length;i++){
ps.setObject(i+1,args[i]);
}
//3.执行sql操作
resultSet = ps.executeQuery();
//查询结果集的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//查询结果集的列数
int columnCount = metaData.getColumnCount();
if(resultSet.next()){
//newInstance()只能调用无参构造方法,创建当前类的对象
T t = clazz.newInstance();
//返回结果集中的每一个列
for(int i=0;i<columnCount;i++){
//获取每个列的列值,通过resultSet
Object columnValue = resultSet.getObject(i + 1);
//通过ResultSetMetaData
//获取每个列的列名
String columnName = metaData.getColumnName(i + 1);
//获取每个列的别名
String columnLabel = metaData.getColumnLabel(i + 1);
//通过反射,将对象指定名getColumnName的属性设置为指定的属性值:columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t,columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(ps,connect,resultSet);
}
return null;
}
}
查询结果:
2.多条记录的通用查询
public class PrepareStatement02 {
@Test
public void test(){
/*
针对于表的字段名与类的属性名不相同的情况:
1.必须声明sql时,使用类的属性名来命名字段的别名
2.使用ResultSetMetaData时,需要使用getColumnLabel()来替换getColumnName(),获取列的别名。
说明:如果sql中没有给字段其别名,getColumnLabel()获取的就是列名(此处是指数据库表的字段名和类的属性名一致时)
*/
String sql = "select id,name,email,birth from customers where id < ?";
List list = queryForTable(Customer.class, sql, 12);
list.forEach(System.out::println);
//sql中where条件后面没有筛选条件时,查询所有的数据,参数此时可以不写
sql = "select order_id orderId,order_name orderName,order_date orderDate from `order`";
List list1 = queryForTable(Order.class, sql);
list1.forEach(System.out::println);
}
//针对不同数据表的通用的多行查询操作
public <T> List queryForTable(Class<T> clazz, String sql, Object...args) {
Connection connect = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//1.获取数据库的连接
connect = JdbcUtils.getConnection();
//2.预编译Sql
ps = connect.prepareStatement(sql);
for(int i=0;i<args.length;i++){
ps.setObject(i+1,args[i]);
}
//3.执行sql操作
resultSet = ps.executeQuery();
//查询结果集的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//查询结果集的列数
int columnCount = metaData.getColumnCount();
ArrayList<T> list = new ArrayList<>();
while (resultSet.next()){
//newInstance()只能调用无参构造方法,创建当前类的对象
T t = clazz.newInstance();
//返回结果集中的每一个列,给指定的t的属性赋值
for(int i=0;i<columnCount;i++){
//获取每个列的列值,通过resultSet
Object columnValue = resultSet.getObject(i + 1);
//通过ResultSetMetaData
//获取每个列的列名
String columnName = metaData.getColumnName(i + 1);
//获取每个列的别名
String columnLabel = metaData.getColumnLabel(i + 1);
//通过反射,将对象指定名getColumnName的属性设置为指定的属性值:columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t,columnValue);
}
//处理完当前的一个完整对象之后,添加进集合list
list.add(t);
}
//所有的操作结束之后,再返回list
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(ps,connect,resultSet);
}
return null;
}
}
查询结果: