JDBC连接MySQL

JDBC连接MySQL学习

1.JDBC编程6步

1.注册驱动

2.获取连接

3.获取数据库操作对象

4.执行SQL语句(DQL DML)

5.处理查询结果(执行select语句的时候)

6.释放资源

2.注册驱动

驱动由数据库厂提供,我们需要做的就是通过接口来调用方法。要用Java对数据库进行操作,我们首先需要有数据库厂家提供驱动架包,驱动架包可以通过对应数据库厂家的官网首页下载。

这里的驱动并不是所谓的电脑驱动的硬件层面的,这里的驱动就是一堆jar架包,这些架包都是数据库厂家的Java工程师编写的,我们开发人员不用考虑数据库底层是如何实现的,我们要关系的是,Java提供的数据库API接口,我们是通过接口来调用方法来对数据库进行操作。

这里我选择的是MySQL数据库,所以我事先已将架包导入到项目里面了,如果发现有一些类无法加载,请考虑是否已经导入架包。不导入架包,将无法运行以下代码。

import java.sql.*;   
//我们导入的java包的Driver等其他类
public class JDBtest1{
	public status void main(String args[]){
	Driver driver = null;
	try{
		//注册驱动
		//带包名(属于mysql的架包)
		//注册驱动的目的是要让Java程序知道你要使用的是那个数据库。
		//com.mysql.jdbc.Driver属于MySQL驱动提供的。
		//driver = new com.mysql.cj.jdbc.Driver();   mysql6以上使用
		driver = new com.mysql.jdbc.Driver();
		DriverManger.registerDriver(driver); 
	}catch(SQLException e){
		e.printStackTrace();
	}
		
	}
}
3.获取连接

我们使用DriverManager的getConnection方法来连接数据库,获取连接前首先要知道你的服务器的ip地址并且需要知道你服务器段的数据库端口,一般是3306端口。然后用过url来对数据库进行连接。还有你必须拥有数据库的管理权限,对应的安全组也要开放,要不然是无法连接数据库的。还有实例(主机)的防火墙。

import java.sql.*;   
//我们导入的java包的Driver等其他类
public class JDBtest1{
	public status void main(String args[]){
	Driver driver = null;
	Connection conn = null;
	try{
		//1.注册驱动
		//带包名(属于mysql的架包)
		driver = new com.mysql.jdbc.Driver();
		DriverManger.registerDriver(driver);
		//DriverManger.registerDriver(new com.mysql.jdbc.Driver()); 
		
		//2.获取连接
		/*
			url:统一资源定位符
			url=协议+服务器ip+服务器软件端口+资源名
			协议:通讯协议是通讯数据传送格式,规定了数据包传送的格式。
		*/
		String url = "jdbc:mysql://127.0.0.1:3306/node1";
		String user = "root";     
		String password = "123456";
		conn= DriverManger.getConnection(url,user,password);
		System.out.println("数据库连接对象:"+conn);
		
	}catch(SQLException e){
		e.printStackTrace();
	}
		
	}
}
4.获取数据库操作对象和执行sql

连接到数据库之后我们需要获取数据库对象,因为我们Java是以对象实例进行操作的,所以我们连接到数据库之后我们需要创建数据库操作对象。我们继续在上面的代码基础上进行更改。这里我使用Connection接口的createStatement方法获取数据操作对象。

import java.sql.*;   
//我们导入的java包的Driver等其他类
public class JDBtest1{
	public status void main(String args[]){
	Driver driver = null;
	Connection conn = null;
	Statement stm = null;
	try{
		//1.注册驱动
		//带包名(属于mysql的架包)
		driver = new com.mysql.jdbc.Driver();
		DriverManger.registerDriver(driver);
		//DriverManger.registerDriver(new com.mysql.jdbc.Driver()); 
		
		//2.获取连接
		/*
			url:统一资源定位符
			url=协议+服务器ip+服务器软件端口+资源名
			协议:通讯协议是通讯数据传送格式,规定了数据包传送的格式。
		*/
		String url = "jdbc:mysql://127.0.0.1:3306/node1";
		String user = "root";     
		String password = "123456";
		Connection conn= DriverManger.getConnection(uri,user,password);
		System.out.println("数据库连接对象:"+conn);
		
		
		//3.获取数据库操作对象
		stm = conn.createStatement();    //存在sql注入风险
		
		//4.执行sql(这里执行插入语句)
		string sql = "INSERT INTO n VALUES (1, 'tom', '23'), (2, 'john', '22');"
		int count = stm.executeUpdate(sql);
		
	}catch(SQLException e){
		e.printStackTrace();
	}
		
	}
}
5.释放资源

释放资源需要逐个释放,首先释放资源Statement,然后释放资源Connection.是一层层的释放。

import java.sql.*;

public class JDBtest1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Driver driver = null;
		Connection conn = null;
		Statement stm = null;
		try{
			//1.注册驱动
			//带包名(属于mysql的架包)
			driver = new com.mysql.cj.jdbc.Driver();
			DriverManager.registerDriver(driver);
			
			
			//DriverManger.registerDriver(new com.mysql.jdbc.Driver()); 
			
			//2.获取连接
			/*
				url:统一资源定位符
				url=协议+服务器ip+服务器软件端口+资源名
				协议:通讯协议是通讯数据传送格式,规定了数据包传送的格式。
			*/
			//JDBC连接Mysql6 com.mysql.cj.jdbc.Driver, 需要指定时区serverTimezone
			String url = "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=UTC";
			String user = "root" ;    
			String password = "A258258258";
			conn= DriverManager.getConnection(url,user,password);
			System.out.println("数据库连接对象:"+conn);
			
			
			//3.获取数据库操作对象
			stm = conn.createStatement();		//存在sql注入风险
			
			//4.执行sql(这里执行插入语句)
			String sql = "INSERT INTO Student VALUES (3, 'tom'), (4, 'john');";
			int count = stm.executeUpdate(sql);
			
		}catch(SQLException e){
			e.printStackTrace();
		}finally {
			//6.释放资源(逐个释放)
			try {
				if(stm!=null)
					stm.close();
			} catch (SQLException e2) {
				// TODO: handle exception
				e2.printStackTrace();
			}
			
			try {
				if(conn!=null)
					conn.close();
			} catch (SQLException e3) {
				// TODO: handle exception
				e3.printStackTrace();
			}
			
		}
	}

}
6.MySQL6以上Drever问题

这里转载博文:https://blog.csdn.net/f2006116/article/details/81224569

com.mysql.jdbc.Driver 是 mysql-connector-java 5中的,
com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6中的

1、JDBC连接Mysql5 com.mysql.jdbc.Driver:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
username=root
password=

2、JDBC连接Mysql6 com.mysql.cj.jdbc.Driver, 需要指定时区serverTimezone:

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
username=root
password=

在设定时区的时候,如果设定serverTimezone=UTC,会比中国时间早8个小时,如果在中国,可以选择Asia/Shanghai或者Asia/Hongkong,例如:

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?serverTimezone=Shanghai&?useUnicode=true&characterEncoding=utf8&useSSL=false
username=root
password=

备注:

I、如果mysql-connector-java用的6.0以上的,如下:

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

但是你的driver用的还是com.mysql.jdbc.Driver,就会报错:

Loading class 'com.mysql.jdbc.Driver'. This is deprecated. The new 
driver class is 'com.mysql.cj.jdbc.Driver'. 
The driver is automatically registered via the SPI 
and manual loading of the driver class is generally unnecessary.

此时需要把com.mysql.jdbc.Driver 改为com.mysql.cj.jdbc.Driver

II、还有一个警告:

WARN: Establishing SSL connection without server’s identity verification is not recommended. 
According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection 
must be established by default if explicit option isn’t set. 
For compliance with existing applications not using SSL the verifyServerCertificate property is set to ‘false’. 
You need either to explicitly disable SSL by setting useSSL=false, 
or set useSSL=true and provide truststore for server certificate verification.

不推荐不使用服务器身份验证来建立SSL连接。
如果未明确设置,MySQL 5.5.45+, 5.6.26+ and 5.7.6+版本默认要求建立SSL连接。
为了符合当前不使用SSL连接的应用程序,verifyServerCertificate属性设置为’false’。
如果你不需要使用SSL连接,你需要通过设置useSSL=false来显式禁用SSL连接。
如果你需要用SSL连接,就要为服务器证书验证提供信任库,并设置useSSL=true。

SSL – Secure Sockets Layer(安全套接层)

7.通过静态代码块注册驱动(常用)
mport java.sql.*;   
//我们导入的java包的Driver等其他类
public class JDBtest1{
	public status void main(String args[]){
	
	try{
		//1.注册驱动
		//带包名(属于mysql的架包)
		//注册驱动的目的是要让Java程序知道你要使用的是那个数据库。
		//com.mysql.jdbc.Driver属于MySQL驱动提供的。
		Class.forName("com.mysql.jdbc.Driver");//通过com.mysql.jdbc.Driver静态代码块源码
	}catch(SQLException e){
		e.printStackTrace();
	}
		
	}
}

com.mysql.jdbc.Driver静态代码块源码:

	 //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
8.处理查询结果

当执行的是查询语句,就必须对结果集进行处理。

			//4.执行sql
			String sql = "SELECT * FROM student";
			rs = stm.executeQuery(sql);
			
			//5.处理结果
			while (rs.next()) {
				String id = rs.getString("id");
				String name = rs.getString("name");
				System.out.println("id="+id+"\tname="+name);
			}

因为你执行了查询语句你就必须对查询结果集释放。

//6.释放资源(逐个释放)
//释放查询结果集
try {
    if(rs!=null)
        rs.close();
} catch (Exception e) {
    // TODO: handle exception
    e.printStackTrace();
}
//释放数据库操作对象
try {
    if(stm!=null)
        stm.close();
} catch (SQLException e2) {
    // TODO: handle exception
    e2.printStackTrace();
}
//释放数据库连接
try {
    if(conn!=null)
        conn.close();
} catch (SQLException e3) {
    // TODO: handle exception
    e3.printStackTrace();
}
9.SQL注入

因为Statement存在SQL注入问题,在用户输入的情况下防止sql注入时,我们应该选择preparedStatement来代替Statement,关于SQL注入博文:https://blog.csdn.net/github_36032947/article/details/78442189,https://blog.csdn.net/qq_34858648/article/details/52750038。现已绝大部分网站已经解决改问题。不过有时一些情况Statement也有它的用途。当需要sql注入时,Statement时必要的。

//3.获取数据库操作对象(存在SQL注入)
Statement stm = null;
stm = conn.createStatement();

//4.执行sql(这里执行插入语句)
String sql = "INSERT INTO Student VALUES (3, 'tom'), (4, 'john');";
int count = stm.executeUpdate(sql)



//3.获取预编译的数据库操作对象(保证用户传入值不参与sql语句的编译,只能做值传递)
//问号为占位符,传递值。
preparedStatement ps =null;
String sql = "SELECT * FROM student WHERE name = ?;";   //先传模板
ps = conn.prepareStatement(sql);   //DBMS对传入sql预编译
ps.setString(1,"Tom");			//"Tom"为传入值

//4.执行sql语句
rs = ps.executeQuery();

Statement存在SQL注入,PreparedStatement解决了SQL注入问题,并且PreparedStatement会做类型检测。

PreparedStatement执行效率可能下降一点点,因为网上也有其他人对此问题进行了讨论,他们得出的结论也有不同的,有的说mysql预编译提高效率,有的说mysql预编译减低效率。不过怎么说都建议使用PreparedStatement,因为能有效防止SQL注入。安全比效率更重要。

选择我分两派说明一下mysql预编译。

认为效率提高:

因为MySQL会对Statement编译一次执行一次,对PreparedStatement编译一次执行多次。

在测试的过程中,100线程情况下,使用和不适用预编译消耗的cpu是差不多的,执行时间上,使用预编译的情况会好些,但是提升的效果不是很明显,可能是我的测试用例比较简单只有单个语句,3%的提升。

博文:https://blog.csdn.net/LKCTO/article/details/47420111

博文2:https://blog.csdn.net/aoerqileng/article/details/78656315

认为效率减低:
mysql是否默认开启预编译,与MySQL server的版本无关,而与 MySQL Connector/J(驱动程序)的版本有关,Connector/J 5.0.5及以后的版本默认不支持预编译,Connector/J 5.0.5之前的版本默认支持预编译。
是否开启预编译:
①开启预编译:useServerPstmts=true (Connector/J 5.0.5及之后默认false)
②开启预编译缓存:cachePrepStmts=true (一直默认false)
效率:
①开启预编译,不开启预编译缓存,效率低于非预编译执行;
②开启预编译同时开启预编译缓存,效率与非预编译执行几乎一样(同connect);
③预编译与预编译缓存只能在同connect中共享,当stms关闭后,每次调用都会重新预编译语句,效率反而低下。

打开预编译缓冲和开启预编译代码:

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/prepare_stmt_test?user=root&password=root&useServerPrepStmts=true&cachePrepStmts=true"); 

博文:https://www.cnblogs.com/qiumingcheng/p/8060471.html

博文2:https://blog.csdn.net/P19777/article/details/100593996

10.JDBC事务机制

我们先看一下什么是JDBC事务机制。

事务:由一个或多个执行、完成的语句组成,以组的形式提交或回滚。当前事务结束,另一个事务开始。
在JDBC中,事务默认是自动提交的,即每执行一条语句,就是一个事务。
事务的特性:
  1. 原子性(atomicity):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。

  2. 一致性(consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。

  3. 隔离性(isolation):一个事务的执行不能被其他事务所影响。

  4. 持久性(durability):一个事务一旦提交,事物的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改。

事务(Transaction):是并发控制的单元,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,sql server 能将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整性。事务通常是以begin transaction开始,以commit或rollback结束。Commint表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据的更新写回到磁盘上的物理数据库中去,事务正常结束。Rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。

三种事务:

自动提交事务:每条单独的语句都是一个事务。每个语句后都隐含一个commit。 (默认)

显式事务:以begin transaction显示开始,以commit或rollback结束。

隐式事务:当连接以隐式事务模式进行操作时,sql server数据库引擎实例将在提交或回滚当前事务后自动启动新事务。无须描述事物的开始,只需提交或回滚每个事务。但每个事务仍以commit或rollback显式结束。连接将隐性事务模式设置为打开之后,当数据库引擎实例首次执行下列任何语句时,都会自动启动一个隐式事务:alter table,insert,create,open ,delete,revoke ,drop,select, fetch ,truncate table,grant,update在发出commit或rollback语句之前,该事务将一直保持有效。在第一个事务被提交或回滚之后,下次当连接执行以上任何语句时,数据库引擎实例都将自动启动一个新事务。该实例将不断地生成隐性事务链,直到隐性事务模式关闭为止。

事务并发处理可能引起的问题:

脏读(dirty read) 一个事务读取了另一个事务尚未提交的数据,
不可重复读(non-repeatable read) 一个事务的操作导致另一个事务前后两次读取到不同的数据
幻读(phantom read) 一个事务的操作导致另一个事务前后两次查询的结果数据量不同。
举例:
事务A、B并发执行时,
当A事务update后,B事务select读取到A尚未提交的数据,此时A事务rollback,则B读到的数据是无效的"脏"数据。
当B事务select读取数据后,A事务update操作更改B事务select到的数据,此时B事务再次读去该数据,发现前后两次的数据不一样。
当B事务select读取数据后,A事务insert或delete了一条满足A事务的select条件的记录,此时B事务再次select,发现查询到前次不存在的记录(“幻影”),或者前次的某个记录不见了。

事务隔离级别(Transaction Isolation Levels)

JDBC定义了五种事务隔离级别:
TRANSACTION_NONE JDBC驱动不支持事务
TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读。
TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读。
TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,单运行幻读。
TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读。

说了这么多,和看来这么多博文。现在如何解决JDBC事务自动提交呢,想JDBC事务不 自动提交我们可以通过Connection对象的setAutoCommit(false)方法来吧JDBC事务的自动提交进行修改,现在需要你自己手动进行提交或者出问题时进行回滚。

//2.获取数据库连接对象(并设置不进行自动提交事务)
conn= DriverManager.getConnection(url,user,password);
conn.setAutoCommit(false);
//3.获取预编译的数据库操作对象
String sql = "insert into student(id,name) valuse(?,?)";   //先传模板
ps = conn.prepareStatement(sql);   //DBMS对传入sql预编译

//4.执行sql语句
try{
ps.setInt(1,1);			//1为传入值
ps.setString(2,"Tom");			//"Tom"为传入值
ps.executeUpdata();

ps.setInt(1,2);			//传入新值
ps.setString(2,"Jake");			//传入新值
ps.executeUpdata();
conn.commit();      //当两个操作成功后手动提交 
}catch(Exception e) {  
	try{
	conn.rollback();    //一旦其中一个操作出错都将回滚,使两个操作都不成功  
	}catch(SQLException e){
		e.printStackTrace();
	}   
}

并且conn还有一个方法来设置回滚点(存储点),在事务过程中,在两个操作间可以插入一个命名的存储点作为标记,因此可以将事务回滚到那个标记,保留标记有效前的所有操作。

如:

conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
 
stmt.executeUpdate(update1);
Savepoint point1 = conn.setSavepoint("point1");
 
stmt.executeUpdate(update2);
stmt.executeUpdate(update3);
 
conn.rollback(point1);
conn.commit();
博文1:https://blog.csdn.net/apextrace/article/details/7481408

博文2:https://blog.csdn.net/nszkadrgg/article/details/8471728

11.其他相关知识

其他相关知识很多,比如连接池,还有悲观锁(行级锁)和乐观锁。

乐观锁和悲观锁:https://blog.csdn.net/stuShan/article/details/51296098

连接池:https://www.cnblogs.com/aspirant/p/6747238.html

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值