引入:
先猜想一下 java程序如何和mysql建立连接:思路:可以用socket,需要遵循的是mysql协议。那么mysql又是如何规定发送的数据的格式和mysql返回的响应数据的格式呢?
java程序通过mysql协议发送请求(内容以sql语句)给服务端,服务端按照mysql协议处理数据结果,
java程序再解析协议,处理数据(这些过程mysql已经给我们提供了一些API)
但是又出现了一个问题:不同的数据库的协议不同,那么API也不会不同,那我们的java程序就得重写去适配不同的数据库。
此时sun公司指定了一套标准协议,所有支持java语言的数据库,必须支持java语言的一套协议:jdbc
JDBC:java database connectivity
也就是数据库和java程序交互的一套标准API。但是这些API,大多都是接口,我们还必须实现?
这些具体实现,都通过不同的数据库厂商自己来实现,也就是 数据库的驱动程序。
所以虽然实现不同,但是都是相同的接口。
驱动程序: 实现mysql协议的jar包
所以jdbc的流程就清晰了:
1.建立连接
2.书写sql语句,mysql服务器发送sql语句
3.mysql接收并按照sql语句来处理数据
4.mysql服务器发送响应到客户端,客户端接收数据
5.java程序按照业务逻辑,处理响应数据。
6.释放资源。
测试框架:
对public class 类名,的类名右键,Goto ——> Test ——>JUnit4(开源测试框架)
里面的所有方法都可以单独运行。
一旦添加过这个JUnit4框架,以后就直接在普通类中的 要运行的方法 添加注解:@Test
编写JDBC程序:
1.向JVM注册驱动:注册的是具体的数据库开发厂商实现的Driver类对象
DriverManager.registerDriver(new Driver());(这个Driver是mysql的)
2.建立连接(数据库jdbc和java程序):
String url="jdbc:mysql://localhost:3306/jdbc"
url:要连接哪个数据库,格式: jdbc:mysql(主协议):[子协议(可有可无)] //localhost:3306(主机端口号)/test(数据库名称)(?参数名=参数值)
String user="root"
连接数据库的用户名
String password="1234"
密码
Connection connection = DriverManager.getConnection(url,user,password)
引用表示java和mysql服务器的连接。
3.写sql语句,将请求发送给服务器。
String sql ="insert into user(id,name,age) values(1,'zs',18)"
Statement statement = connection.createStatement();
//增/删/改(DML语句)的时候使用,返回值 int i 代表 本次操作所影响的行数。
int i = statement.excuteUpdate(sql);
//查(DQL语句)
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
//查找数据表的属性值
1.通过列名获取每个属性
resultSet.getInt("id");
resultSet.getString("name");
2.通过列名的index索引来获取(index 从1 开始编号,表示列数)
resultSet.getInt(1);
}
4.根据业务逻辑处理响应数据。
if(i>0){
sout("插入成功")
}
5.释放相关资源
//如果只有增删改操作的话:
statement.close();
connection.close();
程序详解:
1.驱动的加载:
DriverManager.registerDriver(new Driver());查看mysql的源码发现,Driver类自己已经有了静态方法:Static { DriverManager.registerDriver(new Driver());}
所以我们就不需要重复书写这句代码,只要确保这句代码在我们自己的程序执行之前执行就可以了。
但是如何做呢?注意到 此代码是Driver类的静态代码块中的,又因为静态代码块在类加载的时候执行。
那么有几种方式类加载:
1.生成对象:DriverManager.registerDriver(new Driver());//需要导mysql.Driver包。
2.Class.forName("com.mysql.jdbc.Driver") 全类名(推荐)
3.放在程序的静态代码块中,statice{Class.forName("com.mysql.jdbc.Driver")}
4.坑,不会加载静态代码块的方法:driver.class()
如果我想要跨数据库来加载(不同的驱动都可以运行) : 通过配置文件。
2.建立连接
方式一:设置好url,user,password
Connection connection DriverManager.getConnection(url,user,password);
方式二:
原url:String url="jdbc:mysql://localhost:3306/jdbc"
灵活使用url(user/password通过参数给) : String url="jdbc:mysql://localhost:3306/jdbc?user=root&password=****"
如果默认IP地址是localhost、端口号是3306,则简写:String url="jdbc:mysql:///jbdc?...."
方式三:
配置文件:在 src 目录下new 一个配置文件(configure)。
url="jdbc:mysql://localhost:3306/jdbc"
user=""
password=""
在类中:
把配置文件当Map<String,String>用
Properties properties = new Properties();
new FileInputStream("相对路径")
那么相对路径是什么?sout(System.getProperty("user.dir"))
相对路径会改动,如果启动服务器的话,所以行不通
获取配置文件:
法一:URL resouce = 类名.class.getClassLoader().getResource("配置文件名")
这个路径 是 相对于 src的路径(虽然其的路径是到了classes目录下,但是是把配置文件复制到classes目录下的)
法二:
(通过反射拿到配置文件,放入输入流中)
InputStream RS = 类名.class.getClassLoader().getResourceAsStream("配置文件名");
//加载配置文件
properties.load(RS);
//获取Driver的全类名:在配置文件中 : URL=com.mysql.jdbc.Driver
String url = properties.getProperty("URL");
//建立连接
Connection connection = DriverManager.getConnection(url,properties);
3.写sql语句,发送给服务器。
1.获取statement对象Statement statement = connection.createStatement();
2.ResultSet — 结果集(也是一种需要释放的资源)
ResultSet resultSet = statement.executeQuery(sql);
ResultSet 结果集 中会维护一个游标,用游标来指示表的具体行。
遍历 ResultSet 结果集:
先将游标向后移动一个位置,如果游标的当前位置是一个有效的位置(表内)就返回true
(无效位置就是表的第一行数据之前和最后一行数据之后。)
//开始的时候游标处在第一行数据之前的位置
while(resultSet.next()){ //.next()结果返回true或false
//遍历每一行,获取其每一个属性值
//两种方式获取当前行的各个属性值
1.通过列名获取(游标所指的行)
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
2.通过列的 index索引值 来获取(不太推荐,可读性差)
index 从第一列开始编号.
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int age = resultSet.getInt(3);
}
游标的位置还可以通过API进行修改:
resultSet.afterLast() ——将游标放在表中最后一行之后
resultSet.BeforeFrist() ——将游标放在表中第一行之前
absolute(int row) ——将游标放在指定行(从1开始),要注意遍历的时候,游标会先移动一位
游标的前后数据判断:(返回的都是boolean类型)
resultSet.next()/resultSet.previous()
一个注意点(注意区分):
//查询操作,返回的是一个结果集
ResultSet resultSet = statement.executeQuery(sql);
// 增 删 改 操作,返回的是一个修改了多少行的数值
int i = statement.executeUpdate(sql);
3.根据业务逻辑处理响应
1.引入 TestCase ,让测试框架根据我们的业务逻辑来判断测试成功与否TestCase.assertTrue(i > 0);(这里i是上面的修改数据表的行数)
执行条件表达式如果为false,会直接抛出异常。
2.测试框架中异常的处理:
如果测试框架中,自己捕获了异常,那么测试框架就会忽略这个异常,继续执行。
4.释放资源
因为Statement/Connection/ResultSet 都继承了 AutoCloseable,所以他们的释放可以写一个方法来释放,而不用在主代码中写:if(st != null){st.close()}if(ct != null){ct.close()}....
public static void release(Statement st,Result rs,Connection con){
closeQuietly.st;
closeQuietly.rs;
closeQuietly.con;
}
private static void closeQuietly(AutoCloseable closeable){
//因为Statement/Connection/ResultSet 都继承了 AutoCloseable
if(closeable != null){
closeable.close();
}
}
发现上面的代码有很多重复的代码,自己创建一个工具类DBUtil:
public class DBUtil{1.注册驱动
static {
2.建立链接
//将全类名放入配置文件 class="com.mysql.jdbc.Driver"
Properties properties = new Properties();
InputStream configureStream = 类名.class.getClassLoader().getResourceAsStream("配置文件名.properties");
//加载配置文件
properties.load(configureStream);
//从配置文件中拿东西
Class.forName("com.mysql.jdbc.Driver")}
2.public static Connection getConnection(){ //这里的Connection对象最好导java.sql.Connection
return DriverManager.getConnection(url,properties)
}
3.将释放资源的代码放入(很重要的一种释放流的方式!!!)
public static void release(Statement st,Result rs,Connection con){
closeQuietly.st;
closeQuietly.rs;
closeQuietly.con;
}
private static void closeQuietly(AutoCloseable closeable){
//因为Statement/Connection/ResultSet 都继承了 AutoCloseable
if(closeable != null){
closeable.close();
}
}
}
————————————————————————————————————————————————————
Day20 SQL注入(安全问题):
当用户名输入成:zs' or 1=1 -- 还有一个空格则这条数据会变成:NAME='zs' OR 1=1 -- 'and password=1234'
后面的密码变成了注释,前面的语句变成了true。
主要原因是用户输入的东西里面包含了sql语句,会被解析和执行。
解决方案一:
分开发送 指令 和 参数值(PrepareStatement)
在发送的时候,只发送占位符sql = select ...name=? and password=?;
connection.prepareStatement(sql);
第二次发送他们的账号和密码:(这时,mysql会将所有符号,转义,就仅代表符号了)
statement.executeUpDate()
prepareStatement和statement的区别:
1.statement发送sql语句 和 mysql服务器通信一次
2.perpareStatement 和mysql服务器通信两次,第一次发送sql指令,第二次发送sql参数值。
实例:(通过prepareStatement拿到数据库数据)
connection = MyDBUtil.getConnection();//建立连接的方法已经写MyDBUtil..这是什么颜色//引入两个占位符,占位符都有编号,从左往右依次编号(从1开始)
String sql = "select * from user where name=? and password=?;";
//发送sql语句,mysql服务器会进行预编译,使用 prepareStatement 方法。
pre = nonnection.prepareStatement(sql);
//设置参数值,第一个参数:parameterIndex:占位符编号,第二个参数:实际传递的参数值
pre.setString(1,"zs");
pre.setString(2,"1234");
//查找
//发送参数(连接已经建立好了,所以只需要拼接executeQuery)
ResultSet rs = pre.executeQuery();
while(rs.next()){
int id = es.getInt("id");
...
}
//修改
//插入也是差不多
String sql = "insert into user values(?,?,?,?);"; //使用占位符,后面的批处理中会发现这个的好处。
pre.setInt(1);
pre.setString("zs");
pre.setInt(18);
pre.setString("1234");
//增删改的时候使用executeUpdate
int i = pre.executeUpdate(sql);
TestCase.assertTrue(i > 0);//如果i<=0会直接抛出异常
单条sql语句的执行:statement的效率 > prepareStatement (prepare继承自Statement)
但是 prepareStatement 可以阻止sql注入问题的发送,但是statement不行
引入JDBC的批处理机制:
主要用于数据表的 增 删 改 操作,查询不需要批处理一次执行多条sql语句。Batch
API:addBatch() /executeBatch() /clearBatch()
//sql语句添加到批处理中
statement.addBatch(sql语句);
//发送批处理,数组每一个元素代表每个sql语句(代码顺序从上到下)所影响的行数
//表明 批处理 只是针对 增删改,查询不需要批处理
int[] result = statement.executeBatch()
很多条sql语句的时候:
方法一:
使用 statement 对象。
for(int i = 0; i < 50 ;i++)
String sql = "insert into user values('"+i+"');";
//每十条执行一次发送
statement.executeBatch();
//清空上一次的批处理缓存
statement.clearBatch();
}
如果sql语句都是一样的,只是其中的参数值不同,那么上面的方法就不是很好,多次编译同一句sql语句
可以使用另一种实现形式:
方法二:
利用 prepareStatement对象 执行批处理,不过具有局限性:只能针对sql指令一样,参数值不同的情况。
因为指令会传送到服务器端预编译后放入缓存,然后一次性发送所有参数
String insertSql="insert into user values(?,?,?,?);";
PrepareStatement pre = connection.prepareStatement(insertSql)
for(int i = 1;i<50;i++){
pre.setInt(1,i);
pre.setString(2,i+"");
pre.setInt(3,i);
//添加到批处理中
pre.addBatch();
}
pre.executeBatch();
方法一、二的比较:
statement 对象执行批处理,相同的sql每次都会执行,效率低
prepareStatement 对象执行批处理,sql语句会被编译一次,但是,会和mysql服务器
通信两次(第一次发送预编译的sql指令,第二次发送sql参数)
所以在批处理中,prepaStatement执行效率比较高,不过有限定条件(指令相同,参数不同)
JDBC事务(Transaction)
1.概述
事务(transaction)是作为单个逻辑工作单元执行的一系列操作。
这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。
事务的特点(ACID特性):
1.原子性(Atomicity):
事务是一个不可分割的工作单位。
2.一致性(Consistency):
事务必须使数据库从一个一致性状态转变到另一个 一致性状态。
(例如银行转账,张三要给李四转100元。则第一步张三的账户需要减去100元,第二步李四的账户需要加上100元。
这是两个操作,但是应该在一个事务里面。如果没有在一个事务里面,张三减去100,李四并没有增加100,
那这样数据就出现了不一致性,张三的钱跑哪去了呢?)
3.永久性(Durability):
修改会被数据库永久保存,
4.隔离性(Isolation):
事务是数据库中并发控制的基本单位。
多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,
多个并发事务之间要相互隔离。(事物之间不能相互干扰)
比如业务A:张三减100,李四加100;同时业务B也是张三减100,李四加100进行操作。
业务A和B是同时的,这时候就出现了并发,这个时候是怎么变化的呢?
当业务员A进行操作的时候,业务员B就要等待……就是同一时间对数据库的操作要保持一个事务的锁定。
也就是说我在做的时候,别人是不能做的。我做完了之后别人才能做,彼此之间是隔离的
事务的 隔离性 和 隔离级别:
如果事务不考虑隔离性,可能会产生以下三种情况:
(因为虽然事务是原子操作,但是事务还有 隔离级别 之分)
1.脏读:当一个事务修改了某一数据行的值而未提交时,另一事务读取了此行值。
倘若前一事务发生了回退,则后一事务将得到一个无效的值(“脏”数据)。
2.不可重复读:当一个事务在读取某一数据行时,另一事务同时在修改此数据行。
则前一事务在重复读取此行时将得到一个不一致的值。
3.幻读:在一次事务中读取到了别的事务插入的数据,导致前后读取不一致。
如何处理这些情况呢?隔离级别
数据库一共设置了四种隔离级别。
Serializable :可避免 脏读、不可重复读、虚读情况的发生。(串行化)
Repeatable read:可避免 脏读、不可重复读情况的发生。(无法避免虚读)
Read committed:可避免 脏读情况发生。(无法避免不可重复读)
Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
设置方式:set session transaction isolation level read uncommitted;
set session transaction isolation level 设置事务隔离级别
select @@tx_isolation 查询当前事务隔离级别。
数据库 对 共享数据 天然存在加锁的操作。
在MySQL中的默认隔离级别是比较高的,但是实现不同:(REPEATABLE-READ)
隔离级别名称(可重复读):Repeatable-read (可避免脏读,不可重复读,幻读等的情况)
执行事务
1.命令行(DOM)中执行一次事务:
start transaction;//开始事务
执行原子操作;
commit;//托付(交给sql服务器)
2.利用java代码使用事务。
当java程序向数据库获得一个connnection对象时,默认会一句一句执行sql语句。
作为事务,执行单位应该是一组原子操作, 事务操作默认是自动提交。
API:
setAutoCommit(false) : 禁止自动提交
(相当于命令行的 start transaction;)
connection.rollback();事务回滚,
(相当于命令行的commit)
比如说我们插入的数据、更新的数据都会变成原来没有更新、没有插入时的样子。
(一般来说这个是mysql服务器自己做的,)
代码流程:
1.获取连接
Connection connection = MyDBUtil.getConnection();
2.创建sql语句
String sql1 ="update account set money=money-100 where name='zs';";
String sql2 ="update account set money=money+100" where name='lisi';";
//开启事务
connection.setAutoCommit(false);
Statement statement = connection.createStetament();
statement.executeUpdate(sql1);
int i = 1/0; //会报异常 并 执行回滚操作,开启事务之后的所有修改都会被抹除。
statement.executeUpdate(sql2);
//提交,完成本次事务
connection.commit();
设置回滚点:(三处注意点)
如果不想抹除开启事务之后的所有修改,可以设置回滚点。
Savepoint save = connection.setSavapoint();
//且必须自己调用回滚方法,告诉程序要回滚到哪个点
try{}catch(exception e){
connection.rollback(save);
//如果想要让回滚点之前的代码生效,还必须提交才行。
connection.commit();
}
设置回滚点之后,回滚的位置就不是整个事务,而是从发生异常处 —— 到回滚点。回滚点之前,事务开启之后 的修改不会被抹除。
总结一下jdbc的事务:
1.什么是事务?
事务:单独的一系列逻辑工作的执行单元。
这些操作作为一个整体,一起向系统提交,也就是事务中的操作,要么都执行,要么都不执行。
2.事务有哪些特性?
1.原子性:
原子操作,事务是一个不可分割的工作单位。
2.一致性:
完成一次事务的前后,数据库的状态必须保持一致性。
就比如银行转账:张三给李四转了100元,分为两步:张三—100,李四+100,。两个操作应该在同一个事务中。
3.永久性:
一次成功的事务中的操作对数据库的修改应该是永久保存下来了的。
4.隔离性:
事务是数据库中并发控制的基本单位。
多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。(事物之间不能相互干扰)
3.mysql数据库针对事务的隔离性,有哪些隔离级别?
4个隔离级别,从低到高依次为:不坚定<坚定<可重读<可串行化
Read-uncommitted < Read-committed < repeatable-read < serializable
set session transaction isolation level 设置事务隔离级别
select @@tx_isolation 查询当前事务隔离级别
4.每个隔离级别的作用分别是什么?
read-uncommitted : 最低级别,可能产生脏读,幻读,不可重复读
read-committed : 可避免脏读。
repeatable-read:可避免脏读,不可重复读,无法避免幻读。
serializable:脏读,不可重复读,幻读都可以避免
5.如果数据库的隔离级别比较低,会发生什么问题?
如果隔离级别比较低,
脏读:当一个事务修改了某一数据行的值而未提交时,另一事务读取了此行值。倘若前一事务发生了回退,则后一事务将得到一个无效的值(“脏”数据)。
不可重复读:当一个事务在读取某一数据行时,另一事务同时在修改此数据行。则前一事务在重复读取此行时将得到一个不一致的值。
幻读:在一个事务中读到了别的事务刚刚插入的数据,导致前后读取不一致。
6.数据库的隔离级别是设置的越高越好吗?为什么?
首先事务的独立性增加,可以更有效地防止事务操作之间的冲突,但同时也增加了锁的开销,降低了用户之间访问的并发性,程序的运行效率也随之降低。
比如如果 有很多个 事务 都只是要读取一个静态的数据库,那么就没有必要将两个隔离级别都设置成最高,这样的话,查询的效率就会降低。
数据库连接池(DBCP / C3P0)
连接池产生的背景:
数据库连接是一种重要资源。大部分很重要的数据都存在数据库里,那么在产生连接池之前,我们连接数据库的方式:直连。(获取连接->使用->关闭连接)程序小的话可以采用这种方式,但是如果程序很大,比如大型网站,它可能每分钟或者每秒变化量在100万次,就是说同时访问数据库有100万个用户,这时候如果我们不用连接池的话,我们就需要创建100万个连接这样的话就会对数据库造成很大的压力,如果数据库承受不了的话就崩溃了,服务器也崩溃了,网站就瘫痪了。
即:
①数据库连接是一种重要资源;
②频繁的连接数据库会增加数据库的压力;
③为解决以上问题出现连接池技术。
(池子里保持一定数量的连接,当使用时就从池子中拿一个连接出来,当使用完连接后就把它释放到池子里。当你同时访问数据库人很多的时候,这个时候连接不够用,就需要等待,减少数据库的压力)
常用的开源数据库连接池:
- dbcp
- c3p0
引述取自http://www.cnblogs.com/Qian123/p/5349884.html
spring开发组推荐使用dbcp(c3p0连接池有weblogic连接池同样的问题,就是强行关闭连接或数据库重启后,无法reconnect,告诉连接被重置,这个设置可以解决);
先丢个重点,帮助理解:
这两个连接池的代码无论是使用DBCP还是使用C3P0连接池,在写成工具类后都要对外提供一个 数据源DateSource 和一个 连接Connection。作用是,如果我们在进行数据库操作的时候要是使用DBUtils类的时候,就要使用QueryRunner核心类,而这个类在进行与数据库连接的时候只要一句代码:
QueryRunner qr=newQueryRunner(数据源);这里的数据源,我们可以通过两个连接池获得:C3P0Utils.getDataSource() ,或者:DBCPUtils.getDataSource()获得。如果不使用DBUtils工具类,我们就可以直接通过C3P0Utils.getConnection()或者 DBCPUtils.getConnection()来直接获得连接。
这些可以看我的轻量级框架DBUtils,里面有详细的代码。
引入 设计模式 :
想要使用连接池就必须要获取连接池:
public class MyConnectionPool{ private static LinkedList<Connection> pool; private static final int INIT_COUNT_NUM = 5; //初始化 static{ pool = new LinkedList<Connection>(); //初始化连接数 for(){ Connection connection = MyDBUtil.getConnection(); pool.add(0,connection); } } //从池子里拿 public static Connection getConnFromPool(){ return pool.removeFirst();//你拿了别人不能拿 //removeFirst 会删除 } //还给池子 public static void addToConnPool (Connection conn){ pool.addLast();//从表尾放入 } }
包装设计模式 和 适配器模式 引入:
上述代码的不足之处:功能上:
1.无法扩容(了解Arraylist的扩容机制)
2.空闲的连接回收。
3.自定义初始化size。
设计上:
1.易用性还可以
2.通用性不行(接口的通用性才可以)
JAVAEE为了解决连接池的通用性的问题,提供了一个连接池的标准接口(规范):DataSource 接口
编写连接池需要 实现 javax.sql.DataSource 接口:
javax.sql.DataSource 定义了两个方法需要实现:
getConnection() 和 getConnection(String username,String password);
还有父类一堆方法。。。
放入上述代码
//定义了取连接的API
//但是没有定义 还连接 到池子里的API,这是不是他们没考虑到的问题呢?
//两种情况,怎么还连接?如果用户connection.close()释放了这个连接怎么办?
其实就是直接让用户close(),但是重写了Connection 的 close()方法。
那么该如何实现?
如果写一个类,去继承自Connection接口,然后发现,有几十个API要实现,我不知道怎么做,但是Connection对象知道怎么做。
方法1:(包装设计模式)
写一个构造和函数,然后将参数(connection,pool)传进来我只需要获取Connection接口的close()方法就行了,那么就对每一个API,使用return 他们自己的connection.他们自己的实现
然后重写里面的close()方法:自己包装成pool.addlist(this);
每一个方法我们不知道怎么实现,但是Connection接口它知道怎么实现,所以都让他自己去实现,我们就包装了一个接口,然后只实现了我们自己需要的方法,其他的方法让这个类本身自己去实现。这就是包装设计模式
方法2:适配器的设计模式(Adapter)
优化包装设计模式的实现: 将上面一个类变成 抽象类 :abstrct class adapter,然后删除close()方法, 然后写一个类,继承这个抽象类,就只需要实现没有实现的close()方法。可以让代码变得简洁。//但是只需要重写 getconnection close方法 public class MyConnection2 extends MyConnectionAdapter { public MyConnection2(Connection connection, LinkedList<Connection> pool) { super(connection, pool); } @Override public void close() throws SQLException { pool.add(this); } }
不过如果你能看懂下面这些代码,就可以不用看连接池了:
那么连接池的工具类怎么写的?如下,:
1.声明私有 静态的 (BasicDateSource(dbcp的) 或 ComboPooledDataSource(C3P0的))
2.静态代码块:初始化配置文件(.properties)或者(XML文件)
3.设置3个方法便于代码复用:getConnection()、closeConnection(Connection con)、getDateSource()
代码如下:
C3P0的Utils:
public class C3P0Utils { private static ComboPooledDataSource cpds; static{ //方法1 硬编码 /* cpds = new ComboPooledDataSource(); try { //设置 cpds.setUser("root"); cpds.setPassword("123456"); cpds.setDriverClass("com.mysql.jdbc.Driver"); cpds.setJdbcUrl("jdbc:mysql://localhost:3306/logindemo"); cpds.setMinPoolSize(5); //增加的size cpds.setAcquireIncrement(5); cpds.setMaxPoolSize(20); } catch (PropertyVetoException e) { e.printStackTrace(); }*/ //方法2 通过配置文件 //如果什么都没写,使用的是c3p0-config.xml配置文件里的默认项 cpds = new ComboPooledDataSource("mysql"); } public static Connection getConnection() throws SQLException { return cpds.getConnection(); } public static void releaseConnection(Connection conn) throws SQLException { if (conn!=null){ conn.close(); } } public static ComboPooledDataSource getDateSource(){ return cpds; } }XML文件的内容:
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/logindemo</property>//这个看你数据库的名称 <property name="user">root</property> //数据库账号 <property name="password">123456</property> //数据库密码 <property name="acquireIncrement">5</property> //每次扩容池子的大小 <property name="initialPoolSize">10</property> //初始池子大小 <property name="minPoolSize">5</property> //最小空闲池子保持的连接数 <property name="maxPoolSize">20</property> //连接池最大容量 </default-config> <named-config name="mysql"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/logindemo</property> <property name="user">root</property> <property name="password">123456</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">15</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </named-config> <named-config name="oracle"> //如果有多个数据库的话 <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </named-config> </c3p0-config>
DBCP的Utils:
public class MyDBCPUtil { private static BasicDataSource pool; static{ try { //获取配置文件对象 Properties properties = new Properties(); //获取流 InputStream in = MyDBCPUtil.class.getClassLoader().getResourceAsStream("conf.properties"); //加载配置文件 properties.load(in); //使用工具类 BasicDataSourceFactory factory = new BasicDataSourceFactory(); pool = factory.createDataSource(properties); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } //获取池子 public static BasicDataSource getDataSource(){ return pool; } //获得连接池 public static Connection getConnection() throws SQLException { return pool.getConnection(); } //放回池子 public static void closeConnection(Connection connection) throws SQLException { connection.close(); } }dbcp的配置文件 ( xxx.properties)配置如下:还有一点很重要, 配置文件的位置:src目录下
url=jdbc:mysql://localhost:3306/dbcp username=root password=1234 driverClassName=com.mysql.jdbc.Driver #初始化连接数 initialSize=10 #最大连接数量 maxActive=50 #最大空闲连接 maxIdle=20 #最小空闲连接 minIdle=5 #超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true;characterEncoding=gbk #指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix) defaultReadOnly=false #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE, # READ_UNCOMMITTED, # READ_COMMITTED, # REPEATABLE_READ, # SERIALIZABLE # defaultTransactionIsolation=REPEATABLE_READ
使用DBCP的流程:
1.导包:
2.在项目根目录下创建一个配置文件:
3.创建一个DBUtils类,可以通过这个类得到一个连接池
//使用DBCP连接池作为数据库连接的工具 public class DBCPUtils { private static BasicDataSource pool ; static{ /* pool = new BasicDataSource();*/ //连接池的初始化 4个信息 username password url driver //方法1 直接硬编码 /* pool.setUsername("root"); pool.setPassword("123456"); pool.setUrl("jdbc:mysql://localhost:3306/logindemo"); pool.setDriverClassName("com.mysql.jdbc.Driver"); pool.setInitialSize(5);*/ //方法2 通过配置文件来配置 try { InputStream in = DBCPUtils.class.getClassLoader(). getResourceAsStream("configure.properties"); Properties prop = new Properties(); prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory(); pool = factory.createDataSource(prop); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } //获取连接(从DBCP连接池获取) public static Connection getConnection() throws SQLException { Connection connection = pool.getConnection(); return connection; } //释放连接(放回到池子里),因为这个Connection已经被包装过了,所以可以直接close(); public static void closeConnection(Connection connection){ if (connection!=null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static BasicDataSource getDataSource(){ return pool; } }
也可以创建一个C3P0的类,来获取连接池
public class C3P0Utils { private static ComboPooledDataSource cpds; static{ //方法1 硬编码 /* cpds = new ComboPooledDataSource(); try { //设置 cpds.setUser("root"); cpds.setPassword("123456"); cpds.setDriverClass("com.mysql.jdbc.Driver"); cpds.setJdbcUrl("jdbc:mysql://localhost:3306/logindemo"); cpds.setMinPoolSize(5); //增加的size cpds.setAcquireIncrement(5); cpds.setMaxPoolSize(20); } catch (PropertyVetoException e) { e.printStackTrace(); }*/ //方法2 通过配置文件 //如果什么都没写,使用的是c3p0-config.xml配置文件里的默认项 cpds = new ComboPooledDataSource("mysql"); } public static Connection getConnection() throws SQLException { return cpds.getConnection(); } public static void releaseConnection(Connection conn) throws SQLException { if (conn!=null){ conn.close(); } } public static BasicDataSource getDataSource(){ return cpds; } }
4.开始测试我们写的DBUtils类
public class MyTest { @Test public void testGetConntion(){ try { //得到一个连接池 Connection connection = DBCPUtils.getConnection(); //connection.setAutoCommit(false); System.out.println("conn="+connection); Statement statement = connection.createStatement(); statement.execute("DELETE from tt_user WHERE ID= 3") //connection.commit(); DBCPUtils.closeConnection(connection); } catch (SQLException e) { e.printStackTrace(); } } }
C3P0的方法和DBCP不同的地方主要是:
导包不同:C3P0的包:
Mchange Commons Java-0.2.11
c3p0-0.9.5.2
DBCP的包:
dbcp.jar
pool.jar
logging.jar;
实现类不同:
C3P0的类实现类:
private static ComboPooledDataSource cpds;
opds = new ComboPooledDataSource();
DBCP的实现类:
private static BasicDataSource pool;
BasicDataSourceFactory factory = new BasicDataSourceFactory();
pool = factory.createDataSource(properties);
实现方式不同:(按每种方法最简单的方式来比较)
DBCP加载 配置文件.properties 中的东西注册连接池的驱动。
C3P0读取 .XML 的文件内容来注册驱动
C3P0通过 .XML 文件来注册驱动:
通过配置文件注册的代码如下:
import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.dbutils.QueryRunner; import com.mchange.v2.c3p0.ComboPooledDataSource; public class C3P0Utils { //1、提供私有化的数据源 使用默认设置 private static ComboPooledDataSource datasource=new ComboPooledDataSource(); //使用命名配置 //private static ComboPooledDataSource datasource =new ComboPooledDataSource("itheima"); //2.提供对外的数据源 public static DataSource getDataSource(){ return datasource; } //3.提供对外的链接 public static Connection getConnection() throws SQLException{ return datasource.getConnection(); } }
C3P0也可以 不通过 配置文件.xml 的方法注册驱动。
不使用配置文件的代码:三个步骤:
1、导入核心类:ComboPooledDataSource
2、基本的四项设置,也就是设置驱动,URL,用户名和密码等四项。
3、其他四项。
public class c3p0_test01 { public static void main(String[] args) throws Exception { //导入一个核心类 ComboPooledDataSource dataSource = new ComboPooledDataSource(); //基本四项设置 dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/eeday09"); dataSource.setUser("root"); dataSource.setPassword("123"); //其他四项设置 dataSource.setInitialPoolSize(10); //初始化连接个数 dataSource.setMaxPoolSize(40); //最大链接数 dataSource.setMinPoolSize(5); //设置最小链接数 dataSource.setAcquireIncrement(2); //设置每次增加的连接数 Connection conn=dataSource.getConnection(); //进行数据库连接 System.out.println(conn); } }
BasicDataSourceDataFactory 类
主要根据一个实现了DataSource接口的对象,获取该对象的相关数据源配置参数(通过Reference对象,采用类似指针的方法),然后将new一个BaiscDataSource对象,结合获取的参数,形成一个BasicDataSource对象,并将之返回。