JDBC
前言
JDBC作为Java操作数据库的规范,也是核心基础,以后的框架的原理也是封装了JDBC,利用反射、注解和设计模式来完成简化。
一、JDBC体系结构
- JDBC接口(API)包括两个层次:
- 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
- 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。
不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 ————面向接口编程
Java如何操作数据库如下:
二、JDBC程序编写步骤
三、JDBC连接
package com.xhu.java.jdbc;
import com.mysql.jdbc.Driver;
import org.junit.Test;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/**
* 请开启服务
*/
public class TestConnection {
@Test
public void testConn1() {
//设置需登录的属性文件
Properties pro = new Properties();
pro.setProperty("user", "root");
pro.setProperty("password", "lls");
//获取连接
try (Connection conn = new Driver().connect("jdbc:mysql://localhost:3306/test", pro);) {
System.out.println(conn);
if (conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
@Test
public void testConn2() throws Exception {
//屏蔽具体的第三方实现,利用反射实现
//1.获取class对象
Class driverC = Class.forName("com.mysql.jdbc.Driver");
//2.设置需登录的属性文件
Properties pro = new Properties();
pro.setProperty("user", "root");
pro.setProperty("password", "lls");
//3.通过反射拿到构造器
Constructor dc = driverC.getDeclaredConstructor();
//4.通过拿到的构造器来实例对象
java.sql.Driver driver = (Driver) dc.newInstance();
//5.根据实例的对象来获取连接
Connection conn = driver.connect("jdbc:mysql://localhost:3306/test", pro);
System.out.println(conn);
if (conn != null)
conn.close();
}
@Test
public void testConn3() throws Exception {
//使用DriverManager
//1.获取class对象
Class driverC = Class.forName("com.mysql.jdbc.Driver");
//2.设置需登录的属性文件
Properties pro = new Properties();
pro.setProperty("user", "root");
pro.setProperty("password", "lls");
//3.通过反射拿到构造器
Constructor dc = driverC.getDeclaredConstructor();
//4.通过拿到的构造器来实例对象
java.sql.Driver driver = (Driver) dc.newInstance();
//5.注册driver
DriverManager.registerDriver(driver);
//6.用注册的manager来获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", pro);
System.out.println(conn);
conn.close();
}
@Test
public void testConn4() throws Exception {
//使用DriverManager,MySQL提供静态代码块来注册了driver
//1.加载类,此时JVM加载类、链接、初始化,在初始化时,静态代码块和静态赋值语句一起合并到clinit()方法中,并执行。
/*static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}*/
Class.forName("com.mysql.jdbc.Driver");
//2.设置需登录的属性文件
Properties pro = new Properties();
pro.setProperty("user", "root");
pro.setProperty("password", "lls");
//3.用注册的manager来获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", pro);
System.out.println(conn);
conn.close();
}
@Test
public void testConn5() throws Exception {
//要变化的不写入代码,而是让代码去读配置文件.
//1)这样不用重新编译Java文件,只需改改配置文件。2)代码与数据分离,耦合性低(非硬编码)。
//1.对目标文件创建节点流
InputStream is = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
//2.用Properties将文件读入
Properties pro = new Properties();
pro.load(is);
is.close();
//3.获取properties里value
String driverClass = pro.getProperty("driverClass");
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
//4.获取Class对象,用于反射操作。
Class driverC = Class.forName(driverClass);
//5.用注册的manager来获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
conn.close();
}
}
四、PreparedStatement实现CRUD操作
1、操作和访问数据库
-
数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。
-
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象,会出现SQL注入问题。
- PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
2、CRUD
package com.xhu.java.jdbc;
import org.junit.Test;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
public class TestCRUD {
@Test
public void test_JDBCUtils() {
//别名要对应POJO类中的属性名,才可以用反射注入数据。
String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order`";
List<Order> orders = JDBCUtils.getMultiInstance(Order.class, sql);
orders.forEach(System.out::println);
}
@Test
public void test_getMultiInstance() throws Exception {
String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order`";
List<Order> orders = getMultiInstance(Order.class, sql);
System.out.println(Arrays.toString(orders.toArray()));
orders.forEach(System.out::println);
}
/**
* @param c
* @param sql
* @param args
* @param <T>
* @return
* @throws Exception
* @function 获取多条记录
*/
public <T> List<T> getMultiInstance(Class<T> c, String sql, Object... args) throws Exception {
//1.获取连接
Connection conn = getConn();
//2.获取预处理SQL对象
PreparedStatement ps = conn.prepareStatement(sql);
//3.占位符赋值
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4.执行SQL
ResultSet res = ps.executeQuery();
//5.处理结果集
/**
* @Reflect
* res将查询出数据的字段数据即元数据信息封装在Meta-Data中
* 获取元数据,再获取查询的列数以及字段名
* 通过res获取字段的对应值
* 通过反射+字段名把对应值动态注入进去
*/
List<T> resList = new ArrayList<>();
while (res.next()) {
ResultSetMetaData metaData = res.getMetaData();
int cCount = metaData.getColumnCount();
T t = c.getConstructor(null).newInstance();
//反射注入需要的class对象
Class cusC = t.getClass();
for (int i = 0; i < cCount; i++) {
//获取字段名
String columnName = metaData.getColumnLabel(i + 1);//获取别名对应POJO类,如果没有别名就是原名
Object columnValue = res.getObject(i + 1);
//反射实现动态注入数据
//获取任意权限的字段
Field field = cusC.getDeclaredField(columnName);
//获取private的权限
field.setAccessible(true);
//设置属性值
field.set(t, columnValue);
}
resList.add(t);
}
//6.关闭资源
closeQuery(ps, conn, res);
//7.返回查询结果
return resList;
}
@Test
public void test_getInstance() throws Exception {
String sql = "select id,name,email,birth from customers where id = ?";
System.out.println(getInstance(Customer.class, sql, 1));
}
/**
* @param c
* @param sql
* @param args
* @param <T>
* @return T
* @throws Exception
* @function 各种类的通用查询
*/
public <T> T getInstance(Class<T> c, String sql, Object... args) throws Exception {
//1.获取连接
Connection conn = getConn();
//2.获取预处理SQL对象
PreparedStatement ps = conn.prepareStatement(sql);
//3.占位符赋值
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4.执行SQL
ResultSet res = ps.executeQuery();
//5.处理结果集
/**
* @Reflect
* res将查询出数据的字段数据即元数据信息封装在Meta-Data中
* 获取元数据,再获取查询的列数以及字段名
* 通过res获取字段的对应值
* 通过反射+字段名把对应值动态注入进去
*/
T t = null;
if (res.next()) {
ResultSetMetaData metaData = res.getMetaData();
int cCount = metaData.getColumnCount();
t = c.getConstructor(null).newInstance();
//反射注入需要的class对象
Class cusC = t.getClass();
for (int i = 0; i < cCount; i++) {
//获取字段名
String columnName = metaData.getColumnLabel(i + 1);//获取别名对应POJO类,如果没有别名就是原名
Object columnValue = res.getObject(i + 1);
//反射实现动态注入数据
String setMethodName = "set" + (char) (columnName.charAt(0) - 32) + columnName.substring(1);
String getMethodName = "get" + (char) (columnName.charAt(0) - 32) + columnName.substring(1);
Method method = cusC.getDeclaredMethod(setMethodName, cusC.getDeclaredMethod(getMethodName, null).getReturnType());
method.invoke(t, columnValue);
/**
* 方式二
* //获取任意权限的字段
* Field field = cusC.getDeclaredField(columnName);
* //获取private的权限
* field.setAccessible(true);
* //设置属性值
* field.set(cus, columnName);
*/
}
}
//6.关闭资源
closeQuery(ps, conn, res);
//7.返回查询结果
return t;
}
@Test
public void selectBySQL() throws Exception {
//根据id来查
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = selectCommon(sql, 1);
System.out.println(customer);
//根据name来查
String sql2 = "select id,name,email,birth from customers where name = ?";
customer = selectCommon(sql2, "周杰伦");
System.out.println(customer);
}
/**
* @param sql
* @param args
* @return Customer类
* @throws Exception
* @function 一个类的通用操作
*/
public Customer selectCommon(String sql, Object... args) throws Exception {
//1.获取连接
Connection conn = getConn();
//2.获取预处理SQL对象
PreparedStatement ps = conn.prepareStatement(sql);
//3.占位符赋值
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4.执行SQL
ResultSet res = ps.executeQuery();
//5.处理结果集
/**
* @Reflect
* res将查询出数据的字段数据即元数据信息封装在Meta-Data中
* 获取元数据,再获取查询的列数以及字段名
* 通过res获取字段的对应值
* 通过反射+字段名把对应值动态注入进去
*/
Customer cus = null;
if (res.next()) {
System.out.println("有结果");
ResultSetMetaData metaData = res.getMetaData();
int cCount = metaData.getColumnCount();
cus = new Customer();
//反射注入需要的class对象
Class cusC = cus.getClass();
for (int i = 0; i < cCount; i++) {
//获取字段名
String columnName = metaData.getColumnLabel(i + 1);//获取别名对应POJO类,如果没有别名就是原名
Object columnValue = res.getObject(i + 1);
//反射实现动态注入数据
String setMethodName = "set" + (char) (columnName.charAt(0) - 32) + columnName.substring(1);
String getMethodName = "get" + (char) (columnName.charAt(0) - 32) + columnName.substring(1);
Method method = cusC.getDeclaredMethod(setMethodName, cusC.getDeclaredMethod(getMethodName, null).getReturnType());
method.invoke(cus, columnValue);
/**
* 方式二
* //获取任意权限的字段
* Field field = cusC.getDeclaredField(columnName);
* //获取private的权限
* field.setAccessible(true);
* //设置属性值
* field.set(cus, columnName);
*/
}
}
//6.关闭资源
closeQuery(ps, conn, res);
//7.返回查询结果
return cus;
}
@Test
public void select() throws Exception {
//1.获取连接
Connection conn = getConn();
//2.获取预编译处理SQL的对象
String sql = "select * from customers where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
//3.赋值
ps.setObject(1, 1);
//4.执行得到查询的结果
ResultSet res = ps.executeQuery();
//5.处理结果
/**
* ResultSet对象的next方法作用:判断是否有下一个值,如果有就将指针下移并返回true,否则返回false。
* Iterator对象的hasNext方法作用,只单纯判断是否有下一个,有则返回true,否则返回false。next方法作用:将指针下移并获取元素。
*/
Customer cus = null;
if (res.next()) {
int id = res.getInt(1);
String name = res.getString(2);
String email = res.getString(3);
Date birth = res.getDate(4);
cus = new Customer(id, name, email, birth);
}
System.out.println(cus);
//6.关闭资源
closeQuery(ps, conn, res);
}
/**
* @param ps
* @param conn
* @param res
* @function 关闭连接、预处理SQL、结果集获取的三个流资源
*/
private void closeQuery(PreparedStatement ps, Connection conn, ResultSet res) {
close(ps, conn);
try {
if (res != null) res.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
@Test
public void insert() {
//1.获取连接
Connection conn = null;
PreparedStatement ps = null;
try {
conn = getConn();
//2.通过连接+sql得到一个把SQL语句预编译的操作SQL对象PreparedStatement
String sql = "insert into customers(name,email,birth) values(?,?,?)";//?为占位符
ps = conn.prepareStatement(sql);
//3.为预编译的SQL中占位符设置数值
ps.setString(1, "lls");
ps.setString(2, "lls@qq.com");
ps.setDate(3, new Date(new SimpleDateFormat("yyyy-MM-dd").parse("1998-8-8").getTime()));
//4.执行SQL
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭连接
close(ps, conn);
}
}
@Test
public void update() throws Exception {
//1.获取数据库连接
Connection conn = getConn();
//2.拿到操作SQL的对象
String sql = "update customers set name=? where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
//3.为占位符赋值
ps.setString(1, "LLS");
ps.setInt(2, 19);
//4.执行SQL
ps.execute();
//5.关闭资源
close(ps, conn);
}
private Connection getConn() throws Exception {
//要变化的不写入代码,而是让代码去读配置文件.
//1)这样不用重新编译Java文件,只需改改配置文件。2)代码与数据分离,耦合性低(非硬编码)。
//1.对目标文件创建节点流
InputStream is = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
//2.用Properties将文件读入
Properties pro = new Properties();
pro.load(is);
is.close();
//3.获取properties里value
String driverClass = pro.getProperty("driverClass");
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
//4.获取Class对象,用于反射操作。
Class driverC = Class.forName(driverClass);
//5.用注册的manager来获取连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
private void close(PreparedStatement ps, Connection conn) {
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() {
Object[] args = new Object[]{"lls", 19};
String sql = "update customers set name=? where id = ?";
JDBCUtils.update(sql, args);
}
}
3、JDBCUtils
package com.xhu.java.jdbc;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* @author lls
* @version 1.0
* @describle 数据库操作工具类
* @date 2021-11-27
* @method connection, insert, ...
*/
/**
* 使用PreparedStatement有什么好处?
* 1.SQL注入问题,在获取该对象时,就传入SQL语句进行逻辑的预编译,再注入数据时就不会改变逻辑,从而防止SQL注入问题。
* 2.批量插入数据效率问题,批量插入数据时,该对象在预编译时对SQL已经做了语法校验,再用该对象执行等价SQL就不会校验了,而普通的Statement没执行一条SQL都要重新编译并校验一次语法。
* 3.向数据库写二进制问题,该对象可以通过对占位符赋值IO流来写入二进制文件。
*/
/*
* ORM编程思想 (object relational mapping)
* 一个数据表对应一个java类
* 表中的一条记录对应java类的一个对象
* 表中的一个字段对应java类的一个属性
*
*/
public class JDBCUtils {
/**
* @param c
* @param sql
* @param args
* @param <T>
* @return
* @throws Exception
* @function 获取多条记录, 通用与所有类型
*/
public static <T> List<T> getMultiInstance(Class<T> c, String sql, Object... args) {
//1.获取连接
Connection conn = null;
PreparedStatement ps = null;
ResultSet res = null;
//返回结果
List<T> resList = new ArrayList<>();
try {
conn = getConn();
//2.获取预处理SQL对象
ps = conn.prepareStatement(sql);
//3.占位符赋值
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4.执行SQL
res = ps.executeQuery();
//5.处理结果集
/**
* @Reflect
* res将查询出数据的字段数据即元数据信息封装在Meta-Data中
* 获取元数据,再获取查询的列数以及字段名
* 通过res获取字段的对应值
* 通过反射+字段名把对应值动态注入进去
*/
while (res.next()) {
ResultSetMetaData metaData = res.getMetaData();
int cCount = metaData.getColumnCount();
T t = c.getConstructor(null).newInstance();
//反射注入需要的class对象
Class cusC = t.getClass();
for (int i = 0; i < cCount; i++) {
//获取字段名
String columnName = metaData.getColumnLabel(i + 1);//获取别名对应POJO类,如果没有别名就是原名
Object columnValue = res.getObject(i + 1);
//反射实现动态注入数据
//获取任意权限的字段
Field field = cusC.getDeclaredField(columnName);
//获取private的权限
field.setAccessible(true);
//设置属性值
field.set(t, columnValue);
}
resList.add(t);
}
} catch (Exception e) {
e.printStackTrace();
}
//6.关闭资源
closeQuery(ps, conn, res);
//7.返回查询结果
return resList;
}
/**
* @param sql 要执行的SQL语句
* @param args SQL中要填充的参数
* @function 增删改统一操作
*/
public static void update(String sql, Object... args) {
//1.获取连接
Connection conn = null;
PreparedStatement ps = null;
try {
conn = getConn();
//2.通过连接+sql得到一个把SQL语句预编译的操作SQL对象PreparedStatement
ps = conn.prepareStatement(sql);
//3.为预编译的SQL中占位符设置数值
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4.执行SQL
ps.execute();
} catch (Exception e) {
System.out.println("插入异常");
e.printStackTrace();
} finally {
//5.关闭连接
close(ps, conn);
}
}
/**
* @param c 一个对象实例,对应数据库一条记录
* @function 插入数据
*/
public static void insert(Customer c) {
//1.获取连接
Connection conn = null;
PreparedStatement ps = null;
try {
conn = getConn();
//2.通过连接+sql得到一个把SQL语句预编译的操作SQL对象PreparedStatement
String sql = "insert into customers (`name`,email,birth) values(?,?,?)";//?为占位符
ps = conn.prepareStatement(sql);
//3.为预编译的SQL中占位符设置数值
ps.setString(1, c.getName());
ps.setString(2, c.getEmail());
ps.setDate(3, c.getBirth());
//4.执行SQL
ps.execute();
} catch (Exception e) {
System.out.println("插入异常");
e.printStackTrace();
} finally {
//5.关闭连接
close(ps, conn);
}
}
/**
* @return 数据库连接
* @throws Exception
* @function 获取数据库连接
*/
private static Connection getConn() throws Exception {
//要变化的不写入代码,而是让代码去读配置文件.
//1)这样不用重新编译Java文件,只需改改配置文件。2)代码与数据分离,耦合性低(非硬编码)。
//1.对目标文件创建节点流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
//2.用Properties将文件读入
Properties pro = new Properties();
pro.load(is);
is.close();
//3.获取properties里value
String driverClass = pro.getProperty("driverClass");
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
//4.获取Class对象,用于反射操作。
Class driverC = Class.forName(driverClass);
//5.用注册的manager来获取连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
/**
* @param ps
* @param conn
*/
private static void close(PreparedStatement ps, Connection conn) {
try {
if (ps != null) ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (conn != null) conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* @param ps
* @param conn
* @param res
* @function 关闭连接、预处理SQL、结果集获取的三个流资源
*/
private static void closeQuery(PreparedStatement ps, Connection conn, ResultSet res) {
close(ps, conn);
try {
if (res != null) res.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
4、Java与SQL对应数据类型转换表
Java类型 | SQL类型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR,LONGVARCHAR |
byte array | BINARY , VAR BINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
5、JDBC API小结
-
两种思想
-
面向接口编程的思想
-
ORM思想(object relational mapping)
- 一个数据表对应一个java类
- 表中的一条记录对应java类的一个对象
- 表中的一个字段对应java类的一个属性
sql是需要结合列名和表的属性名来写。注意起别名。
-
-
两种技术
- JDBC结果集的元数据:ResultSetMetaData
- 获取列数:getColumnCount()
- 获取列的别名:getColumnLabel()
- 通过反射,创建指定类的对象,获取指定的属性并赋值
- JDBC结果集的元数据:ResultSetMetaData
总结
1)JDBC简介和连接。
2)JDBC的通用CRUD。
参考文献
[1] JDBC 尚硅谷