概念:
全称: Java DataBase Connectivty (Java数据库连接)
JDBC是Java中用于连接不同数据库的解决方案。
JDBC中定义了一套规范标准,它对应的是各种接口与抽象类(通常对应java.sql包下面的各种类与接口)。
使用:
JDBC运用过程分为七步:
- 导包。我们要想操作数据库,就需要把厂商开发的实现类。也就是jar包。
- 注册驱动,或者叫加载驱动,只需要做一次就行
- 建立连接,建立和数据库的连接
- 创建运行SQL的语句
- 运行结果
- 处理运行结果,也就是可以在控制台遍历展示
- 释放资源
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 数据库连接
// jdbc:mysql://IP地址:端口号/数据库
Connection connection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_14_", "root", "root");
// 3 语句传输对象
String sql = "select * from student";
Statement statement = connection.createStatement();
// 4 接收结果集
ResultSet resultSet = statement.executeQuery(sql);
// 5 遍历获取数据
// next 就等于 迭代器中 hasNext() 和 next() 连用
// 判断下面有没有元素,如果有就指向下一行元素
while (resultSet.next()) {
System.out.print(resultSet.getInt("id") + " ");
// getString 也可以 获取int类型的值,但是不推荐
// System.out.println(resultSet.getString("id"));
System.out.print(resultSet.getString("name")+" ");
System.out.println(resultSet.getInt("age"));
}
// 6 关闭资源
resultSet.close();
statement.close();
connection.close();
上面是查询代码,增删改,过程类似,但是他不需要返回的数据,也就是不需要ResultSet
Connection connection = null;
Statement statement = null;
try {
// 1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 创建数据库连接对象
connection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_14_", "root", "root");
// 3 语句传输对象
statement = connection.createStatement();
// String sql = "insert into student (name,age) values ('test1',11)";
// String sql = "update student set name='test2' where name = 'test1' ";
String sql = "delete from student where name='test2' ";
// 查询语句 返回true,否则返回false
// statement.execute(sql);
// 返回值是int 返回影响(操作)了几条数据
int count = statement.executeUpdate(sql);
System.out.println("影响了 "+count+" 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
优化:
statement与preparedstatement区别
SQL注入
首先我们需要知道,在创建SQL语句并传入数据时,有一个SQL注入的问题,SQL注入就是,利用Java运行规则,去恶意的填写一些特殊的登录名和密码,另Java解析时,不管你登录名和密码是否正确都会返回是正确的(利用or)。
SQL注入代码如下:
public static void main(String[] args) throws Exception {
login("' or 1=1 or 1>'", "11");
}
public static void login(String username, String password) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 数据库连接
// jdbc:mysql://IP地址:端口号/数据库
connection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_14_", "root", "root");
// 3 语句传输对象
String sql = "select * from t_user where username = '" + username
+ "' and password = '" + password + "' ";
statement = connection.createStatement();
// 4 接收结果集
resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
System.out.println("登陆成功,欢迎 : "
+ resultSet.getString("nickname"));
} else {
System.out.println("登陆失败,用户名或密码错误");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
区别:
-
而statement就是有Sql注入的风险,而preparestatement就没有Sql注入的风险,
preparestatement 在使用数据库前就对sql语句进行了分析优化等,这个时候它使用了占位符去代替该位置(?) ,当数据传入进来时,他就被当做一个值,而不是一个指令了。
-
statement是编译一次运行一次Sql指令语句,而preparestatement是直接预编译一次,可以使用多条语句。所以在执行只有一条语句时我们可以用statement,当执行多条语句时,我们要用到preparestatement,这样的执行效率会高。
prepare statement代码示例:
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_14_", "root", "root");
PreparedStatement preparedStatement = connection
.prepareStatement("insert into t_user (username,password,nickname) values (?,?,?)");
preparedStatement.setString(1, "张三");
preparedStatement.setString(2, "root");
preparedStatement.setString(3, "xxxx");
//可以重复执行多次,也可以改变数值,只要sql语句没有变
int count = preparedStatement.executeUpdate();
System.out.println("影响了 "+count);
封装工具类
我们可以感觉到不管时statement还是prepare statement,不管是查询还是增删改,七步当中操作的六步,第一第二和最后一步都不会发生变动,所以我们可以吧他们封装到一个类中,当我们用到它的时候可以调用它,做到代码复用。同时最后一步的关闭资源,由于他或继承或实现了同一个类,我们可以运用多态的规则,只写那一个类就行,调用时只要传入子类或者实现类就行。
public class DBUtil {
public static Connection getConnection() throws ClassNotFoundException,
SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_14_", "root", "root");
return connection;
}
public static void close(AutoCloseable autoCloseable) {
try {
if (autoCloseable != null) {
autoCloseable.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
Batch
batch,多语句操作,在执行一次任务时,操作多条语句,这就相当于Java中的缓存流,他就像一个桶,见你要一次性要操作的多条语句放到一起,当调用一个指定的方法.executBatch(),他就会一次性执行,同时多条语句之间相互不影响。
这是用到prepare statement实现的例子,statement一样只不过,他没有预编译,执行多条语句要多次编译SQL语句,addBatch()是添加。
Connection conn = null;
PreparedStatement prst = null;
try {
conn = DBUtil.getConnection();
String sql = "insert into test_jdbc (id,name,money) values(?,?,?)";
prst = conn.prepareStatement(sql);
prst.setInt(1, 31);
prst.setString(2, "prst多条测试1");
prst.setDouble(3, 11.1);
prst.addBatch();
prst.setInt(1, 32);
prst.setString(2, "prst多条测试2");
prst.setDouble(3, 21.1);
prst.addBatch();
prst.setInt(1, 33);
prst.setString(2, "prst多条测试3");
prst.setDouble(3, 31.1);
prst.addBatch();
prst.executeBatch();
System.out.println("执行成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(prst);
DBUtil.close(conn);
}
优化硬代码
硬代码: 就是一些需要变动的配置信息写死到代码中(这种行为叫硬编码,hardcode),别人就无法修改了,想要修改必须要在源码中修改。在这里我们使用了Properties来优化JDBC里面的硬代码(driver ,url,username ,password )。也就是注册驱动和建立数据库连接那里的数据。
Properties是一种专门读取和写出相对应Properties文件的操作方法
Properties = new Properties();
//相对定位,eclipse中第一根目录是src
//这里只是把文件读取进了properties中,怎么拿出来不在这个方法中,应该是哪里用哪里拿
Properties.load(DBUtil.class.getClassLoader().getResourceAsStream(
"jdbc.properties"));
Properties properties = PropertiesUtil.getProperties();
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, username,
password);
优化工具类
关于Properties优化硬代码时,每一次调用我们都要创建一次Properties对象,那我们干脆使用单例模式,让多次调用只用到一个对象就行,节约了内存,当我们需要改动直接改动Properties为后缀的文件就行。
public class PropertiesUtil {
//运用单例模式,只创造一个对象,被拿去多次读取文件
private static Properties Properties = null;
private PropertiesUtil(){
}
public static Properties getProperties(){
if (Properties == null) {
Properties = new Properties();
try {
//相对定位,eclipse中第一根目录是src
//这里只是把文件读取进了properties中,怎么拿出来不在这个方法中,应该是哪里用哪里拿
Properties.load(DBUtil.class.getClassLoader().getResourceAsStream(
"jdbc.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
return Properties;
}
事务机制
Transaction事务机制管理
默认情况下,是执行一条SQL语句就保存一次,那么比如我需要 有三条数据同时成功同时失败,就是这三条语句必须相当于组合关系,必须同时成功同时失败,这个时候就需要开启事务机制了
开启事务机制,执行中发生问题,会 回滚 到没有操作之前,相当于什么也没有发生过,这就能保证了多条语句同时成功同时失败了。
代码上面主要用到了取消自动提交setAutoCommit(false),手动提交commit(),回滚rollback(),开启自动提交setAutoCommit(true);
Connection conn = null;
PreparedStatement prst = null;
Statement stmt = null;
try {
conn = DBUtil.getConnection();
// 取消自动提交
conn.setAutoCommit(false);
String sql = "insert into test_jdbc (id,name,money) values(?,?,?)";
prst = conn.prepareStatement(sql);
prst.setInt(1, 31);
prst.setString(2, "事务测试1");
prst.setDouble(3, 11.1);
prst.addBatch();
prst.setInt(1, 32);
// 这里故意写成1,让报错
prst.setString(1, "事务测试2");
prst.setDouble(3, 21.1);
prst.addBatch();
prst.setInt(1, 33);
prst.setString(2, "事务测试3");
prst.setDouble(3, 31.1);
prst.addBatch();
prst.executeBatch();
// 提交
conn.commit();
conn.setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
try {
// 事务回滚
conn.rollback();
conn.setAutoCommit(true);
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
DBUtil.close(stmt);
DBUtil.close(prst);
DBUtil.close(conn);
}
连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
这项技术能明显提高对数据库操作的性能
优点 :
- 资源复用 : 数据库连接得到重用,避免了频繁创建释放链接引起的大量性能开销
在减少系统消耗的基础上,也增进了系统运行环境的平稳性 - 更快的系统响应速度 : 数据库连接池在初始化过程中,往往就已经创建了若干个数据库连接对象放到池中备用
这时,连接的初始化工作已完成,对于业务请求处理而言,直接利用现有的可用连接,避免了数据库连接初始化和释放过程的时间,从而缩减了系统整体的响应时间 - 统一的连接管理,避免数据库连接遗漏 : 在较为完备数据库连接池中,可以根据预先的连接占用超时设定,强制回收占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露情况
数据库连接池
package Exam;
import java.util.Properties;
import org.apache.commons.dbcp2.BasicDataSource;
import JDBC_02.PropertiesUtil;
/*
* 连接池
*
*/
public class BasicDataSourceUtil {
private BasicDataSource bds ;
private static BasicDataSourceUtil bdsu;
private BasicDataSourceUtil() {
bds=new BasicDataSource();
Properties properties =PropertiesUtil.getProperties();
bds.setDriverClassName(properties .getProperty("driver"));
bds.setUrl(properties .getProperty("url"));
bds.setUsername(properties .getProperty("username"));
bds.setPassword(properties .getProperty("password"));
}
public static BasicDataSourceUtil getInstance() {
if(bdsu==null ) {
synchronized(BasicDataSourceUtil.class) {
if(bdsu==null) {
bdsu= new BasicDataSourceUtil();
}
}
}
return bdsu;
}
public BasicDataSource getBasicDataSource () {
return bds;
}
}
PropertiesUtil 类
package Exam;
import java.io.IOException;
import java.util.Properties;
import com.DBUtil;
public class PropertiesUtil {
private static Properties Properties = null;
private PropertiesUtil(){
}
public static Properties getProperties(){
if (Properties == null) {
Properties = new Properties();
try {
Properties.load(DBUtil.class.getClassLoader().getResourceAsStream(
"jdbc.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
return Properties;
}
}
代码测试
package Exam;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.commons.dbcp2.BasicDataSource;
/*
* 用编程实现DBCP连接池程序,更新上面的student_score表,将学号为001的学生的高等数学成绩修改为90分。
*/
public class JDBC_05_Test_02 {
public static void main(String[] args) {
BasicDataSource bds=BasicDataSourceUtil.getInstance().getBasicDataSource();
//获取连接
try {
Connection connection=bds.getConnection();
Statement statement =connection .createStatement();
int count =statement .executeUpdate("update student_score set score=90 where coursename='高等数学'" );
System.out.println("受影响行数 "+count);
} catch (SQLException e) {
e.printStackTrace();
}
}
}