目录
1、JDBC:Java DataBase Connectivity(Java语言连接数据库)
2、JDBC的本质是什么?JDBC是SUN公司制定的一套接口(interface)
java.sql.*; (这个软件包下有很多接口。)
为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
3、为什么SUN制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样。
Oracle数据库有自己的原理。
MySQL数据库也有自己的原理。
MS SqlServer数据库也有自己的原理。
....
每一个数据库产品都有自己独特的实现原理。
![](https://i-blog.csdnimg.cn/blog_migrate/0f0b2fb19a42db56163978faec5a4508.jpeg)
JDBC编程六步
下面使用的是mysql8.0数据库,还需要mysql连接器的jar包,打开这个网址mysql连接jar根据本机上数据库版本下载对应连接器版本。
有一个名为ms的数据库,其中有user表
第一步:注册驱动(作用:告诉Java程序,即将要连接的是哪个品牌的数据库)
方式一:
Driver driver = new com.mysql.cj.jdbc.Driver(); // 多态,父类型引用指向子类型对象。
DriverManager.registerDriver(driver);
方式二:(常用)
因为参数是一个字符串,字符串可以写到xxx.properties文件中。以下方法不需要接收返回值,因为我们只想用它的类加载动作。
Class.forName("com.mysql.cj.jdbc.Driver");
可以写一个工具类,写在静态代码块中,在类加载的时候执行,且只执行一次。下面步骤中重复的代码都可以写在工具类。方便编程。
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
创建jdbc.properties文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/easyshop?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
user=root
password=root
使用资源绑定器绑定属性配置文件,获取数据库的连接信息也可以写进配置文件中。
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Class.forName(driver);
第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道。)
url:统一资源定位符(网络中某个资源的绝对路径),如:http://www.baidu.com/ 这就是url。
URL包括:协议,IP,PORT,资源名
http://182.61.200.7:80/index.html
http:// 通信协议
182.61.200.7 服务器IP地址
80 服务器上软件的端口
什么是通信协议,有什么用?
通信协议是通信之前就提前设定好的数据传送格式。数据包具体是怎么传数据,格式提前定好的。
String url = "jdbc:mysql://localhost:3306/ms?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
其中,url,user是数据库账号,password是数据库密码。
第三步:获取数据库操作对象(专门执行sql语句的对象)
Statement方式
Statement stmt = conn.createStatement();
【注意】:java.sql.Statement可能会出现SQL注入问题
1、导致SQL注入的原因是:用户输入的信息含有SQL语句的关键字,并且这些关键字参与是SQL语句的编译过程,导致SQL语句的原意被扭曲,进而达到SQL注入。
2、解决SQL注入问题:只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用。要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement。
PreparedStatement接口继承了java.sql.Statement,PreparedStatement是属于预编译的数据库操作对象。PreparedStatement的原理是:预先对sql语句的框架进行编译,然后再给SQL语句传“值”。
3、解决SQL注入的关键是什么?
用户提供的信息中即使含有SQL语句的关键字,但是这些关键字并没有参与编译,不起作用。
4、对比以下Statement和PreparedStatement?
- Statement存在sql注入问题,PreparedStatement解决了SQL注入问题。
- Statement是编译一次执行一次。PreparedStatement是编译一次,可执行N次,PreparedStatement效率较高一些。
- PreparedStatement会在编译阶段做类似的安全检查。
综上所述,PreparedStatement使用较多,只有极少数的情况下需要使用Statement
5、什么情况下必须使用Statement呢?
业务方面要求必须支持sql注入的时候。Statement支持sql注入,凡是业务方面要求是需要sql语句拼接的,必须使用Statement。
PreparedStatement方式
String sql = "select * from user where username = ? and password = ? ";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "asd");
ps.setString(2, "147");
解析:sql字符串是SQL语句的框子。其中一个?,表示一个占位符,一个?来接收一个“值”,【注意】:占位符不能使用单引号括起来。
conn.prepareStatement(sql);程序执行到这里,会发送sql语句给DBMS进行sql语句的预先编译。
setString(1,"asd"); 给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中所有的下标从1开始。)
实现模糊查询
String sql = "select * from user where username = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "%s%");
第四步:执行SQL语句(DQL DML....)
executeUpdate();专门执行的DML语句的(insert delete update),返回值是“影响数据库中的记录条数”。
Statement方式
String sql = "insert into user(username, password) values('asd', '147')";
int count = stmt.executeUpdate(sql);
PreparedStatement方式
rs = ps.executeUpdate();
executeQuery(); 专门执行select的,返回值是ResultSet结果集合
Statement方式
String sql = "select id, username, password from user";
ResultSet rs = stmt.executeQuery(sql);
PreparedStatement方式
rs = ps.executeQuery();
第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。)
方式一:(常用)
while(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println(id + "," + username + "," + password);
}
getString(),getInt()....其中参数是不是表中的列名称,是查询结果集的名称。(使用别名,就是别名)。
方式二:可以以特定的类型取出
while(rs.next()){
int id = rs.getInt(1);
String username = rs.getString(2);
String password = rs.getString(3);
System.out.println(id + "," + username + "," + password);
}
第六步:释放资源(使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭。)
在finally语句块中关闭资源,并且要遵循从小到大依次关闭,分别对其try..catch
Statement方式
if(rs != null){
try{
rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(stmt != null){
try{
stmt.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
PreparedStatement方式
if(rs != null){
try{
rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(ps != null){
try{
ps.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
JDBC事务机制
1、JDBC中的事务是自动提交的,什么是自动提交?
只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为。但是在实际开发中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败。
在获取数据连接对象时调用函数,开始事务
conn.setAutoCommit(false);
在执行SQL语句之后,提交事务
conn.commit();
程序能够走到上面代码说明程序没有异常,事务结束,手动提交数据
如果程序中途发生错误,在catch语句中,会执行回滚事务
if (conn != null) {
try {
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
批处理
当插入数据的数据量比较多的时候,可以使用批处理,增加效率。
String sql = "insert into user(id, username) value(?,?)";
ps = conn.prepareStatement(sql);
for (int i = 1; i <= 1000; i++){
ps.setInt(1, i);
ps.setString(2, "username"+i);
ps.addBatch(); // 打包
if (i % 300 == 0){
ps.executeBatch(); // 每300个发送一次给数据库
ps.clearBatch(); // 清空包
}
}
ps.executeBatch();// 为了保证所有数据插入,所以这里再执行一次
锁
悲观锁(行级锁)
语法:select后面添加for update
开启一个事务1进行查询,并且使用悲观锁,锁住相关记录。
String sql = "select id,name,age from user where id=? for update";
ps = conn.prepareStatement(sql);
ps.setString(1, "1001");
rs = ps.executeQuery();
再开启另一个事务2修改这条数据,无法进行,因为其中事务1先执行,执行之后看到版本号是1.1,于是提交事务,将版本号修改成1.2,事务2后执行,执行之后准备提交事务的时候,发现版本号是1.2,和它最初读的版本号不一致。回滚数据。
悲观锁:事务必须排队执行,数据锁住了,不允许并发。
乐观锁:支持并发,事务不需要排队,只不过需要一个版本号。