JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API。JDBC是Java访问数据
库的标准规范,可以为不同的关系型数据库提供统一访问,它由一组用Java语言编写的接口和类组成。
JDBC规范(掌握四个核心对象)
- DriverManager:用于注册驱动
- Connection: 表示与数据库创建的连接
- Statement: 操作数据库sql语句的对象
- ResultSet: 结果集或一张虚拟表
package com.example.jdbc;
import java.sql.*;
public class TestJdbc {
public static void main(String[] args) {
ResultSet resultSet = null;
Statement statement = null;
Connection connection = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 获得连接
String url = "jdbc:mysql://192.168.117.5:3306/test";
connection = DriverManager.getConnection(url, "root", "root");
// 3 获得执行sql语句的对象
statement = connection.createStatement();
// 4 执行sql语句,获取结果集
resultSet = statement.executeQuery("select * from category");
// 5 处理结果集
while (resultSet.next()) {
String cid = resultSet.getString("cid");
String cname = resultSet.getString("cname");
System.out.println(cid + "-------------" + cname);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (statement != null) {
statement.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (connection != null) {
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
API
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 与数据库建立连接
static Connection getConnection(String url, String user, String password)
getConnection("jdbc:mysql://localhost:3306/day04", "root", "root")
//创建操作sql语句的对象
Statement createStatement();
// 操作sql语句,并返回相应结果
String sql = "某SQL语句"; 获取Statement语句执行平台:Statement stmt =con.createStatement();
// 执行insert update delete语句
int executeUpdate(String sql);
// 执行select语句
ResultSet executeQuery(String sql);
// 仅当执行select并且有结果时才返回true,执行其他的语句返回false
boolean execute(String sql);
// 处理结果集ResultSet(注:执行insert、update、delete无需处理)
//指向第一行
rs.next();
//获取第一行第一列的数据
rs.getInt(1);
// 获得任意对象
Object getObject(int index) / Object getObject(String name)
// 获得字符串
String getString(int index) / String getString(String name)
// 获得整形
int getInt(int index) / int getInt(String name)
// 获得双精度浮点型
double getDouble(int index) / double getDouble(String name)
JDBCUtils
package com.example.jdbc;
import java.sql.*;
public class JdbcUtils {
private static String driver = "com.mysql.jdbc.Driver";
private static String url = "jdbc:mysql://192.168.117.5:3306/test?useUnicode=true&characterEncoding=utf8";
private static String user = "root";
private static String password = "root";
static {
try {
//注册驱动
Class.forName(driver);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获得连接
*
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
//获得连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
/** 释放资源
* @param conn
* @param st
* @param rs
*/
public static void closeResource(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
}
}
}
测试
package com.example.jdbc;
import org.junit.Test;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBCUtils {
@Test
public void TestInsert(){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
int i = statement.executeUpdate("insert into category(cid,cname) values('c004','测试')");
System.out.println(i);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.closeResource(connection,statement,resultSet);
}
}
@Test
public void TestUpdate(){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
int i = statement.executeUpdate("update category set cname='java测试' where cid='c004'");
System.out.println(i);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.closeResource(connection,statement,resultSet);
}
}
@Test
public void TestDelete(){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
int i = statement.executeUpdate("delete from category where cid='c004'");
System.out.println(i);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.closeResource(connection,statement,resultSet);
}
}
@Test
public void TestFindById(){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery("select * from category where cid='c003'");
if (resultSet.next()){
System.out.println(resultSet.getString("cid") + resultSet.getString("cname"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.closeResource(connection,statement,resultSet);
}
}
}
sql注入
SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。 假设有登录案例SQL语句
如下:
SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码;
此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:
XXX’ OR ‘a’=’a
时,则真正执行的代码变为:
SELECT * FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’a’=’a’;
此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这
便是SQL注入问题。 为此,我们使用PreparedStatement来解决对应的问题。
preparedStatement
预编译对象,是Statement对象的子类。
- 性能高
- 会把sql语句先编译
- 能过滤掉用户输入的关键字。
PreparedStatement预处理对象,处理的每条sql语句中所有的实际参数,都必须使用占位符?替换。
String sql = "select * from user where username = ? and password = ?";
preparedStatement使用
// 获得预处理对象,需要提供已经使用占位符处理后的SQL语句
PreparedStatement psmt = conn.prepareStatement(sql)
// 将指定参数设置指定类型的值
// 索引从1开始
void setXxx(int index, Xxx xx)
// 执行insert update delete语句
int executeUpdate();
// 执行select语句
ResultSet executeQuery();
// 执行select返回true 执行其他的语句返回false
boolean execute();
连接池
连接池理解为存放多个连接
的集合。
使用连接池技术的目的:解决建立数据库连接耗费资源和时间的问题,提高性能。
数据库连接是一种关键的有限的昂贵的
资源,这一点在多用户的网页应用程序中体现得尤为突出。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池正是针对这个问题提出来的。
数据库连接池负责分配、管理和释放数据库连接
,它允许应用程序重复使用一个现有的数据库连接,而再不是重新建立一个。这项技术能明显提高对数据库操作的性能。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数
来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。
连接池的最大数据库连接数量
限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
数据库连接池的最小连接数和最大连接数的设置要考虑到下列几个因素:
-
最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;
-
最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
-
如果最小连接数与最大连接数相差太大,那么最先的连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是空闲超时后被释放。
C3P0连接池
Java为数据库连接池提供了公共的接口javax.sql.DataSource
,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
常见的连接池:C3P0、DRUID.、DBCP连接池。
C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用C3P0连接池需要导入jar包,
c3p0使用时还需要添加配置文件“c3p0-config.xml”
c3p0常用参数配置
-
initialPoolSize 初始连接数
刚创建好连接池的时候准备的连接数量 -
maxPoolSize 最大连接数
连接池中最多可以放多少个连接 -
checkoutTimeout 最大等待时间
连接池中没有连接时最长等待时间 -
maxIdleTime 最大空闲回收时间
连接池中的空闲连接多久没有使用就会回收
jdbc事务操作
// Connection对象
// 开启事务
conn.setAutoCommit(false)
// 提交事务
conn.commit()
// 回滚事务
conn.rollback()
//事务模板代码
public void demo01() throws SQLException {
// 获得连接
Connection conn = null;
try {
//1 开始事务
conn.setAutoCommit(false);
//.... 加钱 ,减钱
// #2 提交事务
conn.commit();
} catch (Exception e) {
//#3 回滚事务
conn.rollback();
} finally {
// 释放资源
conn.close();
}
}
转账小案例
c3p0-config.xml
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://192.168.117.5:3306/webdb</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<!-- 初始连接数,刚创建好连接池的时候准备的连接数量-->
<property name="initialPoolSize">5</property>
<!-- 最大连接数,连接池中最多可以放多少个连接-->
<property name="maxPoolSize">10</property>
<!-- 最大等待时间,连接池中没有连接时最长等待时间-->
<property name="checkoutTimeout">2000</property>
<!-- 最大空闲回收时间,连接池中的空闲连接多久没有使用就会回收-->
<property name="maxIdleTime">1000</property>
</default-config>
</c3p0-config>
APP程序入口
package com.example.jdbc;
public class App {
public static void main(String[] args) {
try {
String outUser = "jack";
String inUser = "rose";
Integer money = 100;
AccountService accountService = new AccountService();
accountService.transfer(outUser,inUser,money);
System.out.println("转账成功");
} catch (Exception e) {
System.out.println("转账失败");
e.printStackTrace();
}
}
}
service
package com.example.jdbc;
import org.apache.commons.dbutils.DbUtils;
import java.sql.Connection;
import java.sql.SQLException;
public class AccountService {
public void transfer(String from, String to, Integer money) {
AccountDao accountDao = new AccountDao();
Connection connection = null;
try {
connection = C3p0Utils.getConnection();
// 设置事务不自动提交
connection.setAutoCommit(false);
accountDao.outMoney(connection,from,money);
// 如果有异常
// int a = 1/0;
accountDao.inMoney(connection,to,money);
// 提交事务
DbUtils.commitAndCloseQuietly(connection);
}catch (SQLException e){
// 回滚事务
DbUtils.rollbackAndCloseQuietly(connection);
e.printStackTrace();
}
}
}
dao
package com.example.jdbc;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
import java.sql.SQLException;
public class AccountDao {
public void outMoney(Connection connection, String from, Integer money) {
QueryRunner queryRunner = new QueryRunner();
try {
String sql = "update account set money = money - ? where name = ?";
queryRunner.update(connection, sql, money, from);
} catch (Exception e) {
e.printStackTrace();
}
}
public void inMoney(Connection connection, String to, Integer money) {
QueryRunner qr = new QueryRunner();
try {
String sql = "update account set money = money + ? where name = ?";
qr.update(connection, sql, money, to);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
相关知识:ThreadLocal
java.lang.ThreadLocal
该类提供了线程局部(thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具
类底层就是相当于一个Map,key存放的当前线程,value存放需要共享的数据
package com.example.jdbc;
public class ThreadLocalDemo {
public static void main(String[] args) {
// 向ThreadLocal对象中添加的数据只能在当前线程下使用
ThreadLocal<String> mainThreadLocal = new ThreadLocal<>();
mainThreadLocal.set("main value");
// main value
System.out.println(mainThreadLocal.get());
new Thread(()->{
// null
System.out.println(mainThreadLocal.get());
}).start();
}
}
使用ThreadLocal改造案例
在“事务传递Connection参数案例”中,我们必须传递Connection对象,才可以完成整个事务操作。如果不传递参
数,是否可以完成?在JDK中给我们提供了一个工具类:ThreadLocal,此类可以在一个线程中共享数据。
java.lang.ThreadLocal
该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。
package com.example.jdbc;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class C3p0Utils {
// 创建一个c3p0的连接池对象
public static DataSource comboPooledDataSource = new ComboPooledDataSource();
private static ThreadLocal<Connection> localConnection = new ThreadLocal<>();
// 从池中获得一个连接
public static Connection getConnection() throws SQLException{
try {
// 从当前线程中,获得已经绑定的连接
Connection connection = localConnection.get();
if (connection == null){
// 第一次获得绑定内容,从连接池获得
localConnection.set(comboPooledDataSource.getConnection());
}
return connection;
}catch (Exception e){
//将编译时异常 转换 运行时 , 以后开发中运行时异常使用比较多的。
throw new RuntimeException(e);
}
}
}
package com.example.jdbc;
import org.apache.commons.dbutils.DbUtils;
import java.sql.Connection;
import java.sql.SQLException;
public class AccountService {
public void transfer(String from, String to, Integer money) {
AccountDao accountDao = new AccountDao();
Connection connection = null;
try {
connection = C3p0Utils.getConnection();
// 设置事务不自动提交
connection.setAutoCommit(false);
accountDao.outMoney(from,money);
// 如果有异常
// int a = 1/0;
accountDao.inMoney(to,money);
// 提交事务
DbUtils.commitAndCloseQuietly(connection);
}catch (SQLException e){
// 回滚事务
DbUtils.rollbackAndCloseQuietly(connection);
e.printStackTrace();
}
}
}
米店老板姓曾,今年62岁,
三十出头的时候,从四川广安来到重庆,
守着解放碑的一个粮店,送了二十几年大米,
在脊背越来越佝偻,被雇主嫌弃的时候,
自己在自力巷租房开了一家米店,
老曾头开米店的初衷,主要是为了赚取送货的力资,
可是在尝到利润甜头之后,
他时常感叹自己懂事太晚
最后的棒棒 何苦