JDBC 学习笔记(一)
文章目录
JDBC 介绍
定义
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,使用这些类库可以以一种标准的方法、方便地访问数据库资源。任何提供了JDBC驱动程序的数据库系统都可以使用Java连接。开发者可以通过直接使用JDBC提供的接口,方便的对数据库进行增删改查操作。
JDBC 结构
JDBC接口包括两方面:
· 面向应用的API:抽象接口,提供给开发人员使用;
· 面向数据库的API:供开发商开发数据库驱动使用(标准化数据库操作)。
JDBC连接
要进行JDBC的连接,分为如下几步:
· 加载驱动
· 注册驱动
· 获取连接
首先加载驱动,使用Class.forName()方法,通过此方法可以将对应的驱动类加载到内存中,对于Mysql数据库使用的是"com.mysql.jdbc.Driver"驱动,也就是一个类。
在这里有必要说明一下,Class.forName()返回与给定的字符串名称相关联类或接口的Class对象。是一个静态方法,同样可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。第一种形式的参数 name表示的是类的全名;initialize表示是否初始化类;loader表示加载时使用的类加载器。第二种形式则相当于设置了参数 initialize的值为 true,loader的值为当前类的类加载器。下面来观察一下Driver类的定义情况,类中包含了一个静态代码块,在类加载的时候调用了DriverManager类中的registerDriver方法,参数传入一个Driver()类实例,实现驱动的注册功能,换句话说,在使用ClassforName()进行驱动的加载时,就已经自动的进行了驱动的注册。
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
//创建一个MysqlDriver实例,然后注册到DriverManager中
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
最后一步获取连接,使用DriverManager类中的getConnect()方法建立数据库的连接,该方法的定义如下:
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
因为getConnection是静态方法,所以可以通过DriverManager.getConnection(url,user, password)来调用此方法,实现数据库的连接获取。
在JDBC的连接过程中需要三个要素:Driver接口的实现类、URL、用户名和密码。
因此可以使用配置文件存储这三个要素,在代码中加载配置文件,实现数据库的连接操作。
配置文件(jdbc.properties)如下:
#获取数据
user=root
password=159613
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
driverClass=com.mysql.jdbc.Driver
使用配置文件实现JDBC的连接操作:
public void testConnection5()throws Exception{
//读取配置文件中的4个基本信息
InputStream is = ConnectTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//加载驱动
Class.forName(driverClass);
//获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
public static void main(String[] args) throws Exception {
ConnectTest con = new ConnectTest();
con.testConnection5();
}
这样就实现了一个JDBC的连接。
使用Preparedment实现CRUD操作
数据库的访问方式
在java.sql包中有三个接口实现了对数据库的调用,分别是:Statement、PreparedStatement、CallableStatement
Statement
使用createStatement()方法创建对象,用于执行静态sql语句。Statement在操作数据表时或存在弊端:
1.需要进行拼串操作
2.存在sql注入问题
观察示例代码:
package zhang.da.pao;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.Scanner;
import org.junit.Test;
public class StatementTest {
// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
@Test
public void testLogin() {
Scanner scan = new Scanner(System.in);
System.out.print("用户名:");
String userName = scan.nextLine();
System.out.print("密 码:");
String password = scan.nextLine();
// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '
// ='1' or '1' = '1';
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
+ "'";
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
// 使用Statement实现对数据表的查询操作
public <T> T get(String sql, Class<T> clazz) {
T t = null;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加载配置文件
InputStream is = StatementTest.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.获取连接
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement();
rs = st.executeQuery(sql);
// 获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
// 获取结果集的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// //1. 获取列的名称
// String columnName = rsmd.getColumnName(i+1);
// 1. 获取列的别名
String columnName = rsmd.getColumnLabel(i + 1);
// 2. 根据列名获取对应数据表中的数据
Object columnVal = rs.getObject(columnName);
// 3. 将数据表中得到的数据,封装进对象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return null;
}
}
在上例中,使用Statement实现数据库的操作,sql语句需要进行字符串的拼接,另外,如果使用下面的sql语句,会产生sql注入问题:
SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
所以要使用PreparedStatement接口来代替Statement接口。
PreparedStatement
通过调用Connection对象的PreparedStatement(String sql)方法来获取PreparedStatement对象。
需要注意的是:Preparedstatement对象接收的sql语句中,sql包含的参数使用占位符:“?”来表示,使用setObject()等方法进行参数的设置,setObject()的参数有两个,第一个参数表示第几个“?”,第二个参数是需要设置的值。
增删改操作
数据库增加操作:
//向customer中添加一条记录
public void testInsert() throws Exception {
//1.读取配置文件中的4个基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("src/jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取链接
Connection conn = DriverManager.getConnection(url,user,password);
//System.out.println(conn);
//4.预编译sql语句,返回preparedStatement的实例
String sql = "insert into customers(name,email,birth)values(?,?,?)";//?;占位符
PreparedStatement ps = conn.prepareStatement(sql);
//5.填充占位符
ps.setString(1,"jiege");
ps.setString(2,"z18014702106@163.com");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
Date date = sdf.parse("1996-12-24");
ps.setDate(3,new java.sql.Date(date.getTime()));
//6.执行sql
ps.execute();
//7.资源的关闭
ps.close();
conn.close();
}
此时就实现了数据库的数据增加操作,共分为七步完成:
1.读取配置文件中的4个基本信息
2.加载驱动
3.获取链接
4.预编译sql语句,返回preparedStatement的实例
5.填充占位符
6.执行sql
7.资源的关闭
对于数据的增删改来说,仅仅是sql语句的内容不同,没有返回数据,所以可以将代码修改为一般性的增删改通用代码,而且对于每次进行增删改操作,数据库的连接,资源的关闭操作都是相同的,可以将这些操作定义在工具类JDBCUtils中:
/*
* 操作数据库的工具类
* */
public class JDBCUtils {
/*
* 获取数据库的链接
* */
public static Connection getConnection()throws Exception{
//1.读取配置文件中的4个基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
return conn;
}
/*
* 关闭资源
* */
public static void closeResource(Connection conn, PreparedStatement ps)throws Exception{
if(ps!=null){
ps.close();
}
if(conn!=null){
conn.close();
}
}
public static void closeResource(Connection conn, PreparedStatement ps,ResultSet resultSet)throws Exception{
if(ps!=null){
ps.close();
}
if(conn!=null){
conn.close();
}
if(resultSet!=null){
resultSet.close();
}
}
}
此时可以使用JDBCUtils.getConnection()方法获取Connection对象,使用JDBCUtils.close(Connection connection,PreparedStatement ps)关闭资源。
下面是通用的增删改操作代码:
public void sqlTest(String sql,Object ...args)throws Exception{
//1.获取链接
Connection conn = JDBCUtils.getConnection();
//2.预编译
PreparedStatement ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i =0;i<args.length;i++){
ps.setObject(i+1,args[i]);
}
//4.执行
ps.execute();
//5.关闭资源
JDBCUtils.closeResource(conn,ps);
}
这样代码一下子变得精简起来了,这里我们已经实现了多数据库的增删改操作,还有一个很重要的查询操作,因为数据查询涉及了返回值的问题,所以需要单独拿出来说明。
数据库的查询操作
数据库的查询操作返回了一个结果集,使用PreparedStatement类中的executeQuery()获取结果集resultSet,因为获取的结果集为一个表数据,包含了属性名和属性值,我们使用一个对象来存储获取到的结果集,需要知道的是结果集的列数,使用ResultSetMetaData metaData = resultSet.getMetaData()获取元数据(包括列名和列数),metaData.getClumnCount()获取,还需要知道结果集的列属性名称:使用:metaData.getColumnLabel(i+1)获取第i行对应的属性值。使用反射的方法,通过属性名来获取对应的存储类中的属性,给对象的相应属性赋值。
示例代码如下:
public <T> T testQuery(Class<T> clazz,String sql,Object ...args )throws Exception{
//获取连接
Connection conn = JDBCUtils.getConnection();
//预编译
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
for(int i = 0;i<args.length;i++){
ps.setObject(i+1,args[i]);
}
//获取结果集
ResultSet resultSet = ps.executeQuery();
//获取元数据
ResultSetMetaData metaData = resultSet.getMetaData();
if(resultSet.next()){
//获取结果集的列数
int columnCount = metaData.getColumnCount();
//创建对象,接收数据
T t = clazz.getDeclaredConstructor().newInstance();
for(int i = 0;i <columnCount;i++){
//获取每列的元素名
String columnLabel = metaData.getColumnLabel(i+1);
//获取列值
Object value = resultSet.getObject(i + 1);
//通过反射存入数据
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);
declaredField.set(t,value);
}
return t;
}
return null;
}