JDBC
JDBC简介
JDBC(java database connectivity,java数据库连接)
可以直接通过java语言操作数据库
jdbc是一套标准,它是由一些接口与类组成的的
涉及到的类和接口
主要再两个包下面
java.sql
类:DriverManger
接口:Connection Statement ResultSet PreparedStatment
CallableStatement(它是用于调用存储过程)
javax.sql
接口:DataSource
什么是驱动?
两个设备要进行通信,满足一定通信数据格式,数据格式由设别提供商规定,设备提供商为设备提供驱动软件
通过软件可以与改设备进行通信
JDBC入门
编写一个jdbc入门代码,完成对数据库的操作
create table USER(
id int primary key auto_increment,
username varchar(20) unique not null,
password varchar(20) not null,
email varchar(40) not null
);
desc USER;
select * from USER;
insert into USER values(null,'tom','123','ffdfsa@gg.com');
insert into USER values(null,'fox','123','ffdfsa@gg.com');
1.下载jar包
将驱动的jar包放在lib下
2.创建一个jdbcdemo类
//1.注册驱动
DriverManager.registerDriver(new Driver());
//2。获取连接对象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/day17", "root", "password");
//3.通过连接对象获取sql语句Statement
Statement st = con.createStatement();
//4.操作sql语句
String sql = "select * from user";
//操作select语句,会返回一个ResultSet结果集
ResultSet rs = st.executeQuery(sql);
//5.遍历结果集
// boolean flag = rs.next(); //向下移动,返回值为true,代表有下一条记录
// int id = rs.getInt(1);
//
// String username = rs.getString(2); //rs.getString("username");
// System.out.println(id);
// System.out.println(username);
while(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
String email = rs.getString("email");
System.out.println(id+" "+username+" "+password+" "+email);
}
//6.释放资源
rs.close();
st.close();
con.close();
DirverManager对象
DriverManager.registerDriver(new Driver());
//加载了两个驱动(com.mysql.jdbc.Driver里面有个静态代码块给加载了一下,
我们自己new Driver()又加载了)
Class.forName("com.mysql.jdbc.Driver"); //反射,只是加载一次驱动
用反射的的好处:
1.只加载一次,装入一个驱动对象
2.降低耦合,不依赖于驱动
通过DriverManager来获取连接对象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/day17", "root", "password");
url的作用,就是用于确定一个驱动
mysql url:jdc:mysql://localhost:3306/数据库名
oralce url:jdbc:oracle:thin:@localhost:1521:sid
DriverManager作用
1.注册驱动
2.获取连接Connection
关于url
url格式
主协议 子协议 主机 端口 数据库
jdbc: mysql: //localhost:3306/day17
mysql的url可以简写
前提:主机是localhost 端口是3306
jdbc:mysql///day17
url后面可以加参数 如:useUnicode=true
Connection详情
java.sql.Connection,它代表一个连接对象。就是程序于数据库的连接
Connection作用:
1.可以通过Connection获取操作sql的Statement对象
Statement createStatement() throws SQLException
2.操作事务
setAutoCommit(boolean flag);开启事务
rollback();事务回滚
commit();提交事务
Statement详解
java.sql.Statement用于执行sql语句
Statement作用:
1.执行sql
DML
int executeUpdate(String sql)
利用返回值判断非0来确定sql语句是否执行成功
DQL
ResultSet executeQuery(String sql)
可以通过execute方法来执行任何sql语句
2.批处理操作
addBatch(String sql); 将sql语句添加到批处理
executeBatch(); 批量执行
clearBatch(); 清楚批处理
ResultSet详解
java.sql.ResultSet他是用于封装select语句执行后的的结果
常用API:
1.next()方法
判断是否有下一条记录,如果有返回true,游标下移一行
2.getXxx()仿佛获取当前游标指向这条记录的数据
getInt()
getString()
getDate()
getDouble()....
参数有两种
1.getInt(int columnIndex)
2.getInt(String columnName)
如果列的类型不知到,可以通过下面的方法
getObject()
关闭资源
try...catch...
finally{
//6.释放资源
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(st != null)
st.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(con!=null)
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
JDBC示例—CURD
1.查询
1.查询全部
2.条件查询
2.修改
3.删除
4.添加
关于JdbcUtils抽取
只抽取到Connection
public static Connection getConnection() throws ClassNotFoundException, SQLException{
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection con = DriverManager.getConnection("jdbc:mysql:///day17","root","w1xd_RQC");
return con;
}
上述抽取的缺点
1.它只能针对于mysql数据库
2.每一次调用,都会注册一次驱动
改进
1.将关于连接数据库的信息定义到配置文件中
读取配置文件的时候加载
private static final String DRIVERCLASS;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
static{
DRIVERCLASS = ResourceBundle.getBundle("jdbc").getString("driverClass");
URL = ResourceBundle.getBundle("jdbc").getString("url");
USERNAME = ResourceBundle.getBundle("jdbc").getString("username");
PASSWORD = ResourceBundle.getBundle("jdbc").getString("password");
}
static{
//1.注册驱动,静态代码块只加载一次
try {
Class.forName(DRIVERCLASS);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection() throws ClassNotFoundException, SQLException{
//2.获取连接
Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
return con;
}
//关闭操作
public static void closeConnection(Connection con) throws SQLException{
if(con!=null){
con.close();
}
}
滚动结果集
默认得到的ResultSet它只能向下遍历(next()),对于ResultSet可以设置成滚动的,可以向上遍历,或者直接定位到一个指定的物理行号
问题:怎么得到一个滚动结果集?
设置参数
Statement st = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE
);
参数 int type
ResultSet.TYPE_FORWORD_ONLY 结果集的游标只能向下滚动。
ResultSet.TYPE_SCROLL_INSENSITIVE 结果集的游标可以上下移动,当数据库变化时,当前结果集不变。
ResultSet.TYPE_SCROLL_SENSITIVE 返回可滚动的结果集,当数据库变化时,当前结果集同步改变。
参数 int concurrency
ResultSet.CONCUR_READ_ONLY 不能用结果集更新数据库中的表。
ResultSet.CONCUR_UPDATETABLE 能用结果集更新数据库中的表。
dao模式
使用dao模式完成登陆操作
1.web层
login.jsp LoginServlet User
2.Service层
UserService
3.dao层
UserDao
sql注入
由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数中添加一些sql关键字,达到改变sql运行结果的目的,也可以完成恶意攻击
示例:
在输入用户名时 fox' or '1'='1
此时就不会验证密码了
解决方案:
PreparedStatement
他是一个预设Statement,是java.sql.Statement接口的一个子接口
总结PreparedStatement使用:
1.在sql语句中,使用"?"占位
String sql = "select * from user where username=? and password=?";
2.得到PreparedStatement对象
PreparedStatement pst = con.prepareStatement(String sql);
3.对占位符复制
pst.SetXxx(int index,Object obj);
index从1开始,占位符的序号
4.执行sql
DML pst.executeUpdate();
DQL pst.executeQuery();
无参数,因为sql已经被预处理了
他的优点:
1.解决sql注入(具有预处理功能)
2.不需要再拼sql语句
jdbc处理大数据
mysql中有大数据
blob大二进制
TINYBOLB(255),BLOB(64kb),MEDIUMBLOB(16m),LONGBLOB(4g)
text(clob)大文本
TINYTEXT(255),TEXT(64kb),MEDIUMTEXT(16m),LONGTEXT(4g)
大数据操作,一般只有insert select
演示:
create table myblob{
id int primary key auto_increment,
content longblob
}
向表中插入数据
mysql驱动不支持setBinaryStream(int,InputStream);
mysql支持pst.setBinaryStream(1,fis,file.length())也会出错,因为fis.length()是long类型
因该改为pst.setBinaryStream(1,fis,(int)file.length())
如果文件比较大,修改my.ini配置文件
jdbc批处理
一次可以执行多条sql语句
再jdbc中可执行sql语句的对象有Statment,PrepareStatement,他们都提供批处理
1.Statment执行批处理
st.addBatch(); 将sql语句添加到批处理
st.executeBatch(); 执行批处理
st.clearBatch();
1.PreparedStatment执行批处理
pst.addBatch(); 将sql语句添加到批处理
pst.executeBatch(); 执行批处理
pst.clearBatch();清空缓存
以上两个对象执行批处理的区别?
1.Statement更适合执行不同sql的批处理,它没有预处理功能,性能比较低
2.PreparedStatement它时候执行相同sql的批处理,它提供了预处理功能
mysql默认情况下,批处理中的预处理功能没有开启,需要开启
1.在url下添加参数
url=jdbc:mysql:///day17?useServerPrepStmts=true&cachePreStmts=true&rewriteBatcheStatements=true
2.注意驱动版本
5.1.13以上
事务
事务是什么,有什么用?
事务就是一个事情,组成这件事有多个单元,这些单元要么全成功,要么都不成功。保证数据的完整性
事务怎样操作
创建表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
insert into account values(null,'ccc',1000);
1.mysql下怎么操作
方式1:
start transaction 开启事务
rollback 事务回滚
commit 事务提交
方式2:
show variables like '%commit%'; 可以查看当前autocommit值
在mysql数据库中它的默认值是"on"代表自动事务
自动事务的意义就是:执行任意一条sql语句都会自动提交事务
测试:将autocommit设置为off(oracle默认是off)
set autocommit=off;手动commit才能提交
2.jdbc下怎么操作
java.sql.Connection接口中有几个方法可以操作事务
1.setAutocommit(boolean flag)
如果flag=false;相当于start transaction;
2.rollBack()
事务回滚
3.commit() 提交
String sql = "update account set money=500 where id=2";
Connection con = null;
Statement st = null;
try {
con = JdbcUtils.getConnection();
con.setAutoCommit(false); //开启事务start transaction
st = con.createStatement();
st.executeUpdate(sql);
} catch (Exception e) {
// TODO: handle exception
//事务回滚
con.rollback();
}finally{
try {
con.commit(); //事务提交
st.close();
con.close();
} catch (Exception e2) {
// TODO: handle exception
}
}
回滚放在catch中
事务特性(重点) ACID
原子性
一致性
隔离性:事物之间不能相互影响
持久性:一个事务一旦提价哦,对数据库数据的改变是永久的
如果不考虑事务的隔离性,会出现什么问题?
1.脏读 一个事务读取到另一个事务的未提交数据
2.不可重复读
两次读取的数据不一致(update)
3.虚读(幻读)
两次读取的数据不一致(insert)
4.丢失更新
两个事务对同一条记录进行操作,后提交的事务,将先提交的事务的修改覆盖了
演示以上问题,以及解决方案
对于以上问题,我们可以通过设置事务的隔离级别来解决
1.事务的隔离级别有哪些?
Serializable:可避免脏读,不可重复读,幻读情况的发生(串行化)
Repeatable read:可避免脏读,不可重复读
Read committed:可避免脏读情况发生(读已提交)
Read uncommitted:最低级别,以上均无法保证
2.怎么设置事务的隔离级别?
1.mysql中的设置
1.查看事务的隔离级别
select @@tx_isolation 查询当前事务的隔离级别
mysql默认是:REPEATABLE-READ
oracle默认是:Read committed
2.mysql中怎么设置事务隔离级别
set session transaction isolation level 设置事务隔离级别
2.jdbc的设置
在jdbc中设置事务隔离级别
使用java.sql.Connection接口中提供的方法
void setTransactionIsolation(int level) throw SQLException
参数可取:
Connection.TRANSACTION_READ_UNCOMMITTED
Connection.TRANSACTION_READ_COMMITTED
Connection.TRANSACTION_REPEATABL_READ
Connection.TRANSACTION_SERIALIZABLE
3.演示
1.脏读
一个事务读取到另一个事务的未提交数据
设置A,B事务隔离级别为 read uncommitted
set session transaction isolation level read uncommitted;
1.在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事务中
start transaction;
select * from account;
这时,B事务读取时,会发现,钱已近汇完。那么就出现了脏读
当A事务提交前,执行rollbak,再提交,B事务在查询,就会看发现,钱恢复成原样了
也就出现了两次查询结果不一致问题,出现了不可重复读
2.解决脏读问题
将事务的隔离级别设置为read committed;
将A,B的事务隔离级别都设置为read committed
set session transaction isolation level read committed;
1.在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事务中
start transaction;
select * from account;
A事务提交后,数据才变,解决了脏读但现在B事务再查询,数据不一致了,没有解决不可重复度
3.解决不可重复读
将事务的隔离级别设置为 Repeatable read来解决不可重复读
设置A,B事务隔离级别为 Repeatable read;
set session transaction isolation level repeatable read;
步骤同上
当A事务提交后,B事务查询,于上次查询结果一致
4.设置事务隔离级别为 Serializable,它可以解决所有问题
set session transaction isolation level Serializable;
会出现锁表,也就是说,一个事务在对表操作时,其他事务操作不了
案例:转账汇款----使用事务
问题:service调用了dao中两个方法完成了一个业务操作,如果其中一个方法执行失败怎样办?
需要事务控制
问题:怎样进行事务控制?
我们在service层进行事务的开启,回滚以及提交操作。
问题:进行事务操作需要使用Connection对象,那么,怎样保证,在service中与dao中所使用的是同一个Connection.
在service层创建出Connection对象,将这个对象传递到dao层.
注意:Connecton对象使用完成后,在service层的finally中关闭
而每一个PreparedStatement它们在dao层的方法中用完就关闭.
关于程序问题
1.对于转入与转出操作,我们需要判断是否成功,如果失败了,可以通过抛出自定义异常在servlet中判断,
进行信息展示 。
----------------------------------------------------------
问题:
在设置dao层时,
public interface AccountDao {
public void accountOut(String accountOut, double money) throws Exception;
public void accountIn(String accountIn, double money) throws Exception;
}
那么我们自己去实现这个接口时,怎样处理,同一个Connection对象问题?
使用ThreadLocal
ThreadLocal可以理解成是一个Map集合
Map<Thread,Object>
set方法是向ThreadLocal中存储数据,那么当前的key值就是当前线程对象.
get方法是从ThreadLocal中获取数据,它是根据当前线程对象来获取值。
如果我们是在同一个线程中,只要在任意的一个位置存储了数据,在其它位置上,就可以获取到这个数据。
关于JdbcUtils中使用ThreadLocal
1.声明一个ThreadLocal
private static final ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
2.在getConnection()方法中操作
Connection con = tl.get(); 直接从ThreadLocal中获取,第一次返回的是null.
if (con == null) {
// 2.获取连接
con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
tl.set(con); //将con装入到ThreadLocal中。
}
丢失更新
多个事务对同一条记录进行了操作,后提交的事务将先提交的事务更新了
怎么解决丢失更新问题
1.悲观锁
悲观锁 (假设丢失更新一定会发生 ) ----- 利用数据库内部锁机制,管理事务
1.共享锁
select * from table lock in share mode(读锁、共享锁)
2.排它锁
select * from table for update (写锁、排它锁)
摘抄的一段:对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制
update语句默认添加排它锁
2.乐观锁
乐观锁 (假设丢失更新不会发生)------- 采用程序中添加版本字段解决丢失更新问题
create table product (
id int,
name varchar(20),
updatetime timestamp
);
insert into product values(1,'冰箱',null);
update product set name='洗衣机' where id = 1;
连接池(数据源)
连接池是什么?有什么用?
连接池:创建一个容器,可以装入多个Connection对象,在使用连接对象时,从容器中获取一个Connection,使用后,重新装入连接池(DataSource)
连接池获取Connection对象
优点:节省创建连接与释放连接 性能消耗 ---- 连接池中连接起到复用的作用 ,提高程序性能
自动连接池
1.创建一个MyDataSource类,在这个类中创建一个LinkedList<Connection>
2.在其构造方法中初始化List集合,并向其中装入5个Connection对象。
3.创建一个public Connection getConnection();从List集合中获取一个连接对象返回.
4.创建一个 public void readd(Connection) 这个方法是将使用完成后的Connection对象重新装入到List集合中.
一些问题
1.创建连接池有标准
在javax.sql下有DataSource接口
连接池必须实现DataSource
自定义的连接池必须实现接口
2.我们操作时,要使用标准,怎样可以让con.close()不是销毁,而是将其重新装入到连接池
要解决这个问题,其本质是将Connection 的close()方法行为进行改变
三种改变行为的方法
1.继承
2.装饰模式
1.装饰类于被装饰类要实现同一个接口或继承同一个父类
2.在装饰类中持有一个被装饰引用
3.对方法进行功能增强
3.动态代理
Proxy.newProxyInstance(con.getClass().getClassLoader(),
con.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("close".equals(method.getName())) {
//将连接对象,重新装入到集合中
System.out.println("重新将con对象装入到连接池");
ll.add(con);
return null;
}else{
return method.invoke(con, args); //如果不是close方法,执行原来操作
}
}
});
Connection对象如果从连接池获取到,close方法的行为已经改变,不是销毁,
而是重新装入到连接池中
1.连接池必须实现javax.sql.DataSource接口。
2.要通过连接池获取连接对象 DataSource接口中有一个 getConnection方法.
3.将Connection重新装入到连接池 使用Connection的close()方法。
开源连接池
1.dbcp(了解)
dbcp是apache的一个开源连接池。
需要导包commons-dbcp-1.4.jar,commons-pool-1.5.6.pool
使用:
1.手动配置,编写
BasicDataSource bds = new BasicDataSource();
//需要设置连接数据库最基本的条件4个
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///day18");
bds.setUsername("root");
bds.setPassword("pssword");
//得到一个Connection
Connection con = bds.getConnection();
2.自动配置,用配置文件
FileInputStream fis = new FileInputStream("E:\\javaweb\\day18_2\\src\\dbcp.properties");
props.load(fis );
DataSource ds = BasicDataSourceFactory.createDataSource(props );
2.c3p0(重要)
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等
需要导包,c3p0-0.9.1.2.jar
使用
1.手动
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Dirver");
cpds.setJdbcUrl("jdbc:mysql:///day18");
cpds.setUser("root");
cpds.setPassword("password");
2.自动(配置文件)
c3p0的配置文件可以是properties,XML
c3p0的配置文件如果名称叫做c3p0.properties or c3p0-config.xml
并且放置在classpath路径下(对于web应用就是classes目录)
那么c3p0会自动查找
使用:
ComboPooledDataSource cpds = new ComboPooledDataSource();
//底层会自动查找c3p0-config.xml或者c3p0.properties
注意:我们其实只需要把配置文件放置在src下就可以
3.区别
c3p0与dbcp区别
dbcp没有自动回收空闲连接的功能
c3p0有自动回收空闲连接功能