说明:
全称:Java Data Base Connectivity
jar包位置:java.sql、javax.sql
MySQL驱动jar包:mysql-connector-java-x.x.x.jar
Oracle驱动jar包:ojdbcxx.jar
工作流程:
应用程序 —> JDBC —> MySQL驱动或Oracle驱动 —> MySQL数据库或Oracle数据库
一、JDBC操作数据库(增删改查):
1.加载驱动:
方式1:
/**
* 1加载驱动
*/
static {
try {
// 不推荐,JVM虚似机内存中会产生两个一样的Driver对象。com.mysql.cj.jdbc.Driver
DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
e.printStackTrace();
}
}
方式2:
/**
* 1加载驱动
*/
static {
try {
// 推荐这种方式,不会对具体的驱动类产生依赖。
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
2.建立连接:
(1)URL格式说明:
协议:子协议://主机:端口/数据库名?serverTimezone=UTC
MySQL的url:jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC
Oracle的url:jdbc:oracle:thin:@localhost:1521:数据库名?serverTimezone=UTC
SQLServer的url:jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=数据库名?serverTimezone=UTC
(2)建立连接:
方式1:
/**
* 2.建立连接
*
* @param url 数据库url
* @param username 数据库登录用户名
* @param password 数据库登录密码
*/
public Connection getDBConnection(String url, String username, String password) throws SQLException {
return DriverManager.getConnection(url + "?" + "user=" + username + "&&password=" + password);
}
方式2:
/**
* 2.建立连接
*
* @param url 数据库url
* @param username 数据库登录用户名
* @param password 数据库登录密码
*/
public Connection getDBConnection(String url, String username, String password) throws SQLException {
return DriverManager.getConnection(url, username, password);
}
3.创建PreparedStatement对象:
(1)采用Statement实现,有被SQL注入的危险,如:('or 1=1 or name=') ,且可能造成数据库缓冲区溢出:
// 创建Statement
Statement st = conn.createStatement();
(2)采用PreparedStatement实现,此类安全,可对SQL进行预编译:
/**
* 3.创建PreparedStatement对象
*
* @param conn Connection连接对象
* @param sql sql语句
*/
public PreparedStatement getPreparedStatement(Connection conn, String sql) throws SQLException {
// 创建PreparedStatement
//PreparedStatement st = conn.prepareStatement(sql);
PreparedStatement st = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); // 或,参数2的常量表示生成的主键可以获取
return st;
}
4.设置?占位符数据:
(1)int类型:
String sql = "insert into user(id,name,birthday,mytext,myblob) values(?,?,?,?,?)";
PreparedStatement st = getPreparedStatement(conn, sql);
st.setInt(1, 1); // 参数表示?位置,从1开始,参数2为数据,对应第1个?号,int类型
(2)String类型:
PreparedStatement st = null;
st.setString(2, "杨先生"); // 对应第2个?号,String类型
(3)日期类型:
PreparedStatement st = null;
st.setDate(3, new java.sql.Date(new java.util.Date().getTime())); // 对应第3个?号,日期存入数据库时要转成sql类型的Date
(4)大文本类型:
/*
* 对应第4个?号,大文本类型(文件路径:工程/WebContent/WEB-INF/file/text.txt)
*/
String textFileName = "text.txt";
String textFilePath = context.getRealPath("WEB-INF/file/" + textFileName);
File textFile = new File(textFilePath);
st.setCharacterStream(4, new FileReader(textFile), (int) textFile.length());
(5)二进制数据类型:
PreparedStatement st = null;
/*
* 对应第5个?号,二进制数据类型(文件路径:工程/WebContent/WEB-INF/file/text.txt)
*/
String blobFileName = "blob.png";
String blobFilePath = context.getRealPath("WEB-INF/file/" + blobFileName);
File blobFile = new File(blobFilePath);
st.setBinaryStream(5, new FileInputStream(blobFile), (int) blobFile.length());
5.执行语句:
PreparedStatement st = null;
ResultSet rs = st.getGeneratedKeys(); //得到插入行的主键,只对insert有用
boolean result = st.execute(); //任意操作使用
ResultSet rs = st.executeQuery(); //查询时使用
int result = st.executeUpdate(); //增删改时使用
6.增删改查:
通用关闭方法:
/**
* 关闭数据库连接
*/
public void close(Connection conn, PreparedStatement st, ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
if (st != null) {
st.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
(1)插入数据:
/**
* 4.1插入数据
*
* @param conn Connection连接对象
*/
public void insert(ServletContext context, Connection conn) {
PreparedStatement st = null;
try {
// 1.创建sql语句,id为int类型、name为String类型、birthday为java.sql.Date类型、mytext为大文本类型、myblob为二进制数据类型
String sql = "insert into user(id,name,birthday,mytext,myblob) values(?,?,?,?,?)";
// 2.获取PreparedStatement
st = getPreparedStatement(conn, sql);
// 3.给占位符设置数据
st.setInt(1, 1); // (1)对应第1个?号,int类型
st.setString(2, "杨先生"); // (2)对应第2个?号,String类型
st.setDate(3, new java.sql.Date(new java.util.Date().getTime())); // (3)对应第3个?号,日期存入数据库时要转成sql类型的Date
/*
* (4) 对应第4个?号,大文本类型(文件路径:工程/WebContent/WEB-INF/file/text.txt)
*/
String textFileName = "text.txt";
String textFilePath = context.getRealPath("WEB-INF/file/" + textFileName);
File textFile = new File(textFilePath);
st.setCharacterStream(4, new FileReader(textFile), (int) textFile.length());
/*
* (5)对应第5个?号,二进制数据类型(文件路径:工程/WebContent/WEB-INF/file/text.txt)
*/
String blobFileName = "blob.png";
String blobFilePath = context.getRealPath("WEB-INF/file/" + blobFileName);
File blobFile = new File(blobFilePath);
st.setBinaryStream(5, new FileInputStream(blobFile), (int) blobFile.length());
// 4.执行语句
ResultSet rs = st.getGeneratedKeys();// 得到插入行的主键,只对insert有用
//int result = st.executeUpdate(); // 增删改时,可使用此方法
boolean result = st.execute(); // 增删改查,都可使用此方法
System.out.println("insert result: " + result);
} catch (SQLException | FileNotFoundException e) {
e.printStackTrace();
} finally {
close(conn, st, null);
}
}
(2)更新数据:
/**
* 4.2更新数据
*
* @param conn Connection连接对象
*/
public void update(Connection conn) {
PreparedStatement st = null;
try {
// 1.创建sql语句,name为String类型
String sql = "update user set name=? where name=?";
// 2.获取PreparedStatement
st = getPreparedStatement(conn, sql);
// 3.给占位符设置数据
st.setString(1, "刘先生"); // (1)对应第1个?号,String类型
st.setString(2, "杨先生"); // (2)对应第2个?号,String类型
// 4.执行语句
//boolean result = st.execute(); // 增删改查,都可使用此方法
int result = st.executeUpdate(); // 增删改时,可使用此方法
System.out.println("update result: " + result);
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, st, null);
}
}
(3)查询数据:
移动ResultSet指针:
//移动ResultSet指针
rs.previous(); // 移动到前一行
rs.absolute(1); // 移动到指定行
rs.beforeFirst(); // 移动ResultSet的最前面(表头)
rs.afterLast(); // 移动到ResultSet的最后面(表尾)
rs.next(); // 指向第一条,下次执行指向第二行
取对应列的数据(可使用列名或列position值,从1开始):
// 第一种方法,得到的是个Object对象
rs.getObject("列名"); // 取出该行该列的值
rs.getObject(1); // 取出该行该列的值
// 第二种方法,得到的是对应类型的值
rs.getBoolean("列名");
rs.getByte("列名");
rs.getShort("列名");
rs.getInt("列名");
rs.getLong("列名");
rs.getString("列名");
rs.getDate("列名"); // DATE,得到的是java.sql.Date类型
rs.getTime("列名"); // TIME,得到的是java.sql.Time类型
rs.getTimestamp("列名"); // TIMESTAMP,得到的是java.sql.Timestamp类型
// 大文本类型Text,longText
Reader reader = rs.getCharacterStream("列名");
// Reader reader = rs.getClob("列名").getCharacterStream();
char buffer[] = new char[1024];
int len = 0;
FileWriter writer = new FileWriter("保存文件路径");
while ((len = reader.read(buffer)) > 0) {
writer.write(buffer, 0, len);
}
// 二进制数据类型 //Blob,LongBlob
InputStream in = rs.getBinaryStream("列名");
//InputStream in = rs.getBlob("二进制文件存放列名或列号").getBinaryStream();
byte buffer[] = new byte[1024];
int len = 0;
FileOutputStream out = new FileOutputStream("保存文件路径");
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
例子:
/**
* 4.3查询数据
*
* @param conn Connection连接对象
*/
public void query(Connection conn) {
PreparedStatement st = null;
ResultSet rs = null;
try {
// 1.创建sql语句
String sql = "select * from user";
// 2.获取PreparedStatement
st = getPreparedStatement(conn, sql);
// 3.执行语句
rs = st.executeQuery();// 查询时使用executeQuery
if (rs.next()) {// 指向第一条,下次执行指向第二行
String name = rs.getString("name"); // 获取name列的值
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, st, rs);
}
}
(4)删除数据:
/**
* 44删除数据
*
* @param conn Connection连接对象
*/
public void delete(Connection conn) {
PreparedStatement st = null;
try {
// 1.创建sql语句,name为String类型 DELETE FROM 表名称 WHERE 列名称 = 值
String sql = "delete from user where name=?";
// 2.获取PreparedStatement
st = getPreparedStatement(conn, sql);
// 3.给占位符设置数据
st.setString(1, "刘先生"); // (1)对应第1个?号,String类型
// 4.执行语句
boolean result = st.execute(); // 增删改查,都可使用此方法
System.out.println("insert result: " + result);
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, st, null);
}
}
7.批处理sql语句:
(1)采用Statement批量插入:
优点:可以向数据库发送多条不同的sql语句。
缺点:sql语句没有预编译
String sql = "";
//可实现批处理sql语句
Statement.addBatch(sql)
(2)采用PreparedStatement批量插入:
优点:发送的是预编译后的SQL语句,执行效率高。
缺点:只能应用在sql语句相同,但参数不同的批处理中。
/**
* 批量插入
*
* @param conn Connection连接对象
*/
public void batchInsert(Connection conn) {
PreparedStatement st = null;
try {
String sql = "insert into user(id,name) values(?,?)";
st = conn.prepareStatement(sql);
for (int i = 2; i < 10000; i++) {
st.setInt(1, i);
st.setString(2, "杨先生" + i);
// 程序执行到此,st对象里面已经是一条完整的sql,可以加到 batch里面去了
st.addBatch();
if (i % 1000 == 0) {// batch最大可容1000条sql语句
st.executeBatch();// 执行1次batch里的所有sql,
st.clearBatch();// 清空batch里的所有sql
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, st, null);
}
}
8.在Oracle数据库中实现存二进制文件(注意:这些操作需开启事务):
(1)向列中存一个指针:
/**
* 向列中存一个指针
*/
public void insertEmptyBlob(Connection conn) {
PreparedStatement st = null;
try {
String sql = "insert into user(id,image) values(1,empty_blob())";
st = conn.prepareStatement(sql);
st.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, st, null);
}
}
(2)把指针查询出来,并将图片二进制数据存入数据库:
/**
* 把指针查询出来,并将图片二进制数据存入数据库
*/
public void queryEmptyBlob(ServletContext context, Connection conn) {
PreparedStatement st = null;
ResultSet rs = null;
try {
String sql = "select image from user where id=1 for update"; // for update是为了锁定这一行,为了不产生并发冲突
st = conn.prepareStatement(sql);
rs = st.executeQuery();
if (rs.next()) {
// 把指针查询出来
BLOB blob = (BLOB) rs.getBlob("image");
// 获取image列的输出流
OutputStream out = blob.getBinaryOutputStream();
// 获取图片文件输入流
String fileName = "header.jpg";
String filePath = context.getRealPath("WEB-INF/files/" + fileName);
File file = new File(filePath);
FileInputStream in = new FileInputStream(file);
// 将图片写入数据库image列中
byte buffer[] = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
st.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, st, null);
}
}
二、JDBC调用存储过程:
1.得到CallableStatement,并调用存储过程:
Connection conn = null;
CallableStatement cs = conn.prepareCall("{call 方法(?, ?)}");
2.设置参数,注册返回值,得到输出:
//注册返回值
cs.registerOutParameter(2, Types.VARCHAR);
//设置参数
cs.setString(1, "值");
//执行
cs.execute();
//得到输出
cs.getString("列名");
三、事务:
1.概念:
(1)事务指逻辑上的一组操作,组成这组操作的各个子操作,要不全部成功,要不全部不成功。
(2)特性ACID:
原子性(Atomicity):事务中包含的代码逻辑块不可分割。
一致性(Consistency):事务执行前后,数据完整性。
隔离性(Isolation):多个用户并发访问数据库时,数据库为每一个用户开启的事务,不被其他事务的操作影响,多个并发事务之间要相互隔离。
持久性(Durability):事务执行成功,数据持久保存到磁盘上的数据库。
(3)隔离性:
脏读:指一个事务读取了另外一个事务未提交的数据。
不可重复读:在一个事物内读取表中的某一行数据,多次读取结果不同。
和脏读的区别是:脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
虚读:是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
2.控制事务:
(1)数据库开启事务命令:
//开启事务
start transaction;
//回滚事务
rollback;
//提交事务
commit;
(2)JDBC控制事务:
//conn为Connection类
Connection conn = null;
//关闭JDBC默认提交方式,让多条SQL在一个事务中执行
conn.setAutoCommit(false);
//设置事务回滚点
Savepoint sp = conn.setSavepoint();
//此处进行SQL增删改操作
//...
//提交,回滚后必须要提交
conn.commit();
//Exception中执行回滚,回到上面设置的回滚点
conn.rollback(sp);
3.设置隔离性:
数据库四种隔离级别:
serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)
read committed:可避免脏读情况发生(读已提交)。
read uncommitted:最低级别,以上情况均无法保证。(读未提交)
(1)数据库设置隔离性命令:
//设置事务隔离级别
set session transaction isolation level repeatable read;
//查询当前事务隔离级别
select @@tx_isolation;
(2)JDBC设置隔离级别,此操作要在开启事务之前:
//conn为Connection类
Connection conn = null;
//设置隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); //串行化
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); //可重复读
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); //读已提交
conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); //读未提交
四、元数据-DataBaseMetaData:
1.返回数据库的定义信息:
try {
Connection conn = null;
DatabaseMetaData db = conn.getMetaData(); // 返回数据库元数据对象
db.getURL();// 返回一个String类对象,代表数据库的URL。
db.getUserName(); // 返回连接当前数据库管理系统的用户名。
db.getDatabaseProductName();// 返回数据库的产品名称。
db.getDatabaseProductVersion();// 返回数据库的版本号。
db.getDriverName();// 返回驱动驱动程序的名称。
db.getDriverVersion();// 返回驱动程序的版本号。
db.isReadOnly();// 返回一个boolean值,指示数据库是否只允许读操作。
} catch (SQLException e) {
e.printStackTrace();
}
2.返回表的定义信息:
try {
PreparedStatement st = null;
ParameterMetaData pd = st.getParameterMetaData();// 返回代表PreparedStatement对象的元数据
pd.getParameterCount();// 获得PreparedStatement对象传递的参数个数
pd.getParameterType(1);// 获得PreparedStatement对象传递的参数中第1个参数的sql类型
} catch (SQLException e) {
e.printStackTrace();
}
3.返回列的定义信息:
try {
ResultSet rs = null;
ResultSetMetaData rd = rs.getMetaData();// 获得代表ResultSet对象的元数据对象
rd.getColumnCount();// 返回resultset对象的列数
rd.getColumnName(1);// 获得第1列的列名
rd.getColumnTypeName(1);// 获得第1列的列数据的类型
} catch (SQLException e) {
e.printStackTrace();
}
五、CachedRowSet(离线操作数据库):
1.创建CachedRowSet:
CachedRowSetImpl cache = new CachedRowSetImpl();
2.填充CachedRowSet:
//对查出的结果集提供分页功能
cache.setPageSize(10); //取10行数据
cache.populate(rs,2); //从第2行开始取,起始值是1
3.更新CachedRowSet
调用update***()方法后,调用updateRow方法
更新至数据库,调用acceptChanges方法