Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
通过JDBC将应用程序与数据库相连,从而对数据库进行增删查改等操作。
jdbc建立连接的五大步骤:
@Test
public void demo1() {
Connection conn = null;
Statement stmt = null;
ResultSet resultSet = null;
try {
//1、加载驱动
//DriverManager.registerDriver(new Driver());
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获得连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3307/数据库名?serverTimezone=UTC&characterEncoding=utf-8", "用户名" ,"密码");
//3、创建执行SQL语句的对 象,并且执行SQL
//3.1 创建执行sql的对象
String sql = "SELECT id,name,price,desp FROM goods WHERE price<3500";
stmt = conn.createStatement();
//3.2 执行sql语句
resultSet = stmt.executeQuery(sql);
//4、处理结果集
while(resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
float price = resultSet.getFloat("price");
String desp = resultSet.getString("desp");
System.out.println(id + " " + name + " " + price + " " + desp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5、释放资源
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
主要作用:
1、注册驱动
DriverManager .registerDriver(new Driver());
但是这种方式会导致驱动注册两次(在加载DriverManager的时候会自动注册一次)
实际开发中注册驱动会使用如下方式:
//1、加载驱动
//DriverManager.registerDriver(new Driver());
//开发中常用:
Class.forName("com.mysql.cj.jdbc.Driver");
2、获得连接
//2、获得连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3307/imooc?serverTimezone=UTC&characterEncoding=utf-8", "用户名" ,"密码");
Connection getConnection(String url,String username,String password);
*url写法:jdbc:mysql://localhost:3307/数据库名称
**jdbc:协议
**mysql:子协议
**localhost:主机名
**3307:端口号
*url简写:jdbc:mysql:///jdbc(数据库默认端口号是3306,如果你的数据库不是在默认端口号,最好还是写全)
主要作用:
1、创建执行SQL语句的对象:
方法 | 作用 |
---|---|
Statement createStatement(); | 执行SQL语句,有SQL注入的漏洞存在 |
PreparedStatement prepareStatement(String sql); | 预编译SQL语句并且执行,解决SQL注入的漏洞(实际开发中用的) |
CallableStatement prepareCall(String sql) ; | 执行SQL中存储过程 |
2、进行事务的管理(保证多个操作是在同一个事务当中)
方法 | 作用 |
---|---|
setAutoCommit(boolean autoCommit) | 设置事务是否自动提交(默认自动提交) |
commit() | 事务提交 |
rollback() | 事务回滚 |
若想开启JDBC的事务,需要先将自动提交关闭,设置为false(之后再详细整理事务)
主要作用:
1、 执行SQL语句:
方法 | 作用 |
---|---|
boolean excute(String sql); | (少用)执行sql,如果执行select语句返回true,否则返回false |
ResultSet executeQuery(String sql); | (常用)执行sql中的select语句,返回表格,结果集 |
int excuteUpdate(String sql); | (常用)执行sql中的insert/update/delete语句,返回影响的行数 int 类型 , 当返回值>0时,操作成功 |
*executeQuery:执行select语句(查询)
*executeUpdate:执行insert/update/delete语句(增删改)
2、执行批处理操作:-- 执行一批SQL语句
方法 | 作用 |
---|---|
addBatch(String sql); | 将sql语句添加到批处理当中 |
excuteBatch(); | 执行批处理 |
clearBatch(); | 清空批处理 |
ResultSet [结果集]的使用
是查询语句(select)查询到的结果的封装;
主要作用:
结果集:获取查询到的结果;
主要方法:
boolean next();
最开始是0行,第一次判断第一行有无…
判断是否有下一行的记录,如果有就指向下一行;
getXXX();
获取数据,
针对不同的类型的数据可以使用 getInt() / getString() 等等获取数据;
通用的获取数据的方法:getObject();
Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResiltSet,Statement和Connection对象。
特别是Connection对象,它非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。
Connection的使用原则是尽量晚的创建,尽量早的释放。
释放的代码【通常写在finally代码块】里。但是格式语法需要修改。
finally块中代码,不管是否有异常发生,总是会被执行,可以保证JDBC的资源的释放。
//写在finally块中,以释放Connection对象为例:
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn=null;//垃圾回收机制会更早回收对象。
}
为了简化JDBC的开发,可以将一些重复的代码进行提取。
对注册驱动、获得连接、资源释放进行抽取。
首先在src包中新建properties属性文件,命名为:jdbc.properties,在该文件中填写自己数据库相关信息(端口号一般为3306):
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3307/数据库名?serverTimezone=UTC&characterEncoding=utf-8
username=数据库用户名
password=数据库密码
然后新建一个JDBCUtils类,通过加载属性文件获取数据库信息:
/**
* JDBC的工具类
*/
public class JDBCUtils {
private static final String driverClass;
private static final String url;
private static final String username;
private static final String password;
static{
// 加载属性文件并解析:
Properties props = new Properties();
// 如何获得属性文件的输入流?
// 通常情况下使用类的加载器的方式进行获取:
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
props.load(is);
} catch (IOException e) {
e.printStackTrace();
}
driverClass = props.getProperty("driverClass");
url = props.getProperty("url");
username = props.getProperty("username");
password = props.getProperty("password");
}
/**
* 注册驱动的方法
* @throws ClassNotFoundException
*/
public static void loadDriver() throws ClassNotFoundException{
Class.forName(driverClass);
}
/**
* 获得连接的方法:
* @throws SQLException
*/
public static Connection getConnection() throws Exception{
loadDriver();
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
/**
* 资源释放
*/
public static void release(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs,Statement stmt,Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
以向数据库中插入一条信息为例,测试使用JDBCUtils:
@Test
// 保存记录
public void demo1(){
Connection conn = null;
Statement stmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 创建执行SQL语句的对象
stmt = conn.createStatement();
// 编写SQL:
String sql = "insert into user values (null,'ggg','123','小六')";
// 执行SQL:
int num = stmt.executeUpdate(sql);
if(num > 0){
System.out.println("保存成功!");
}
}catch(Exception e){
e.printStackTrace();
}finally{
// 释放资源:
JDBCUtils.release(stmt, conn);
}
}
对比没有使用工具类的代码,可以看出简化了很多冗余的代码。
应用程序直接获取连接的缺点:
用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。如果说一个网站每天的访问量为10万,则数据库服务器就需要创建连接10万次,极大的浪费了数据库的资源,也极易造成数据库服务器内存溢出。
连接池概念:连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。
连接池特点:用数据库连接池时,不需要再临时创建到数据库的连接,而是直接去使用某个容器或者地址中创建好的connection,用完后归还,从而提高程序的执行效率。
常用的连接池有 : DPCP、C3P0
使用C3P0时,需要导入该jar包:
建议使用XML文档来设置连接池参数(c3p0-config.xml):
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3307/数据库名?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8</property>
<property name="user">数据库用户名</property>
<property name="password">数据库密码</property>
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
</c3p0-config>
使用c3p0连接池的JDBC工具类:
/**
* JDBC的工具类,连接池
*/
public class JDBCUtils2 {
//创建连接池:
private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();
/**
* 获得连接的方法:
* @throws SQLException
*/
public static Connection getConnection() throws Exception{
Connection conn = dataSource.getConnection();
return conn;
}
/**
* 资源释放
*/
public static void release(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs,Statement stmt,Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
测试使用连接池工具类:
@Test
/**
* 使用配置文件的方式
*/
public void demo(){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
// 获得连接:
conn = JDBCUtils2.getConnection();
// 编写Sql:
String sql = "select * from user";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数
// 执行SQL:
rs = pstmt.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils2.release(rs, pstmt, conn);
}
}
现象:已知用户名,但不输入密码,点击登录,也可以登陆
产生原因:输入用户名时输入了sql的关键字,or 或 - -(两个’-’,中间没有空格),对原有程序中的SQL语句,产生了破坏
SQL注入漏洞:
select * from user where username=‘aaa’ or ‘1=1’ and password=‘asddanjdnajsnj’;
select * from user where username=‘aaa’ - - and password=‘asddanjdnajsnj’;
or或者- -(注释)注入程序sql语句中,使得程序偏离想要的结果,产生漏洞
/**
* 产生SQL注入漏洞的方法
* @param username
* @param password
* @return
*/
public static boolean login(String username,String password){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
boolean flag = false;
try{
conn = JDBCUtils.getConnection();
// 创建执行SQL语句的对象:
stmt = conn.createStatement();
// 编写SQL:
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
// 执行SQL:
rs = stmt.executeQuery(sql);
// 判断结果集中是否有数据。
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, stmt, conn);
}
return flag;
}
通过对username和password进行赋值,可通过sql关键字对原本的sql语句进行破坏:
@Test
/**
* 测试SQL注入漏洞的方法
*/
public void demo(){
boolean flag = JDBCDemo.login("aaa' or '1=1", "asddanjdnajsnj");
if(flag == true){
System.out.println("登录成功!");
}else{
System.out.println("登录失败!");
}
}
JDBCDemo是所创建的类,调用login方法,将username和password代回原sql中:
select * from user where username=‘aaa’ or ‘1=1’ and password=‘asddanjdnajsnj’;
or前面为true,后面无论为true或false,结果返回均为true,从而可以随意输入密码而进入用户账号;
同理另一条sql语句:
select * from user where username=‘aaa’ - - and password=‘asddanjdnajsnj’;
在sql中,- -为注释,因此这条语句中将后面的密码注释了,而前面即可判断为true。
SQL注入漏洞的解决:利用PreparedStatement接口的实例对象
PreparedStatement是Statement的子接口,它的实例对象可以通过调用Connection.preparedStatement(sql);方法获得;
与Statement对象相比:
/**
* 避免SQL注入漏洞的方法
*/
public static boolean login2(String username,String password){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
boolean flag = false;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写SQL:
String sql = "select * from user where username = ? and password = ?";
// 预处理SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setString(1, username);//第一个?代表的参数
pstmt.setString(2, password);//第二个?代表的参数
// 执行SQL:
rs = pstmt.executeQuery();
// 判断结果集
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
return flag;
}
PreparedStatement 是用占位符 ?的形式,固定SQL的形式。
而为什么使用占位符就可以解决注入漏洞问题:
一个sql 是 select * from where id=?;
后面是需要传递的参数。
假设传递的参数是:“2 or 1=1”
如果是占位符:sql编译后id的值是 “2 or 1=1”
如果不是占位符:sql编译后id的值是2, 而后面的 or 1=1 会作为另一个查询条件来进行拼接。
关于JDBC就整理到这里啦,下一篇整理JDBC和Mybatis区别!!