文章目录
1 什么是JDBC
JDBC(Java DataBase Connectivity)就是Java数据库连接,就是用Java语言来操作数据库。JDBC是用Java语言向数据库发送SQL语句。
1、来连接数据库吖
(1)导入jar包:驱动。
(2)加载驱动类:Class.forName("classname");
(3)给出URL、username、password。
(4)使用DriverManager类来得到Connection对象。
来看示例吖:
package org.lks.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
public class MySQLDemo {
private static final String MYSQL_DATABASE_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String MYSQL_DATABASE_URL = "jdbc:mysql://localhost:3306/test_simple_practice?useSSL=false&serverTimezone=UTC";
private static final String MYSQL_DATABASE_USERNAME = "simple";
private static final String MYSQL_DATABASE_PASSWORD = "123";
public static void main(String[] args ) throws Exception {
Connection conn = null;
Class.forName(MYSQL_DATABASE_DRIVER); //加载驱动类(注册驱动)
conn = DriverManager.getConnection(MYSQL_DATABASE_URL, MYSQL_DATABASE_USERNAME, MYSQL_DATABASE_PASSWORD); //得到连接对象
System.out.println(conn);
}
}
2 JDBC原理
JDBC是一套访问数据库的规范,就是一组接口,并提供连接数据库的协议标准,然后由各个数据库厂商遵循这些规范提供一套访问自己公司的数据库服务器的API,这样的API称之为驱动。
3 JDBC完成增删改查
JDBC协议格式:jdbc:厂商的名称:子协议
,子协议由厂商自己来规定。对MySQL而言,它的子协议结构://主机:端口号/数据库名称
。
下面开始对数据库做增删改。
1、通过Connection对象创建Statement:Statement createStatement() throws SQLException
(1)Statement语句的发送器,它的功能就是向数据库发送SQL语句
2、调用它的int executeUpdate(String sql) throws SQLException
,它可以发送DML、DDL。
//通过Connection获取Statement对象
Statement stat = conn.createStatement();
//定义要执行的SQL语句
String sql = "INSERT INTO temp(sno,sname) VALUES(1001,'LK'),(1002,'HY')";
//通过executeUpdate(String)函数执行SQL语句,并返回所影响的行数
int row = stat.executeUpdate(sql);
System.out.println(row);
下面继续对数据库进行数据查询
1、通过Connection对象创建Statement
2、调用它的ResultSet executeQuery(String sql) throws SQLException
,它可以发送DQL。
3、解析ResultSet。
//通过Connection获取Statement对象
Statement stat = conn.createStatement();
//定义要执行的SQL语句
String sql = "SELECT * FROM temp";
//通过executeQuery(String)函数执行SQL语句,并返回所影响的行数
ResultSet rs = stat.executeQuery(sql);
while(rs.next()) { //判断是否有数据
int sno = rs.getInt("sno"); //获取属性列名称为sno的内容
String sname = rs.getString(2); //获取第二列的数据
System.out.println(sno + " " + sname); //输出内容
}
最后不要忘了关闭资源,采用倒关的方式,示例如下:
rs.close(); //关闭ResultSet
stat.close(); //关闭Statement
conn.close(); //关闭数据库连接
4 JDBC之代码规范化
package org.lks.jdbcutil;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class MySQLUtils {
private static final String MYSQL_DATABASE_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String MYSQL_DATABASE_URL = "jdbc:mysql://localhost:3306/test_simple_practice?useSSL=false&serverTimezone=UTC";
private static final String MYSQL_DATABASE_USERNAME = "simple";
private static final String MYSQL_DATABASE_PASSWORD = "123";
private MySQLUtils() {}
private static Connection getConnection() {
Connection conn = null;
try {
Class.forName(MYSQL_DATABASE_DRIVER);
conn = DriverManager.getConnection(MYSQL_DATABASE_URL);
}catch(SQLException e) {
e.printStackTrace();
}catch(ClassNotFoundException e) {
e.printStackTrace();
}catch(Exception e) {
e.printStackTrace();
}
return conn;
}
public static int executeUpdate(String sql) {
int result = 0;
Connection conn = null;
Statement stat = null;
if(sql == null | "".equals(sql)) {
return 0;
}
try{
conn = MySQLUtils.getConnection();
stat = conn.createStatement();
result = stat.executeUpdate(sql);
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(stat != null) {
stat.close();
}
if( conn != null) {
conn.close();
}
}catch(Exception e) {
e.printStackTrace();
}
}
return result;
}
public static ResultSet executeQuery(String sql) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
if(sql == null | "".equals(sql)) {
return rs;
}
try{
conn = MySQLUtils.getConnection();
stat = conn.createStatement();
rs = stat.executeQuery(sql);
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(stat != null) {
stat.close();
}
if( conn != null) {
conn.close();
}
}catch(Exception e) {
e.printStackTrace();
}
}
return rs;
}
}
5 结果集光标与元数据
1、DriverManager类
使用最多的就是Class.forName()
和DriverManager.getConnection()
,此代码可能出现的两种异常如下:
(1)ClassNotFoundException
:出现这个异常有两种可能
|——未给出mysql的jar包;
|——类名错误,查看类名是否是com.mysql.jdbc.Driver
,数据库版本为8.0以上版本的是com.mysql.cj.jdbc.Driver
(2)SQLException
:查看数据库URL是否出错,在考虑是否是用户名和密码错误。
2、Connection类
(1)方法定义:Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
,这两个参数是用来确定创建的Statement能生成什么样的结果集。
3、Statement类
(1)int executeUpdate(String sql) throws SQLException
:执行更新操作,即执行INSERT、UPDATE、DELETE语句,其实这个方法也可以执行CREATE TABLE、ALTER TABLE以及DROP TABLE等语句,但我们很少会使用JDBC来执行这些语句。
(2)ResultSet executeQuery(String sql) throws SQLException
:执行查询操作,执行查询操作会返回ResultSet,即结果集。
(3)boolean execute(String sql) throws SQLException
:可以执行所有的更新和查询操作,该方法返回的是boolean类型,表示SQL语句是否有结果。如果执行的是更新操作,那么可以调用int getUpdateCount() throws SQLException
来获取更新语句影响的行数。如果执行的是查询操作,那么可以调用ResultSet getResultSet() throws SQLException
获取查询结果集。
4、获取结果集元数据
(1)得到元数据:ResultSetMetaData getMetaData() throws SQLException
,返回值为ResultSetMetaData;
(2)获取结果集列数:int getColumnCount() throws SQLException
;
(3)获取指定列的列名:String getColumnName(int column) throws SQLException
Connection conn = MySQLUtils.getConnection();
String sql = "SELECT * FROM teach";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
//获取表记录的列数
int col = rs.getMetaData().getColumnCount();
System.out.println(col);
conn.close(); //关闭数据库
6 结果集特性(是否可滚动)
1、ResultSet类
下一行:默认只能使用它,其它方法存在,但不能使用,默认的结果集不可滚动。
ResultSet表示结果集,它是一个二维的表格。ResultSet内部维护一个行光标(游标),ResultSet提供了一系列的方法来移动游标:
(1)void beforeFirst() throws SQLException
:把光标放在第一行前面,即光标默认位置;
(2)void afterLast() throws SQLException
:把光标放在最后一行后面;
(3)boolean first() throws SQLException
:把光标放在第一行的位置上,返回值表示调控光标是否成功;
(4)boolean last() throws SQLException
:把光标放在最后一行的位置上;
(5)boolean isBeforeFirst() throws SQLException
:当前光标位置是否在第一行前面;
(6)boolean isAfterLast() throws SQLException
:当前光标位置是否在最后一行后面;
(7)boolean isFirst() throws SQLException
:当前光标位置是否在第一行上;
(8)boolean isLast() throws SQLException
:当前光标位置是否在最后一行上;
(9)boolean previous() throws SQLException
:把光标向前挪一行;
(10)boolean next() throws SQLException
:把光标向后挪一行;
(11)boolean relative(int rows) throws SQLException
:相对位移,当row为正数时,表示向下移动row行,为负数时表示向上移动row行;
(12)boolean absolute(int row) throws SQLException
:绝对位移,把光标移动到指定的行上;
(13)int getRow() throws SQLException
:返回当前光标所有行。
上面的方法分为两类,一类用来判断游标位置的,另一类是用来移动游标的。如果结果集是不可滚动的,那么只能使用next()方法来移动游标,而其它方法都不可使用。下面演示如何获取表中记录数:
Connection conn = MySQLUtils.getConnection();
String sql = "SELECT * FROM teach";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
//获取表记录的行数
if(rs.last()) {
int rowTotal = rs.getRow();
System.out.println(rowTotal);
}
conn.close();
结果集是否可以滚动,需要再创建Statement
时指定是否可滚动。
2、结果集特性
当使用Connection
的createStatement()
方法时,已经确定了Statement
生成的结果集是什么特性。
(1)是否可滚动
(2)是否敏感
(3)是否可更新
默认情况下,createStatement()生成的结果集不可滚动、不敏感、不可更新。
使用有两个参数的重载方法时,Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
(1)第一个参数:
|——ResultSet.TYPE_FORWARD_ONLY:不滚动结果集;
|——ResultSet.TYPE_SCROLL_INSENSITIVE:滚动结果集,不敏感(数据集数据不会跟随数据库而变化)
|——ResultSet.TYPE_SCROLL_SENSITIVE:滚动结果集,敏感(不常用,各大厂商并未实现,与不敏感无异)
(2)第二个参数:
|—— ResultSet.CONCUR_READ_ONLY:结果集是只读的,不能通过修改结果集而反向影响数据库;
|——ResultSet.CONCUR_UPDATABLE:结果集是可更新的,对结果集的更新可以反向影响数据库。
MySQL默认可滚动的。
7 PreparedStatement的用法
1、PreparedStatement类
(1)是Statement接口的子接口。
(2)扩展:
|——防SQL攻击;
|——提高代码的可读性,可维护性;
|——提高效率。
2、什么是SQL攻击
在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句。
3、演示SQL攻击
package org.lks.jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.lks.jdbcutil.MySQLUtils;
public class MySQLDemo {
public static void main(String[] args ) throws Exception {
//模拟SQL攻击,可以发现数据库中并没有此信息,返回值却是true
//此输入生成的SQL语句:SELECT * FROM user_info WHERE username='a' OR 'a'='a' AND password='a' OR 'a'='a';
System.out.println(login("a' OR 'a'='a","a' OR 'a'='a"));
}
public static boolean login(String username, String password) {
Connection conn = null;
// String sql1 = "CREATE TABLE IF NOT EXISTS user_info(username char(3),password char(8))";
// String sql2 = "INSERT INTO user_info(username,password) VALUES('lks','19961015'),('hy','199908')";
String sql3 = "SELECT * FROM user_info WHERE username='" + username + "' AND " + "password='" + password + "'";
System.out.println(sql3);
try {
conn = MySQLUtils.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql3);
if( rs.next() ) {
return true;
}else {
return false;
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //关闭数据库
}
return false;
}
}
4、PreparedStatement的用法
(1)得到PreparedStatement对象
|——定义sql语句,所有的参数使用?
替代;
|——调用Connection的PreparedStatement prepareStatement(String sql) throws SQLException
;
|——调用PreparedStatement类中的setXxx()对象方法为sql语句中的?
赋值;
|——调用PreparedStatement类中的executeUpdate()或executeQuery(),但它的方法都没有参数。
package org.lks.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.lks.jdbcutil.MySQLUtils;
public class MySQLDemo {
public static void main(String[] args ) throws Exception {
//使用PreparedStatement来防止SQL攻击
System.out.println(login("a' OR 'a'='a","a' OR 'a'='a")); //false
}
public static boolean login(String username, String password) {
Connection conn = null;
// String sql1 = "CREATE TABLE IF NOT EXISTS user_info(username char(3),password char(8))";
// String sql2 = "INSERT INTO user_info(username,password) VALUES('lks','19961015'),('hy','199908')";
String sql3 = "SELECT * FROM user_info WHERE username=? AND password=?";
try {
conn = MySQLUtils.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql3);
pstmt.setString(1,username);
pstmt.setString(2,password);
ResultSet rs = pstmt.executeQuery();
if( rs.next() ) {
return true;
}else {
return false;
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //关闭数据库
}
return false;
}
}
8 预处理的原理
1、服务器的工作
(1)校验sql语句的语法。
(2)编译:一个与函数相似的东西。
(3)执行:调用函数。
2、PreparedStatement
(1)前提:连接的数据库必须支持预处理(几乎没有不支持的)。
(2)每个pstmt都与一个sql模板绑定在一起,先把sql模板给数据库,数据库先进行校验,在进行编译。执行时只是把参数传递过去而已。
(3)若二次执行时,就不用再次校验语法,也不用再此编译,直接执行。
9 mysql的预编译功能默认是关
1、useServerPrepStmts
参数
默认使用PreparedStatement是不能执行预编译的,这需要在url中给出useServerPrepStmts=true
参数(MySQL Server 4.1之前的版本是不支持预编译的,而Connector/J在5.0.5以后的版本,默认是没有开启预编译功能的)。这样才能保证mysql驱动会把SQL语句发送给服务器进行预编译,然后在执行executeQuery时只是把参数发送给服务器。
2、cachePrepStmts
参数
当使用不同的PreparedStatement对象来执行相同的SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数key,导致二次编译。如果希望缓存编译后函数的key,那么就要设置cachePrepStmts
参数为true。
3、打开批处理
MySQL的批处理也需要通过参数来打开:rewriteBatchedStatements=true
。
10 JdbcUtils1.0小工具
databaseconfig.properties配置文件:
databaseDriver=com.mysql.cj.jdbc.Driver
databaseURL=jdbc:mysql://localhost:3306/test_simple_practice?useSSL=true&serverTimezone=UTC
databaseUsername=simple
databasePassword=123
JDBCUtils类:
package org.lks.jdbcutil;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static Properties properties = null;
static {
InputStream input = null;
try {
input = JDBCUtils.class.getClassLoader().getResourceAsStream("databaseconfig.properties");
JDBCUtils.properties = new Properties();
JDBCUtils.properties.load(input);
Class.forName(properties.getProperty("databaseDriver"));
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(input != null) {
try {
input.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static Connection getConnection() {
Connection conn = null;
String url = properties.getProperty("databaseURL");
String user = properties.getProperty("databaseUsername");
String password = properties.getProperty("databasePassword");
try {
conn = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
}
11 面向接口编程
1、DAO模式
DAO(Data Access Object)模式就是写一个类,把访问数据库的代码封装起来。DAO在数据库与业务逻辑(Service)之间。
(1)实体域,即操作的对象,例如我们操作的表是user表,那么就需要先写一个User类;
(2)DAO模式需要先提供一个DAO接口;
(3)然后再提供一个DAO接口实现类;
(4)在编写一个DAO工厂,Service通过工厂来获取DAO实现
2、步骤:
DAO包:
(1)创建UserDao接口。
|——public void addUser(User user)
|——public User findUserByUsername(String username)
package org.lks.loginandregister.dao;
import org.lks.loginandregister.domain.User;
public interface IUserDao {
public User findUserByUsername(String username);
public void addUser(User user);
}
(2)创建UserDaoImpl实现UserDao接口
(3)创建DaoFactory工厂类
|——static UserDao getUserDao():
Service包:
(1)创建UserService类
|——void regist(User user)
|——User login(User user)
(2)创建UserException类
Domain包:
(1)创建User类
12 util包下的Date与sql包下的Date
1、Java中的时间类型
(1)java.sql包下给出三个与数据库相关的日期时间类型,分别是:
|——Date:表示日期,只有年月日,没有时分秒,会丢失时间。
|——Time:表示时间,只有时分秒,没有年月日,会丢失日期。
|——Timestamp:表示时间戳,有年月日时分秒,以及毫秒。
以上三个类都是java.util.Date类的子类。
2、数据库类型与java中类型的对应关系
DATE→java.sql.Date
TIME→java.sql.Time
TIMESTAMP→java.sql.Timestamp
(1)领域对象(domain)中的所有属性不能出现java.sql包下的东西,即不能使用java.sql.Date。
(2)ResultSet.getDate()返回的是java.sql.Date。
(3)PreparedStatement.setDate(int,Date)中的Date是java.sql.Date。
3、时间类型的转换
(1)java.util.Date→java.sql.Date、java.sql.Time、java.sql.Timestamp
|——把util的Date转换为毫秒值,long times = new Date().getTime();
|——使用毫秒值创建sql的Date、Time、Timestamp
(2)java.sql.Date、java.sql.Time、java.sql.Timestamp→java.util.Date
|——这一步不用处理了,因为sql中的时间类型都是util中的Date的子类。
13 大数据
目标:把mp3保存到数据库。
在my.ini中添加如下配置,否则大文件添加数据库会失败。
max_allowed_packet=10485760
1、什么是大数据
所谓大数据,就是指大的字节数据,或大的字符数据。标准SQL中提供了如下类型来保存大数据类型:
类型 | 长度 |
---|---|
tinyblob | 28-1B(256B) |
blob | 216-1B(64KB) |
mediumblob | 224-1B(16MB) |
longblob | 232-1B(4GB) |
tinyclob | 28-1B(256B) |
clob | 216-1B(64KB) |
mediumclob | 224-1B(16MB) |
longclob | 232-1B(4GB) |
但是mysql中没有提供tinyclob、clob、mediumclob、longclob四种类型,而是使用如下四种类型来处理文本大数据。
类型 | 长度 |
---|---|
tinytext | 28-1B(256B) |
text | 216-1B(64KB) |
mediumtext | 224-1B(16MB) |
longtext | 232-1B(4GB) |
保存音频文件在数据库:
public static void saveFile() {
File file = new File("D:\\Users\\victor\\Music\\P.S.我爱你 - A-Lin.mp3");
String fileName = "P.S.我爱你 - A-Lin.mp3";
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
String sql = "INSERT INTO BIG_FILE VALUE(?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1);
pstmt.setString(2, fileName);
pstmt.setBlob(3, new SerialBlob(new FileInputStream(file).readAllBytes()));
pstmt.executeUpdate();
}catch(Exception e) {
e.printStackTrace();
}finally {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
下图可以看到已经成功保存:
从数据库中读取文件:
public static void getFile() {
Connection conn = null;
File file = new File("D:" + File.separator + "love.mp3");
try {
conn = JDBCUtils.getConnection();
String sql = "SELECT * FROM BIG_FILE";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
if(rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
Blob blob = rs.getBlob(3);
System.out.println(id + " "+ name);
FileUtil.copy(blob.getBinaryStream(), new FileOutputStream(file));
}
}catch(Exception e) {
e.printStackTrace();
}finally {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
14 批处理
1、Statement批处理
批处理就是一批一批的处理,而不是一个一个处理。即一次向服务器发送多条SQL语句,然后由服务器一次性处理。
批处理只针对更新操作。可以多次调用Statement类的addBatch(String sql)方法,把需要执行的所有SQL语句添加到一个“批”中,然后调用Statement类的executeBatch()方法来执行当前“批”中的语句。
(1)void addBatch(String sql) throws SQLException
:添加sql语句到批处理集合中。
(2)int[] executeBatch() throws SQLException
:执行批处理的sql语句。
(3)void clearBatch() throws SQLException
:清空批处理集合。
2、PreparedStatement批处理
方法:
(1)void addBatch() throws SQLException
:添加sql语句到批处理集合中。
(2)int[] executeBatch() throws SQLException
:执行批处理的sql语句。
(3)void clearBatch() throws SQLException
:清空批处理集合。
@Test
public void fun() {
String sql = "INSERT INTO TEST_INFO VALUE(?,?,?)";
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
for(int i = 0; i < 10000; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, "stu_" + i);
pstmt.setString(3, i%2==0?"男":"女");
pstmt.addBatch();
}
long start = System.currentTimeMillis();
pstmt.executeBatch();
long end = System.currentTimeMillis();
System.out.println(end - start);
}catch(Exception e) {
e.printStackTrace();
}finally {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}