JDBC基本操作
标签(空格分隔): jdbc
1. 在项目中导入mysql驱动jar包
mysql-connector-java-5.1.45-bin
包
- 在IntelliJ IDEA中打开要添加jar包的Project
- File – Project Structure 或使用快捷键ctrl+alt+shift+s
- 选择Moudules – 再选择Dependencies
- 选中Moudule source – 然后点击右侧+号 – 选第一个jars of directories
- 找到并选中文件mysql-connector-java-5.1.42-bin.jar – 点确定OK
最后,我们可以在External Libraries中找到jar包
2. 通过DriverManager获取数据库连接
DriverManager是驱动的管理类,比直接使用Driver驱动更方便,其特点如下:
1. 可以通过重载的getConnection()方法来
2. 可以同时管理多个驱动程序:若注册了多个数据库连接,则调用getConnection()方法时传入的参数不同,即返回不同的数据库连接
下面提供了两种方法,可重用性较差的方法是将连接数据可的字符串直接进行赋值;可重用性较高的方法是将信息写到配置文件中,然后调用文件中的信息
可重用性较差的方法
通过DriverManager获取数据库连接的过程如下:
- 准备连接数据库的4个字符串:
- 驱动的全类名(com.mysql.jdbc.Driver)
- JDBC URL(jdbc:mysql://localhost:3306//test) localhost:3306为默认,可以省略
- user(root)
- password(1234)
加载数据库驱动程序(对应的Driver实现类中有注册驱动的静态代码块):
Class.forName(driverClass);
通过DriverManager的getConnection()方法获取数据库连接
Connection c = DriverManager.getConnection(jdbcUrl,user,password);
代码如下
public static void testDriverManager() throws ClassNotFoundException, SQLException {
String driverClass = "com.mysql.jdbc.Driver";
String jdbcUrl = "jdbc:mysql://localhost:3306/tables";
String user = "root";
String password = "1234";
Class.forName(driverClass);
Connection connection = DriverManager.getConnection(jdbcUrl,user,password);
System.err.println(connection);
}
可重用性高的方法
步骤如下:
- 准备建立数据库的4个字符串
1.1 创建Properties对象
1.2 获取jdbc.properties对应的输入流
1.3 具体决定user,passwoed等4个字符串 - 加载数据库驱动程序(对应的Driver实现类中有注册驱动的静态代码块)
- 通过DriverManager的getConnection()方法获取数据库连接
public static Connection getConnection() throws IOException, ClassNotFoundException, SQLException {
Properties properties = new Properties();
InputStream inputStream =
JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(inputStream);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String jdbcUrl = properties.getProperty("jdbcurl");
String driver = properties.getProperty("dirver");
Class.forName(driver);
return DriverManager.getConnection(jdbcUrl,user,password);
}
在测试的时候发现上面的代码会在properties.load(inputStream);
处报空指针异常。其根本原因在于this.getClass().getClassLoader().getResourceAsStream("jdbc.properties")
的路径地址有问题。如果我们放在项目的src文件下的,是可以这么写的。
但是我放在了包下,那么就要把包名路径写上:day1/jdbc.properties
3. 通过Statement插入数据
Statement 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。Statement对象,用于执行不带参数的简单SQL语句。
通过JDBC向指定的数据库中插入一条记录
- 获取数据库连接
1.1 创建Connection的对象
1.2 创建Statement的对象, Statement是用于执行SQL语句的对象 - 准备插入的SQL语句
2.1 sql语句是字符串的形式,可以使INSERT,UPDATE或DELETE,但不能是SELECT - 执行插入
3.1 获取操作SQL语句的Statement对象(调用Connection的createStatement()方法实现的)
3.2 调用Statement对象的executeUpdate(sql)执行SQL语句进行插入 - 关闭Statement对象(先关闭)
- 关闭Connection连接(后关闭)
代码如下:
public void insert() throws Exception {
Connection connection = getConnection();
String sql = "INSERT INTO customers(NAME,email,birth) VALUES('jack','jack@163.com','1990-10-07')";
Statement statement = connection.createStatement();
statement.executeUpdate(sql);
statement.close();
connection.close();
}
通过观察我们发现,我们每次都要建立连接,断开连接。因此我们把获取连接的方法和关闭资源的方法写成一个工具类,当然其中也能有事物的提交回滚等。然后我们利用JDBCTools写一个通用的更新方法,包含增,删,改
JDBCTools:
package day1;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Created by japson on 12/3/2017.
* JDBC的工具类,其中封装了一些JDBC的工具方法
*/
public class JDBCTools {
/**
* 获取连接的方法
* 通过读取配置文件从数据库服务器获取一个连接
* @return
* @throws Exception
*/
public Connection getConnection() throws Exception {
Properties properties = new Properties();
InputStream inputStream =
JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(inputStream);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String jdbcUrl = properties.getProperty("jdbcUrl");
String driver = properties.getProperty("driver");
Class.forName(driver);
return DriverManager.getConnection(jdbcUrl,user,password);
}
/**
* 关闭资源:关闭Statement对象以及Connection连接
* @param statement
* @param connection
*/
public static void release(Statement statement,Connection connection) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
通用类:
public void update(String sql) {
Connection connection = null;
Statement statement = null;
try {
connection = JDBCTools.getConnection();
statement = connection.createStatement();
statement.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(Statement statement,Connection connection);
}
}
通过观察我们发现,我们每次都要建立连接,断开连接。因此我们把获取连接的方法和关闭资源的方法写成一个工具类,当然其中也能有事物的提交回滚等
4. 通过ResultSet查询数据
ResultSet结果集,封装了使用JDBC进行查询的结果。其本质就是一张逻辑表格,其目的操作是通过处理结果集,获取数据库中的某列的具体值。
- 调用Statement对象的executeQuery(sql)可以得到结果集
- ResultSet返回的是一张数据表,有一个指针指向数据表的第一行的前面。可以调用next()方法检测下一行是否有效,若有效,该方法返回true且指针下移
- 在查询到行以后,通过getXXX(index)或getXXX(columnName)的方法获取每一列的值
- ResultSet需要进行关闭
其执行过程如下:
1. 获取Connection连接
2. 获取Statement对象
3. 准备SQL
4. 执行查询,调用Statement对象的executeQuery(sql),得到结果集ResultSet的对象
5. 处理ResultSet结果集
5.1 调用ResultSet的next()方法;查看结果集的下一条记录是否有效,若有效则下移指针
5.2 getXXX()方法获取具体的列的值
6. 关闭Statement以及Connection
下面我们写一个能够查询customers表中所有数据的代码
public void testResultSet() {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
statement = connection.createStatement();
String sql = "SELECT id,name,email,birth " + "FROM customers";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString("name");
String email = resultSet.getString(3);
Date birth = resultSet.getDate(4);
System.out.println(id);
System.out.println(name);
System.out.println(birth);
System.out.println(email);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet,statement,connection);
}
}
4. PreparedStatement
我们使用JDBC进行数据库的操作要使用面向对象的方法,其具体就是在准备sql语句时,不是简单的使用字符串,而是为数据表中的一行数据创建一个对象,以对象的字段来代替数据表中的字段。
但是这样就会出现一个问题,在拼写数据库语句时要大量是用“”,‘’和+等符号操作,很繁琐,而且容易出错。
因此我们使用PreparedStatement,PreparedStatement是Statement的子接口,可以传入带占位符的SQL语句,并且提供了补充占位符变量的方法。
使用PreparedStatement操作数据库语句
其使用过程如下:
- 创建sql语句,用?来代替数据库表的字段
- 通过Connection创建PreparedStatement的对象,并将sql作为参数传递
- 调用PreparedStatement的setXXX()的方法
setXXX(int index,Object val)
设置占位符(index)的值(val) - 执行sql语句:
executeQuery()
或executeUpdate()
,注意执行时不再需要传入sql语句
代码如下:
public void testPreparedStatement() {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCTools.getConnection();
String sql = "INSERT INTO customers (name,email,birth)"
+ "VALUES(?,?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"Anny");
preparedStatement.setString(2,"anny@163.com");
//第二个参数我们需要一个sql.Date类型,因此我们new一个sql.Date类型
//但是其构造方法需要一个参数,因此在里面传一个new java.util.Date().getTime())
preparedStatement.setDate(3,new Date(new java.util.Date().getTime()));
preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null,preparedStatement,connection);
}
}
写进JDBCTools的更通用的方法
下面我们可以将其写为更通用的模式,将占位符作为可变参数传入:
public static void update(String sql,Object ... args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCTools.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0;i < args.length;i++) {
preparedStatement.setObject(i+1,args[i]);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null,preparedStatement,connection);
}
}
禁止SQL注入
PreparedStatement对象可以有效地禁止SQL注入。
SQL注入是利用某些系统没有对用户输入进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,从而利用系统的SQL引擎完成恶意行为的做法。
例如我们对某系统的用户名和密码注入非法的语句段达到登录的目的
String username = "a' OR PASSWORD = ";
String password = " OR '1'='1'";
对于Java而言,要防范SQL注入,只要用PreparedStatement取代Statement,用占位符?来代替字符串的拼接,就可以了。
提高性能
DBServer(数据库服务器)会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身 不能匹配,没有缓存语句的意义。事实是没有数据库会对普通语句编译后的执行代码缓存,这样每执行一次都要对传入的数据编译一次。
5. 利用反射及JDBC元数据编写通用的查询方法
思想
我们要写一个通用的查询方法,可能跟插入的通用方法不一样。因为在查询方法中,我们要具体获知某个具体的类的对象,以及类的属性,然后通过处理结果集获取数据库中列的具体值,最后返回给这个对象。
但是其中我们涉及到了一些问题:
- 既然是一个通用方法,我们不知道对象的属性
- 对于查询到的结果集,我们也不清楚这个表具体什么样
- 由于我们不知道具体的数据库,我们也没法将属性和数据库的字段对应起来
因此我们就利用到了反射技术来创建对象,获取对象的属性。并且需要解析到sql语句中列的别名,得到列所对应的值,然后利用反射技术将值赋给属性
思路大致如下:
1. 先利用SQL进行查询,得到结果集
2. 利用反射创建实体类的对象
3. 获取结果集的列的别名
4. 再获取结果集的每一列的值,结合3得到一个Map,键:列的别名,值:列的值
5. 再利用反射为2的对应的属性赋值:属性即为map的键,值即为map的值
其中第3步获取结果集的列的别名,是我们要解决的问题。解决办法是获取JDBC的元数据。
使用 JDBC 驱动程序处理元数据
元数据是描述数据的数据。Java通过JDBC获得连接以后,得到一个Connection对象,可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。根据这些信息,JDBC可以访问一个实现事先并不了解的数据库。
获取这些信息的方法都是在DatabaseMetaData类的对象上实现的,而DataBaseMetaData对象是在Connection对象上获得的。
DatabaseMetaData类的元数据
是描述ResultSet的元数据对象。即从中可以获取到结果集中有多少列,列名是什么…
用法如下:
- 得到ResultSetMetaData对象:调用ResultSet的getMetaData()方法
- ResultSetMetaData有哪些好用的方法:
int getColumnCout()
:SQL语句中包含哪些列String getColumnLabel(int column)
:获取指定的列的别名,其中索引从1开始
写法
我们要实现一个通用的查询方法,查询jobs表中最低工资为20000的部门的详细信息。
这里要注意一点,这个查询是返回一条记录,因此返回对应的对象:
<T> T get(Class<T> clazz,String sql,Object ... args);
如果我们要查询多条记录,比如查询jobs表中最低工资为4000的部门的详细信息。会查询到3个结果,这样就要返回一个对象的集合:
<T> List<T> getForL ist(Class<T> clazz,String sql,Object ... args);
步骤如下
- 获取Connection连接
- 获取PreparedStatement对象,传递SQL语句
- setObject填充占位符
- 利用executeQuery()进行查询得到得到结果集
- 准备Map
package day2;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.HashMap;
import java.util.Map;
/**
* Created by japson on 12/6/2017.
*/
public class JDBCTest {
public static void main(String[] args) {
String sql = "SELECT job_id jobId,job_title jobTitle,min_salary,max_salary FROM jobs WHERE min_salary=?";
//如果查询的是最低工资为20000,则仅有一个对象,如果为4000,会查询到3个结果,这样就要返回一个对象的集合
Job jobs = get(Job.class,sql,4000);
//Customer customer = get(Customer.class,sql,5);
System.out.println(jobs);
}
/**
* 通用的查询方法,可以根据传入的SQL、Class对象返回SQL对应的记录的对象
* @param clazz 描述对象的类型
* @param sql SQL语句。可能带占位符
* @param args 填充占位符的可变参数
* @param <T> 泛型 代表一个类型
* @return
*/
public static <T> T get(Class<T> clazz,String sql,Object... args) {
//创建T类型的实体entity
T entity = null;
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1. 建立JDBC的连接
connection = JDBCTools.getConnection();
//2. 通过Connection创建PreparedStatement的对象,并将sql作为参数传递
preparedStatement = connection.prepareStatement(sql);
//3. 利用setObject的方法获取占位符中的值(数据库中的字段)
for (int i = 0;i < args.length;i++) {
preparedStatement.setObject(i+1,args[i]);
}
//4. 调用Statement对象的无参executeQuery()得到结果集
resultSet = preparedStatement.executeQuery();
//5. 得到ResultSetMetaData对象
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
//6. 创建Map,键为列的别名,值为列的值
//为什么值是Object类型的?因为我们不知道数据库中的值是什么类型的
Map<String,Object> values = new HashMap<>();
//7. 处理结果集,把指针向下移动一个单位
while (resultSet.next()) {
//8. 由ResultSetMetaData对象得到结果集中有多少列
int columnCount = resultSetMetaData.getColumnCount();
//9. 由ResultSetMetaData得到每一列的别名,由ResultSet得到具体每一列的值
for (int i = 0; i < columnCount; i++) {
String columnLabel = resultSetMetaData.getColumnLabel(i + 1);
Object columnValue = resultSet.getObject(columnLabel);
//10. 利用ResultSetMetaData填充Map对象
values.put(columnLabel,columnValue);
}
}
//11. 利用反射创建Class对应的对象
if (values.size() > 0) {
entity = clazz.newInstance();
//12. 遍历Map,利用反射为Class对象的属性赋值
for (Map.Entry<String, Object> entry : values.entrySet() ) {
//属性名为Map中的key,属性值为Map中的value
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
ReflectionUtils.setFieldValue(entity,fieldName,fieldValue);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet,preparedStatement,connection);
}
return entity;
}
}
6. 获取插入记录的主键值
有的时候我们需要在插入记录时获取该条记录的主键值。需要使用重载的prepareStatement(sql,flag)来生成PreparedStatement对象,并调用getGeneratedKeys()获取包含了新生成的主键的ResultSet对象
@Test
public void getGeneratedKey() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "INSERT INTO customers(name,email,birth) VALUES (?,?,?)";
//使用重载的prepareStatement(sql,flag)来生成PreparedStatement对象
preparedStatement = connection.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1,"ADC");
preparedStatement.setString(2,"ADC@163.com");
preparedStatement.setDate(3,new Date(new java.util.Date().getTime()));
preparedStatement.executeUpdate();
//通过 .getGeneratedKeys()获取包含了新生成的主键的ResultSet对象
resultSet = preparedStatement.getGeneratedKeys();
if (resultSet.next()) {
System.out.println(resultSet.getObject(1));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet,preparedStatement,connection);
}
}
7. 处理Blob
BLOB 类型介绍
MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)
类型 | 大小(单位:字节) |
---|---|
TinyBlob | 最大255 |
Blob | 最大65K |
MediumBlob | 最大16M |
LongBlob | 最大4G |
实际使用中根据需要存入的数据大小定义不同的BLOB类型。需要注意的是:如果存储的文件过大,数据库的性能会下降。
插入Blob数据
插入BLOB类型的数据必须使用PreparedStatement:因为BLOB类型的数据时无法使用字符串拼写的
调用setBlob(int index,InputStream inputStream)
@Test
public void testInsertBlob() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "INSERT INTO customers(name,email,birth,picture) VALUES (?,?,?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"ADCD");
preparedStatement.setString(2,"ADCD@163.com");
preparedStatement.setDate(3,new Date(new java.util.Date().getTime()));
//创建一个输入流
InputStream is = new FileInputStream("C:\\Users\\heigui\\Desktop\\文档\\捕获.PNG");
//setBlob需要从输入流中读取数据
preparedStatement.setBlob(4,is);
preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet,preparedStatement,connection);
}
}
读取blob数据
读取blob数据:
1. 使用getBlob方法读取到Blob对象
2. 调用Blob的getBinaryStream()方法得到输入流。再使用IO操作
@Test
public void readBlob() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "SELECT id,name,email,birth,picture FROM customers WHERE id = 10";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
String birth = resultSet.getString(4);
//Blob类型的对象,通过结果集调用getBlob()
Blob picture = resultSet.getBlob(5);
//输入流对象为Blob对象获取的二进制流
InputStream inputStream = picture.getBinaryStream();
//创建输出流对象
OutputStream outputStream = new FileOutputStream("testPic.jpg");
//从输入流中进行读取,通过输出流写入磁盘
byte[] b = new byte[1024];
int len = 0;
while ((len = inputStream.read(b)) != -1) {
outputStream.write(b,0,len);
}
System.out.println(id + "," + name + "," + email + "," + birth);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet,preparedStatement,connection);
}
}