JDBC:
* JDBC概念:Java DataBase Connectivity(Java数据库连接)
SUN公司提供的一组连接数据库API.
* JDBC开发步骤:
* 1.注册驱动.
* 2.获得连接.
* 3.执行SQL.
* 4.释放资源.
* JDBC入门案例:
* JDBC的API详解:
* DriverManager:
* 管理驱动:
* Class.forName(“com.mysql.jdbc.Driver”);
* 获得连接:
* getConnection(String url,String username,String password);
* Connection:
* 创建执行SQL的对象:
* createStatement();
* prepareStatement(String sql);
* 管理事务:
* setAutoCommit(boolean flag);
* commit();
* rollback();
* Statement:
* 执行SQL语句
* executeQuery(String sql);
* executeUpdate(String sql);
* execute(String sql);
* 执行批处理
* addBatch(String sql);
* executeBatch();
* clearBatch();
* ResultSet:
* 结果集:
* 默认只能向下不可以修改.
* 多条记录:
while(rs.next()){
rs.getXXX();
}
* 一条记录:
if(rs.next()){
}
* 滚动结果集:(了解)
JDBC开发CRUD的操作:
* 资源释放:
* Connection的资源是非常稀有的,应该做到晚创建,早释放!
* 抽取工具类:
* 提取了一个配置文件db.properties
EE开发中的DAO模式:
* DAO模式主要用来解决持久层的问题!
* 操作数据库!
DAO模式编写了一个登陆案例:
* 页面---->Servlet---->UserService---->UserDao
* 演示了SQL注入漏洞:
* 解决:
* PreparedStatement:对SQL进行预编译!
大文件读写:
批处理:
1.1 JDBC中事务管理:
1.1.1 事务:
事务:指的是逻辑上一组操作!一组操作要么全都成功,要么全都失败!
1.1.2 MYSQL数据库中操作事务
MYSQL的数据库的事务是自动提交的!!!Oracle不是自动提交的!!!
MySQL数据库,写一个SQL就是一个事务!!!
创建一个表:
create database day18;
use day18;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values (null,'美美',10000);
insert into account values (null,'小边',10000);
insert into account values (null,'冠希',10000);
insert into account values (null,'白鸽',10000);
insert into account values (null,'小白',10000);
MYSQL中管理事务:
有两种方式管理事务:
一、使用命令方式:
* start transaction; --- 开启事务
* commit; --- 事务提交
* rollback; --- 事务回滚
二、设置MYSQL数据库参数:
* show variables like '%commit%';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| autocommit | ON |
| innodb_commit_concurrency | 0 |
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
* 设置自动提交参数为OFF(0) ON(1)
set autocommit = 0;
1.1.3 JDBC中使用事务:
JDBC的事务
通过Connection对象中的
* setAutoCommit(false);
* commit();/rollback();
案例:
@Test
// 冠希给美美转账 1000元
public void demo1(){
Connection conn = null;
PreparedStatement stmt = null;
try{
// 获得连接
conn = JDBCUtils.getConnection();
// 管理事务:事务不是自动提交!
conn.setAutoCommit(false);
// 编写一个SQL语句
String sql = "update account set money = money + ? where name = ?";
// 预编译SQL
stmt = conn.prepareStatement(sql);
// 设置参数:
// 扣去冠希的1000元
stmt.setDouble(1, -1000);
stmt.setString(2, "冠希");
stmt.executeUpdate();
// 如果现在没有事务的保证:当扣除冠希的1000元之后,会发生什么情况?
// 这个时候 冠希的钱就被扣除了,但是美美没有收到钱!!!
// 所以多条SQL要由事务保证:事务就是保证逻辑上的一组操作要么全都成功,要么全都失败!
int a = 10 / 0;
// 为美美增加1000元
stmt.setDouble(1, 1000);
stmt.setString(2, "美美");
stmt.executeUpdate();
// 提交事务:
conn.commit();
}catch(Exception e){
// 异常发生了:
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
JDBCUtils.release(stmt, conn);
}
}
JDBC事务的保存点:(了解)
保存点的作用:回滚的时候不用回滚到最初始的状态!!!
Connection中有
* setSavepoint() --- 设置一个保存点
* rollback(SavePoint savePoint); --- 事务回滚到保存点的位置
@Test
// 小边跟美美说你借我1w,设置一个保存点,我还你10w.但是小边的账号余额不足.回滚到保存点的位置!
public void demo2(){
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try{
// 获得连接
conn = JDBCUtils.getConnection();
// 开启事务:
conn.setAutoCommit(false);
// 编写一个sql语句.
String sql = "update account set money = money + ? where name = ?";
// 预编译SQL
stmt = conn.prepareStatement(sql);
// 扣除美美的1w元
stmt.setDouble(1, -10000);
stmt.setString(2, "美美");
stmt.executeUpdate();
// 给小边增加1w元
stmt.setDouble(1, 10000);
stmt.setString(2, "小边");
stmt.executeUpdate();
// 设置一个保存点
Savepoint savePoint = conn.setSavepoint();
// 小边还给美美10w
stmt.setDouble(1, -100000);
stmt.setString(2, "小边");
stmt.executeUpdate();
stmt.setDouble(1, 100000);
stmt.setString(2, "美美");
stmt.executeUpdate();
sql = "select * from account where name = ?";
stmt = conn.prepareStatement(sql);
// 设置参数
stmt.setString(1, "小边");
rs = stmt.executeQuery();
if(rs.next()){
if(rs.getDouble("money")<0){
conn.rollback(savePoint);
System.out.println("上当了!!!");
}
}
// 事务提交
conn.commit();
}catch(Exception e){
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
JDBCUtils.release(rs, stmt, conn);
}
}
1.1.4 事务的特性:(面试)
原子性:
* 强调的是事务的不可分割!
一致性:
* 事务执行的前后,数据的完整性保持一致!
隔离性:
* 一个事务的执行,不应该受到另一个事务的打扰!
持久性:
* 事务执行结束!数据就永久的保存数据库中!
如果不考虑事务的隔离性:引发哪些问题?
* 引发三种读的问题.
* 脏读 :一个事务读到了另一个事务未提交的数据!!!
* 不可重复读 :一个事务读到了另一个事务已经提交数据(另一个事务中做的是update操作),导致多次查询结果在一个事务中不一致!
* 虚读(幻读) :一个事务读到了另一个事务已经提交数据(另一个事务中做的是insert操作),导致在事务中做的统计的结果不一致!!
* 如何解决问题?
* 数据库提供了四个隔离级别解决三种读问题!!!
* read uncommitted :未提交读.那么脏读、不可重复读、虚读都是有可能发生!!
* read committed :已提交读.避免脏读。但是不可重复读和虚读是有可能发生!!
* repeatable read :重复读.避免脏读、不可重复读。但是虚读是有可能发生的!!
* serializable :串行的.避免脏读、不可重复读、虚读的发生!!
* 安全性:
read uncommitted < read committed < repeatable read < serializable
* 效率性:
read uncommitted > read committed > repeatable read > serializable
***** 数据库中使用的时候一般不会采用最低和最高的!!!
* MYSQL中使用的是repeatable read Oracle中read committed
通过命令查看事务的隔离级别:
* select @@tx_isolation;
设置数据库隔离级别:
* set session transaction isolation level 隔离级别;
演示脏读发生:
脏读:一个事务读到了另一个事务未提交的数据!!!
1.开启两个窗口A、B!分别连接到MYSQL数据库!
* 分别在两个窗口中查看隔离级别!
* select @@tx_isolation;
2.设置A窗口的隔离级别为read uncommitted
* set session transaction isolation level read uncommitted;
* 查看A窗口的隔离级别
* A:READ-UNCOMMITTED
* B:REPEATABLE-READ
3.两个窗口分别开启事务:
* start transaction;
4.在B窗口中完成转账的代码:但是事务不要提交!
* update account set money = money - 1000 where name = '冠希';
* update account set money = money + 1000 where name = '美美';
5.在A窗口中进行查询:
* select * from account;
* 发现钱已经到账了!!!脏读.(A窗口已经读到B窗口还没有提交的数据)
演示不可重复读的发生(避免脏读)
不可重复读:一个事务读到另一个事务已经提交的数据!(另一个事务update)导致当前的事务多次的查询结果不一致
* 避免脏读:设置隔离级别为read committed
1.开启两个窗口A、B!分别连接到MYSQL数据库!
* 分别在两个窗口中查看隔离级别!
* select @@tx_isolation;
2.在A窗口中设置数据库隔离级别为read committed
* set session transaction isolation level read committed;
* A:READ-COMMITTED
* B:REPEATABLE-READ
3.两个窗口分别开启事务:
* start transaction;
4.在B窗口完成转账代码:
* update account set money = money - 1000 where name = '冠希';
* update account set money = money + 1000 where name = '美美';
***** 先不提交事务!
5.在A窗口进行查询!
* select * from account;
* 这个时候钱没有到账!(避免脏读了 一个事务没有读到另一个事务未提交的数据)
6.在B窗口提交事务:
* commit;
7.在A窗口再次去查询!
* select * from account;
* 发现这次结果和上次的结果不一致(两次查询是在一个事务中的!):不可重复读.
演示避免不可重复读
1.开启两个窗口A、B!分别连接到MYSQL数据库!
* 分别在两个窗口中查看隔离级别!
* select @@tx_isolation;
2.设置A窗口的隔离级别:repeatable read
* set session transaction isolation level repeatable read;
* A:REPEATABLE-READ
* B:REPEATABLE-READ
3.两个窗口分别开启事务:
* start transaction;
4.在B窗口完成转账:
* update account set money = money - 1000 where name = '冠希';
* update account set money = money + 1000 where name = '美美';
事务没有提交
5.在A窗口查询:
* select * from account;
* 数据没有变化:避免脏读!
6.在B窗口提交事务:
* commit;
7.在A窗口再次查询:
* select * from account;
* 数据还是没有变化:避免了不可重复读!
演示隔离级别为serializable:
Serializable:串行的!可以避免脏读、不可重复读、虚读!
1.开启两个窗口A、B!分别连接到MYSQL数据库!
* 分别在两个窗口中查看隔离级别!
* select @@tx_isolation;
2.设置A窗口的隔离级别为 Serializable
* set session transaction isolation level Serializable;
* A:SERIALIZABLE
* B:REPEATABLE-READ
3.在两个窗口分别开启事务:
* start transaction;
4.在B窗口中插入一条记录:
* 事务不提交:
5.在A窗口中进行查询:
* select * from account;
* 屏幕卡住了:因为另一个事务没有提交.这个事务就不能执行,串行的!!!
1.1.5 JDBC中如何设置隔离级别
通过Connection对象的:
* setTransactionIsolation(int level) ;
Connection中有以下的常量:
static int TRANSACTION_READ_COMMITTED
指示不可以发生脏读的常量;不可重复读和虚读可以发生。
static int TRANSACTION_READ_UNCOMMITTED
指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。
static int TRANSACTION_REPEATABLE_READ
指示不可以发生脏读和不可重复读的常量;虚读可以发生。
static int TRANSACTION_SERIALIZABLE
指示不可以发生脏读、不可重复读和虚读的常量。
1.1.6 丢失更新:(扩展)
第一类丢失更新
A事务撤销时,把已提交的B事务的数据覆盖掉。这种错误会造成非常严重的后果。
第二类丢失更新
A事务提交时,把已提交的B事务的数据覆盖掉。这种错误会造成非常严重的后果。
解决丢失更新问题:
* 悲观锁:假设丢失更新一定存在!
* 使用的是数据库的一种锁机制:排他锁.
* 乐观锁:假设丢失更新不一定存在!
1.2 连接池
1.2.1 什么是连接池
连接池:就是一个装了很多连接一个容器.(内存中的一块空间,装了很多连接.)
1.2.2 使用连接池:
1.编写一个类 实现DataSource接口.
2.重写getConnection()的方法.
自定义连接池的代码:
public class MyDataSource implements DataSource{
private List<Connection> list = new ArrayList<Connection>();
// 提供一个构造方法:
public MyDataSource(){
// 创建3个连接
for (int i = 1; i <=3; i++) {
// 向list集合放入连接
list.add(JDBCUtils.getConnection());
}
}
// 从连接池中获得连接
public Connection getConnection() throws SQLException {
// 从连接池中获得连接.连接池的扩容
if(list.isEmpty()){
for (int i = 1; i <=3; i++) {
// 向list集合放入连接
list.add(JDBCUtils.getConnection());
}
}
Connection conn = list.remove(0);
return conn;
}
// 用完之后,不是销毁连接而是归还到连接池
public void addBack(Connection conn){
list.add(conn);
}
public PrintWriter getLogWriter() throws SQLException {
return null;
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public Connection getConnection(String username, String password)
throws SQLException {
return null;
}
}
****** 自定义连接池中在使用的使用!需要让程序员额外记住一些自定义的方法例如addBack();而且在构造的时候不能使用接口构造.
能不能有一种方式实现:使用接口构造连接池,而且在归还的时候不需要额外提供方法???
* 原来的Connection的close方法是将连接对象销毁!现在是否可以增强close方法,使方法的逻辑改变,归还连接而不是销毁连接?
* 增强Java中类的方法有几种方式?
* 继承 :
class Person{
public void run(){
System.out.println("跑步1000米...");
}
}
class SuperPerson extends Person{
@Override
public void run() {
// super.run();
System.out.println("跑步10000米...");
}
}
测试:
@Test
public void demo1(){
Person p = new SuperPerson();
p.run(); // 子类的方法.
}
***** 继承这种增强某个类的方法使用条件的:
* 必须能够控制这个类的构造!
* 装饰者模式 :
代码:
interface Bird{
public void fly();
}
// 被增强的类
class MaQue implements Bird{
public void fly() {
System.out.println("飞200米...");
}
}
// 增强的类
class DaMaQue implements Bird{
private Bird bird;
public DaMaQue(Bird bird){
this.bird = bird;
}
public void fly() {
bird.fly();
System.out.println("飞500米...");
}
}
测试:
@Test
public void demo1(){
// Bird bird = new MaQue();
// bird.fly();
Bird bird = new DaMaQue(new MaQue());// IO流
bird.fly();
}
***** 装饰者这种增强某个类的方法使用条件的:
* 1.增强类和被增强类实现相同的接口.
* 2.在增强的类中获得到被增强类的引用.
***** 缺点:
* 接口中的方法特别多!
* 动态代理 :
1.2.3 DBCP连接池:
DBCP(DataBase connection pool),数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件。
使用DBCP连接池
1.引入jar包:
* commons-dbcp-1.4.jar
* commons-pool-1.5.6.jar
2.核心类:
* 手动设置参数.
* 采用配置文件的方式.
1.2.4 C3P0连接池:
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等
使用C3P0连接池
1.导入jar包:
* c3p0-0.9.1.2.jar
2.ComboPooledDataSource
1.2.5 Tomcat内置连接池:(JNDI技术.)
JNDI:Java Naming and Directory Interface,Java命名和目录接口.
* 对一个Java对象起一个名字.通过这个名称查找到该对象.
JNDI连接池:
* 需要有一个配置:<Context>标签的配置.
* <Context>标签:在Tomcat学习的时候使用.配置Tomcat的虚拟路径.
* <Context>标签可以配置在三个地方:
* Tomcat/conf/context.xml:可以被Tomcat下的所有的虚拟主机、虚拟路径访问!
* Tomcat/conf/Catalina/localhost/context.xml:可以Tomcat下的localhost虚拟主机下的所有路径访问!
* 当前的工程下/META-INF/context.xml:只能被当前的工程访问!
使用JNDI连接池:
* 1.在工程的/META-INF/创建一个context.xml
<Context>
<Resource name="jdbc/EmployeeDB" auth="Container"
type="javax.sql.DataSource" username="root" password="123"
driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql:///day18"
maxActive="8" maxIdle="4"/>
</Context>
* 2.Tomcat维护这些连接.这些连接是由Tomcat服务器创建的.
* 必须在Tomcat/lib下copy一个数据库驱动包.
* 3.操作连接池这个类必须是运行在Tomcat服务器中的类!
* 运行在Tomcat中的类是Servlet!!!