jdbc&连接池&事务
预备知识:
一、数据库的基本知识
1. 数据库概述
l 什么是数据库
数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以对数据库中的数据进行增加,修改,删除及查询操作。
l 什么是数据库管理系统
数据库管理系统(DataBase Management System,DBMS):指一种操作和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控制,以保证数据库的安全性和完整性。用户通过数据库管理系统访问数据库中表内的数据。
l 数据库与数据库管理系统的关系
2. 数据库表
数据库中以表为组织单位存储数据。
表类似我们的Java类,每个字段都有对应的数据类型。
那么用我们熟悉的java程序来与关系型数据对比,就会发现以下对应关系。
类----------表
类中属性----------表中字段
对象----------记录
3. 表数据
根据表字段所规定的数据类型,我们可以向其中填入一条条的数据,而表中的每条数据类似类的实例对象。表中的一行一行的信息我们称之为记录。
l 表记录与java类对象的对应关系
4. 常见数据库
l 常见的数据库管理系统
MYSQL:开源免费的数据库,小型的数据库.已经被Oracle收购了.MySQL6.x版本也开始收费。
Oracle:收费的大型数据库,Oracle公司的产品。Oracle收购SUN公司,收购MYSQL。
DB2 :IBM公司的数据库产品,收费的。常应用在银行系统中.
SQLServer:MicroSoft 公司收费的中型的数据库。C#、.net等语言常使用。
SyBase:已经淡出历史舞台。提供了一个非常专业数据建模的工具PowerDesigner。
SQLite:嵌入式的小型数据库,应用在手机端。
常用数据库:MYSQL,Oracle.
这里使用MySQL数据库。MySQL中可以有多个数据库,数据库是真正存储数据的地方。
二、windows中安装mysql
见 <<MySQL安装图解.pdf>>
三、JDBC
1. JDBC概述
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API。JDBC是Java访问数据库的标准规范,可以为不同的关系型数据库提供统一访问,它由一组用Java语言编写的接口和类组成。
JDBC需要连接驱动,驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。 今天我们使用的是mysql的驱动mysql-connector-java-5.1.37-bin.jar
JDBC规范(掌握四个核心对象):
1) DriverManager:用于注册驱动
2) Connection: 表示与数据库创建的连接
3) Statement: 操作数据库sql语句的对象
4) ResultSet: 结果集或一张虚拟表
2. jdbc原理
Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动。
JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库!每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。
3. Jdbc入门案例
1) 准备数据
之前我们学习了sql语句的使用,并创建的分类表category,今天我们将使用JDBC对分类表进行增删改查操作
#创建数据库
create database day04;
#使用数据库
use day04;
#创建分类表
create table category(
cid int PRIMARY KEY AUTO_INCREMENT ,
cname varchar(100)
);
#初始化数据
insert into category (cname) values('家电');
insert into category (cname) values('服饰');
insert into category (cname) values('化妆品');
2) 导入驱动jar包
创建lib目录,存放mysql的驱动mysql-connector-java-5.1.37-bin.jar
选中mysql的jar包,右键选择“ Add as Library...” 完成jar导入
3) 代码演示
操作步骤:
1.注册驱动.
2.获得连接.
3.获得执行sql语句的对象
4.执行sql语句,并返回结果
5.处理结果
6.释放资源.
//查询所有的分类信息
@Test
public void demo1() throws Exception{
// 注意:使用JDBC规范,采用都是 java.sql包下的内容
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
String url = "jdbc:mysql://localhost:3306/mydb";
Connection conn = DriverManager.getConnection(url, "root", "root");
//3获得执行sql语句的对象
Statement stmt = conn.createStatement();
//4执行SQL语句
ResultSet rs = stmt.executeQuery("select * from category");
//5处理结果集
while(rs.next()){
// 获得一行数据
Integer cid = rs.getInt("cid");
String cname = rs.getString("cname");
System.out.println(cid + " , " + cname);
}
//6释放资源
rs.close();
stmt.close();
conn.close();
}
4. API详解
l 1) 注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver())
不建议使用,原因有2个:
导致驱动被注册2次
强烈依赖数据库的驱动jar
解决办法:
Class.forName("com.mysql.jdbc.Driver");
l 2) 获取链接
static Connection getConnection(String url, String user, String password)试图建立到给定数据库 URL 的连接。
参数说明
- url 需要连接数据库的位置(网址)
- user 用户名
- password 密码
扩展说明:
URL:SUN公司与数据库厂商之间的一种协议。
jdbc:mysql://localhost:3306/day04
协议:子协议:/ IP :端口号数据库
mysql: jdbc:mysql://localhost:3306/day04 或者 jdbc:mysql:///day04(默认本机连接)
oracle数据库: jdbc:oracle:thin:@localhost:1521:sid
l 3) java.sql.Connection接口: 连接对象
接口的实现在数据库驱动中。所有与数据库交互都是基于连接对象的
Statement createStatement();//创建操作sql语句的对象
l 4)java.sql.Statement 接口:操作sql语句, 并返回相应结果
String sql = "某SQL语句";
获取Statement语句执行平台:
Statement stmt =con.createStatement();
常用方法:
int executeUpdate(String sql);--执行insert update delete语句.
ResultSet executeQuery(String sql); --执行select语句.
boolean execute(String sql); --仅当执行select并且有结果时才返回true,执行其他的语句返回false.
l 5)处理结果集 :resultSet
注意 : 执行 insert、update、delete无需处理结果集
ResultSet实际上就是一张二维的表格,我们可以调用其boolean next()方法指向某行记录,当第一次调用next()方法时,便指向第一行记录的位置,这时就可以使用ResultSet提供的getXXX(int col)方法来获取指定列的数据:(与数组索引从0开始不同,这里索引从1开始)
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)获得双精度浮点型
l 6)释放资源
与IO流一样,使用后的东西都需要关闭!关闭的顺序是先得到的后关闭,后得到的先关闭。
rs.close();
stmt.close();
con.close();
5. Jdbc工具类
“获得数据库连接”操作,将在以后的增删改查所有功能中都存在,可以封装工具类JDBCUtils。提供获取连接对象的方法,从而达到代码的重复利用。
public class JdbcUtils {
private static String driver = "com.mysql.jdbc.Driver";
private static String url = "jdbc:mysql://localhost:3306/webdb_4";
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) {
}
}
}
}
6. Jdbc增删改查操作
l 插入操作
@Test
public void demo01(){
//添加
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1 获得连接
conn = JdbcUtils.getConnection();
//操作
//1) 获得语句执行者
st = conn.createStatement();
//2) 执行sql语句
int r = st.executeUpdate("insert into category(cname) values('测试')");
//3) 处理结果
System.out.println(r);
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
//释放资源
JdbcUtils.closeResource(conn, st, rs);
}
}
l 修改操作
@Test
public void demo02(){
//修改
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
int r = st.executeUpdate("update category set cname='测试2' where cid = 4");
System.out.println(r);
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JdbcUtils.closeResource(conn, st, rs);
}
}
l 删除
@Test
public void demo03(){
//删除
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//操作
st = conn.createStatement();
int r = st.executeUpdate("delete from category where cid = 4");
System.out.println(r);
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JdbcUtils.closeResource(conn, st, rs);
}
}
l 通过id查询详情
@Test
public void demo04(){
//通过id查询详情
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//操作
st = conn.createStatement();
rs = st.executeQuery("select * from category where cid = 30");
if(rs.next()){
String cid = rs.getString("cid");
String cname = rs.getString("cname");
System.out.println(cid + " @ " + cname );
} else {
System.out.println("没有数据");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JdbcUtils.closeResource(conn, st, rs);
}
}
l 查询所有
@Test
public void demo05(){
//查询所有
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//操作
st = conn.createStatement();
rs = st.executeQuery("select * from category");
while(rs.next()){
String cid = rs.getString("cid");
String cname = rs.getString("cname");
System.out.println(cid + " @ " + cname );
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JdbcUtils.closeResource(conn, st, rs);
}
}
四、预编译执行平台
1. 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来解决对应的问题。
7. API详解:预处理对象
preparedStatement:预编译对象,是Statement对象的子类。
特点:
l 性能高
l 会把SQL语句先编译
l 能过滤掉用户输入的关键词
PreparedStatement预处理对象,处理的每条sql语句中所有的实际参数,都必须使用占位符?替换。
String sql = "select * from user where username = ? and password = ?";
PreparedStatement使用,需要通过以下3步骤完成:
- PreparedStatement预处理对象代码:
// 获得预处理对象,需要提供已经使用占位符处理后的SQL语句
PreparedStatement psmt = conn.prepareStatement(sql)
- 设置实际参数
void setXxx(int index, Xxx xx) 将指定参数设置指定类型的值
参数1:index 实际参数序列号,从1开始。
参数2:xxx 实际参数值,xxx表示具体的类型。
例如:
setString(2, "1234") 把SQL语句中第2个位置的占位符?替换成实际参数 "1234"
- 执行SQL语句
int executeUpdate(); --执行insert update delete语句.
ResultSet executeQuery(); --执行select语句.
boolean execute(); --执行select返回true 执行其他的语句返回false.
8. 基于预处理实现CURD操作
l 1) 插入操作
@Test
public void demo01(){
//添加:向分类表中添加数据
Connection conn = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
//1 获得连接
conn = JdbcUtils.getConnection();
//2 处理sql语句
String sql = "insert into category(cname) values(? )";
//3获得预处理对象
psmt = conn.prepareStatement(sql);
//4设置实际参数
psmt.setString(1,"预处理");
//5执行
int r = psmt.executeUpdate();
System.out.println(r);
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
//6释放资源
JdbcUtils.closeResource(conn, psmt, rs);
}
}
l 2) 更新操作
@Test
public void demo02(){
//修改
Connection conn = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//1 sql语句
String sql = "update category set cname = ? where cid = ?";
//2 获得预处理对象
psmt = conn.prepareStatement(sql);
//3设置实际参数
psmt.setString(1, "测试数据");
psmt.setInt(2, 4);
//4执行
int r = psmt.executeUpdate();
System.out.println(r);
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JdbcUtils.closeResource(conn, psmt, rs);
}
}
l 3) 通过id查询详情
@Test
public void demo05(){
//通过id查询
Connection conn = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "select * from category where cid = ?";
psmt = conn.prepareStatement(sql);
psmt.setInt(1, 2);
rs = psmt.executeQuery();
if(rs.next()){
System.out.println("查询到");
} else {
System.out.println("查询不到");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JdbcUtils.closeResource(conn, psmt, rs);
}
}
五、使用连接池重写工具类
1. 连接池原理
连接池理解为存放多个连接的集合。
使用连接池技术的目的:解决建立数据库连接耗费资源和时间很多的问题,提高性能。
9. 编写标准的数据源(规范)
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
常见的连接池:C3P0、DRUID。
10. C3P0连接池工具类实现
C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用C3P0连接池需要导入jar包,c3p0使用时还需要添加配置文件“c3p0-config.xml”
使用步骤:
添加jar包
编写配置文件 c3p0-config.xml,放在src中(注:文件名一定不要写错)
编写工具类
l 编写配置文件 c3p0-config.xml
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day03</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>
说明: c3p0 连接池常用的配置参数说明:
initialPoolSize : 初始连接数 刚创建好连接池的时候准备的连接数量
maxPoolSize : 最大连接数 连接池中最多可以放多少个连接
checkoutTimeout : 最大等待时间 连接池中没有连接时最长等待时间
maxIdleTime : 最大空闲回收时间 连接池中的空闲连接多久没有使用就会回收
l 编写C3P0工具类
public class JdbcUtils {
//创建一个C3P0的连接池对象(使用c3p0-config.xml中default-config标签中对应的参数)
public static DataSource ds = new ComboPooledDataSource();
//从池中获得一个连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//释放资源
public static void closeAll(ResultSet rs, Statement stmt, Connection conn){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
rs = null;
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
stmt = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
conn = null;
}
}
}
l C3p0连接池工具类的使用
public class Demo {
public static void main(String[] args) throws Exception {
// 拿到连接
Connection conn = JdbcUtils.getConnection();
// 执行sql语句
String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "李四");
pstmt.setInt(2, 30);
pstmt.setDouble(3, 50);
int i = pstmt.executeUpdate();
System.out.println("影响的函数: " + i);
// 关闭资源
JdbcUtils.close(conn, pstmt);
}
}
六、事务操作
事务概述
事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
事务作用:保证在一个事务中多次SQL操作要么全都成功,要么全都失败. 安全性,完整性
1. Mysql事务操作
sql语句 |
描述 |
start transaction |
开启事务 |
commit |
提交事务 |
rollback |
回滚事务 |
l 准备数据
# 创建一个表:账户表.
create database webdb;
# 使用数据库
use webdb;
# 创建账户表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
# 初始化数据
insert into account values (null,'jack',10000);
insert into account values (null,'rose',10000);
insert into account values (null,'tom',10000);
l 操作
n MYSQL中可以有两种方式进行事务的管理
u 自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。
u 手动提交:先开启,再提交
l 方式1:手动提交
start transaction;
update account set money=money-1000 where name='jack';
update account set money=money+1000 where name='rose';
commit;
#或者
rollback;
l 方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制
11. 事务特征: ACID
原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)事务前后数据的完整性必须保持一致。
隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
12. 并发访问问题
如果不考虑隔离性,事务存在3中并发访问问题。
l 脏读:一个事务读到了另一个事务未提交的数据.
l 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
l 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
13. 隔离级别: 解决问题
数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a) 存在:3个问题(脏读、不可重复读、虚读)。
b) 解决:0个问题
read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a) 存在:2个问题(不可重复读、虚读)。
b) 解决:1个问题(脏读)
repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a) 存在:1个问题(虚读)。
b) 解决:2个问题(脏读、不可重复读)
serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
a) 存在:0个问题。
b) 解决:3个问题(脏读、不可重复读、虚读)
安全和性能对比
安全性:serializable > repeatable read > read committed > read uncommitted
性能 : serializable < repeatable read < read committed < read uncommitted
常见数据库的默认隔离级别:
MySql:repeatable read
Oracle:read committed
14. Jdbc事务操作
Connection 对象的方法名 |
描述 |
conn.setAutoCommit(false) |
开启事务 |
conn.commit() |
提交事务 |
conn.rollback() |
回滚事务 |
代码操作:
public class Demo01JDBC {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
try {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库连接对象Connection
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day04", "root", "root");
//开启事务
conn.setAutoCommit(false);
//3.获取执行sql语句的执行者对象Statement
stat = conn.createStatement();
//4.执行sql语句,获取结果
int row1 = stat.executeUpdate("update account set money=money-1000 where name='jack';");
System.out.println(0/0);
int row2 = stat.executeUpdate("update account set money=money+1000 where name='rose';");
//5.处理结果
if(row1>0 && row2>0){
System.out.println("转账成功!");
//提交事务
conn.commit();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("转账失败!");
//回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//6.释放资源
if(stat!=null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}