概述
很多时候数据不可能在内存中报错数据毕竟内存只要重启数据就会被擦掉,而且也不可能存储很多数据,虽然现在的内存条比以往大太多了。哪怕我电脑内存条64G也无法存储大量的数据。
所以说JDBC就是将这些数据持久化。把数据保存到可掉电式存储设备中义工之后使用。大多数情况下,特别是企业级应用数据持续化意味着将内存中的数据保存到硬盘上,而一般持久化的实现过程大多数通过各种关系数据库来完成。
既然在java中聊持久化,那自然就是通过Java对数据库进行一些操作。而这些具体分:
-
JDBC直接访问数据库
-
JDO(Java Data Object)技术
-
第三方O/R工具,如Mybatis,Hibernate等
JDBC是Java访问数据库的基石,JDO,Mybatis等只是更好的封装JDBC.
JDBC介绍
JDBC(Java Database Connectivity)是一种独立特定数据管理习惯,通用的SQL来操作数据存取修改等行为的接口。定义了用来访问数据库的标准Java类库,使用这些类库可以用一种标准的方法方便的访问数据库资源。
但是数据库不单单是一种,有MYSQL和ORALCE等,所以JDBC为访问不同的数据库提供了一种统一的途径,毕竟JDBC的目标就是JAVA开发者可以使用JDBC连接任何使用了JDBC驱动的数据库系统,这样无序开发者对特定的数据库系统的特点有过多了解,从而大大简化和加快了开发过程。
如果没有JDBC,那么JAVA需要直接对用所有的数据库如下:
但是现实情况如下:
说白了就是几个大厂和sun公司商量一些,你们满足我提供接口,然后提供一个驱动,然后让开发者几乎可用相同的方法去操作数据库,而只需要换一下不同的数据库驱动即可。
具体如何使用,图示如下:
驱动器–Driver
Java.sql.Driver接口是对所有的JDBC驱动程序定义的要实现的接口,而不同的数据库厂商提供不同的实现。而程序不允许直接去访问实现Driver接口的类,而是由驱动程序管理器类(Java.sql.DriverManager)去调用这游戏Driver实现。
- Oracle的驱动: oracle.jdbc.driver.OracleDriver
- Mysql的驱动:com.mysql.jdbc.Driver
因为演示通过MYsql演示所以自然就是导入om.mysql.jdbc.Driver
演示创建连接
这个还是老规矩直接演示比较靠谱,但是导入也分好几种,依次进行演示
方式1
// 引入包了,所以自然要直接导入mysql的驱动
Driver driver=new com.mysql.jdbc.Driver();
// 创建一个地址,毕竟来连接数据库,需要一个地址的
// jdbc:mysql 是协议名,比如http和http的一样
// localhost :这个是ip地址,也就是本机,如果是mysql在其他机子上可以将这个变成服务器所在的ip
// 3306 :mysql 默认的端口
// test : 目标数据库名
String url="jdbc:mysql://localhost:3306/test";
Properties info=new Properties();
info.setProperty("user","root");
info.setProperty("password","root");
// 这个会报一个错误,直接将其抛出
Connection connection=driver.connect(url,info);
System.out.println(connection);
方式2
上一个是直接通过new第三方的类创建驱动,但是有时候为了不显示第三方的类,会通过反射创建这个第三方的驱动;
// 引入包了,所以自然要直接导入mysql的驱动
Class driverclass=Class.forName("com.mysql.jdbc.Driver");
Driver driver= (Driver) driverclass.newInstance();
// 创建一个地址,毕竟来连接数据库,需要一个地址的
// jdbc:mysql 是协议名,比如http和http的一样
// localhost :这个是ip地址,也就是本机,如果是mysql在其他机子上可以将这个变成服务器所在的ip
// 3306 :mysql 默认的端口
// test : 目标数据库名
String url="jdbc:mysql://localhost:3306/test";
Properties info=new Properties();
info.setProperty("user","root");
info.setProperty("password","root");
// 这个会报一个错误,直接将其抛出
Connection connection=driver.connect(url,info);
System.out.println(connection);
这样写的意义说白,有点脱裤子放屁的意思,但是有时候就会有这样的要求。不过人家提出这样的规范自然尤其存在的意义,所以不必纠结。还是那句话明白最好,不明白就遵守后面慢慢了解。
方式3
前面说的了一个类驱动管理类(DriverManager),那么自然可以通过这个类对驱动进行管理。先看一下官方文档。
可以看出驱动管理类可以直接得到connection类,但是如果将驱动管理类和驱动关联起来呢?将其注册给驱动管理类即可:
// 引入包了,所以自然要直接导入mysql的驱动
Class driverclass=Class.forName("com.mysql.jdbc.Driver");
Driver driver= (Driver) driverclass.newInstance();
// 将驱动注册给驱动管理器
DriverManager.registerDriver(driver);
String url="jdbc:mysql://localhost:3306/test";
Properties info=new Properties();
String user="root";
String password="root";
Connection connection=DriverManager.getConnection(url,user,password);
System.out.println(connection);
方式4
其实这个是方式3的简化,有时候会这样写:
// 引入包了,所以自然要直接导入mysql的驱动
Class driverclass=Class.forName("com.mysql.jdbc.Driver");
Driver driver= (Driver) driverclass.newInstance();
// 将驱动注册给驱动管理器
// DriverManager.registerDriver(driver); 不注册了
String url="jdbc:mysql://localhost:3306/test";
Properties info=new Properties();
String user="root";
String password="root";
Connection connection=DriverManager.getConnection(url,user,password);
System.out.println(connection);
可以看出没有DriverManager.registerDriver(driver)
依然可以得到连接。为什么?
打开com.mysql.jdbc.Driver
的源码可以看出,有一个static代码块,人家会自动帮你注册一个。
-
补充
如果在省略一点可以吗?
// 引入包了,所以自然要直接导入mysql的驱动 // Class driverclass=Class.forName("com.mysql.jdbc.Driver"); // Driver driver= (Driver) driverclass.newInstance(); // 将驱动注册给驱动管理器 // DriverManager.registerDriver(driver); 不注册了 String url="jdbc:mysql://localhost:3306/test"; Properties info=new Properties(); String user="root"; String password="root"; Connection connection=DriverManager.getConnection(url,user,password); System.out.println(connection);
竟然也可以啊,为什么呢?
因为人家本身就帮你配置好了:
但是一般不要如此省略,因为这种省略只能在Mysql中可以,如果Oralce就不行,因为oracle却没有这样帮你设置这个配置文件。
方式5
其实很多配置信息会单独放在一个配置文件下面的:
比如建立一个mysql.properies.
user = root
password = root
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/test
然后写JDBC
Properties properties=new Properties();
properties.load(jdbc.class.getClassLoader().getResourceAsStream("mysql.properies"));
String user=properties.getProperty("user");
String password=properties.getProperty("password");
String url=properties.getProperty("url");
String drivername=properties.getProperty("driver");
// 引入包了,所以自然要直接导入mysql的驱动
Class driverclass=Class.forName(drivername);
Driver driver= (Driver) driverclass.newInstance();
Connection connection=DriverManager.getConnection(url,user,password);
System.out.println(connection);
演示操作数据库
上面演示了如何连接数据库,但是连接数据库的目的就是操作数据库,而操作数据库需要接口:Statement
但是一般的时候,在开发的时候不会使用Statement这个接口,而是使用其子接口:PreparedStatement。因为Statement接口不安全,下面演示一下;
使用 Statement
首先在msyql中创建一个数据库test,然后创建一个表如下:
如果不了解,就看一下发表的的mysql基础文章。
CREATE DATABASE IF NOT EXISTS test
USE test;
CREATE TABLE IF NOT EXISTS uer_table(
user_name VARCHAR(10),
user_psd VARCHAR(10)
);
# 插入一条数据到数据库
INSERT INTO uer_table VALUES ('haoren','123456');
然后写一个Java进行测试
public class jdbc {
public static void main(String[] args) throws Exception {
test();
}
public static void test(){
String userName="";
String userPassWord="";
Connection con=null;
Statement sts=null;
ResultSet rst=null;
try {
con=getCon();
sts=con.createStatement();
String sql="SELECT user_name, user_psd FROM test.uer_table WHERE user_name='"+userName+"' AND user_psd='"+userPassWord+"'";
rst=sts.executeQuery(sql);
if(rst.next()){// rst 可以这样理解 :和迭代器的结构有点相似
System.out.println("数据库里面有这个人");
}else{
System.out.println("数据库没有返回符号条件的人");
}
}catch (Exception e){
System.out.println(e);
}finally {
try {
rst.close();
sts.close();
con.close();
}catch (Exception e ){
}
}
}
// 将返回连接的单独放在这个方法里面
public static Connection getCon(){
Connection connection=null;
try{
Properties properties=new Properties();
properties.load(jdbc.class.getClassLoader().getResourceAsStream("mysql.properies"));
String user=properties.getProperty("user");
String password=properties.getProperty("password");
String url=properties.getProperty("url");
String drivername=properties.getProperty("driver");
// 引入包了,所以自然要直接导入mysql的驱动
Class driverclass=Class.forName(drivername);
Driver driver= (Driver) driverclass.newInstance();
connection=DriverManager.getConnection(url,user,password);
return connection;
}catch (Exception e){
System.out.println(e);
}
return connection;
// System.out.println(connection);
}
}
然后不停的修改上面:
String userName="haoren";
String userPassWord="123456";
然后这样调用:
String userName="haoren1";
String userPassWord="123456";
因为数据库只有一条数据,为什么说不安全呢?现在变成这样
String userName=" ' OR ' ";
String userPassWord="'=1 OR 1='1";
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tWswnszo-1645596909343)(F:\文档\笔记\java\基础\28:jdbc.assets\image-20220218152028587.png)]
其实这个本质就是用来一种简单sql注入方式而已,很多时候这个就是不安全。所以更多是时候使用的是其子类接口。
PreparedStatement
其实已经预编译了官方文档说的,这个就不再具体解释了。
为什么说preparedstatement安全,现在演示一下还是用上面的代码,如下:
public class jdbc {
public static void main(String[] args) throws Exception {
test();
}
public static void test(){
String userName="";
String userPassWord="";
Connection con=null;
PreparedStatement psts=null;
ResultSet rst=null;
try {
con=getCon();
String sql="SELECT user_name, user_psd FROM test.uer_table WHERE user_name=? AND user_psd=? "; // ? 是占位符, 就是将这个位置作为一个条件值站住
psts= con.prepareStatement(sql);
psts.setString(1,userName);
psts.setString(2,userPassWord);
rst=psts.executeQuery();
if(rst.next()){
System.out.println("数据库里面有这个人");
}else{
System.out.println("数据库没有返回符号条件的人");
}
}catch (Exception e){
System.out.println(e);
}finally {
try {
rst.close();
psts.close();
con.close();
}catch (Exception e ){
}
}
}
public static Connection getCon(){
Connection connection=null;
try{
Properties properties=new Properties();
properties.load(jdbc.class.getClassLoader().getResourceAsStream("mysql.properies"));
String user=properties.getProperty("user");
String password=properties.getProperty("password");
String url=properties.getProperty("url");
String drivername=properties.getProperty("driver");
// 引入包了,所以自然要直接导入mysql的驱动
Class driverclass=Class.forName(drivername);
Driver driver= (Driver) driverclass.newInstance();
connection=DriverManager.getConnection(url,user,password);
return connection;
}catch (Exception e){
System.out.println(e);
}
return connection;
// System.out.println(connection);
}
}
现在进行测试:
String userName="haoren";
String userPassWord="123456";
String userName="haoren1";
String userPassWord="123456";
String userName=" ' OR ' ";
String userPassWord="'=1 OR 1='1";
可以看出preparedstatement在创建的时候用的sql其中有?
,这个其实是占位符,意思这个地方传入的值就是相当于SQL中在=
后面’‘
中间添加数据,哪怕是OR 这样的逻辑运算符符号也会成为引号内的值。
看一下两个方法:
不过SQL语句中有比如删除,更新,插入不用返回值,如果需要返回值的话也就是一个false和true来证明其是否执行成功所以用方法:execute()
如果是查询语句的话,就需要有一个返回的类似集合的类对象保存数据了,就要用到:executeQuery()
不过还有一个
ResultSet
其实这个前面也演示,那就是可以如果是查询语句的话,可以得到查询语句的数据,方便Java进行处理。虽然编程语言中都是从0开始但是这个取出的数据是从1开始的。这个大家需要注意一下。
上面的Connection,ResultSet和preparedstatement
记住都要关闭,毕竟其 操作也是有连接的,这个是IO操作的流有点相似。
这个需要注意ResultSet中两个方法:
ResultSetMetaData 可用于获取有关ResultSet
对象中列的类型和属性的信息的对象。
补充-通用的更新方法
public static void prepareStamentUtil(String sql, String ... arg){
Connection con=null;
PreparedStatement prst=null;
try {
con=getCon();
prst=con.prepareStatement(sql);
int len =arg.length;
for (int i = 0; i < len; i++) {
prst.setObject(i,arg[i]);
}
prst.execute();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
prst.close();
con.close();
}catch (Exception e ){
e.printStackTrace();
}
}
}
但是如果是查询数据的话,需要如下:
// 如果是集合的话,那就按照自己的要求来修改,这个利用的反射
public static <T> T prepareStamentUtil(String sql,Class<T> classz, String ... arg){
Connection con=null;
PreparedStatement prst=null;
ResultSet rst=null;
T t=null;
try {
con=getCon();
prst=con.prepareStatement(sql);
int len =arg.length;
for (int i = 0; i < len; i++) {
prst.setObject(i,arg[i]);
}
rst=prst.executeQuery();
ResultSetMetaData rstdata =rst.getMetaData();
// 这个有两个方法可以得到SQL中的字段名,如果SQL中的字段名和类的属性最好一样
//
// rstdata.getColumnName(int column) 如果SQL语句字段名不一样,使用AS得到别名和类的属性一样 这个就无法使用这个方法了
// rstdata.getColumnLabel(int column) 这个得到无论是否别名都可以得到字段名
int colmumnConut=rstdata.getColumnCount();
while(rst.next()) {
t=classz.newInstance();
for (int i = 0; i < colmumnConut; i++) {
String colmumnName=rstdata.getColumnLabel(1+i);
Field field=classz.getDeclaredField(colmumnName);
field.setAccessible(true);
field.set(t,rst.getObject(i+1));
}
}
return t;
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
rst.close();
prst.close();
con.close();
}catch (Exception e ){
e.printStackTrace();
}
}
return t;
}
其实上面的方法可以以及链接数据库可以单独弄一个工具类的,那样会更加方便。
批量操作
在JDBC其实在插入大量的数据时候,有些方式会加快这个速度,现在演示。
# 首先创建一个一个测试表 这个表就是测试,没有什么意义
CREATE TABLE test_p1(
num INT,
t_name VARCHAR(10)
)
首先用Statement
测试;
public static void test_Statement(){
Connection con=null;
Statement stm=null;
try {
long start=System.currentTimeMillis();
con=getCon();
stm=con.createStatement();
for (int i = 0; i < 20000; i++) {
String sql="INSERT INTO yuarenxue.test_p1 VALUES('"+i+"', '张"+i+"')";
stm.execute(sql);
}
long end=System.currentTimeMillis();
System.out.println("Statement用的时间为: "+(end-start));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stm.close();
con.close();
}catch (Exception e ){
}
}
}
记得将数据库表中数据删除,然后再试一下,这样排除了更多的干扰因素。
public static void test_PreparedStatement(){
Connection con=null;
PreparedStatement pstm=null;
try {
long start=System.currentTimeMillis();
con=getCon();
String sql="INSERT INTO yuarenxue.test_p1 VALUES(?, ?)";
pstm=con.prepareStatement(sql);
for (int i = 0; i < 20000; i++) {
pstm.setObject(1,i);
pstm.setObject(2,"张"+i);
pstm.execute();
}
long end=System.currentTimeMillis();
System.out.println("PreparedStatement用的时间为: "+(end-start));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
pstm.close();
con.close();
}catch (Exception e ){
}
}
}
因为PreparedStatement会将sql预编译,所以要比Statement更快。
真正的批处理
#这个能首先说一下 MYSQL服务器默认是关闭批处理的所以
#需要一个参数开启批处理
url = jdbc:mysql://localhost:3306/test?rewriteBatchedStatments=true
然后还有三个方法:
- addBatch():
- executeBatch():
- clearBatch():
public static void test_Batch(){
Connection con=null;
PreparedStatement pstm=null;
try {
long start=System.currentTimeMillis();
con=getCon();
String sql="INSERT INTO yuarenxue.test_p1 VALUES(?, ?)";
pstm=con.prepareStatement(sql);
for (int i = 0; i < 20000; i++) {
pstm.setObject(1,i);
pstm.setObject(2,"张"+i);
pstm.addBatch();
if ((i+1)%500==0){
pstm.executeBatch();
pstm.clearBatch();
}
}
long end=System.currentTimeMillis();
System.out.println("批处理用的时间为: "+(end-start));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
pstm.close();
con.close();
}catch (Exception e ){
}
}
}
当然上面写的是(i+1)%500==0
也可以控制整个500变成更大的数字减少交互变得更快,还有一种提高的方式,那就是因为MYSQL是自动提交的,所以可以手动提交如下:
public static void test_Batch(){
Connection con=null;
PreparedStatement pstm=null;
try {
long start=System.currentTimeMillis();
con=getCon();
// 因为mysql会自动更新,所以需要关闭才能使用 如果不设置为关闭也可以但是
con.setAutoCommit(false);
String sql="INSERT INTO yuarenxue.test_p1 VALUES(?, ?)";
pstm=con.prepareStatement(sql);
for (int i = 0; i < 20000; i++) {
pstm.setObject(1,i);
pstm.setObject(2,"张"+i);
pstm.addBatch();
if ((i+1)%500==0){
pstm.executeBatch();
pstm.clearBatch();
}
}
con.commit();
long end=System.currentTimeMillis();
System.out.println("手动提交批处理用的时间为: "+(end-start));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
pstm.close();
con.close();
}catch (Exception e ){
}
}
}
因为篇幅有点长,所以分一下,现在了解一下JDBC,后面再了解一下事务。