关于JDBC
Jdbc是java database connectivity的简称,java数据库连接,专门用来通过一段java代码连接数据库的一门技术。
使用方法大致为一下六个步骤:
一、注册驱动
注册驱动有两种方式:
1. Class.forName("com.mysql.jdbc.Driver");
这种方式不会对具体的驱动类产生依赖(即不用import驱动类)。Class类的forName方法中对参数指定的类进行了装载操作。在这里将com.mysql.jdbc.Driver类装载到jvm。众所周知,类装载时,将执行被装载类的静态代码块,而Driver类有一个静态代码块如下:
static{try{
java.sql.DriverManager.registerDriver(newDriver());
}catch(SQLExceptione){
thrownew RuntimeException("can't register driver!");
}
}
所以在装载过程中即完成了driver的注册。这也是使用最多的一种注册驱动方式。
2. DriverManager.registerDriver(newcom.mysql.jdbc.Driver());
这种方式会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖。
具体来说就是:
1. 装载Driver类时注册一次驱动(有关类装载,请参考http://www.yanwushu.com/post/54.html),执行此代码时,又注册一次。
2. 由于实例化了com.mysql.jdbc.Driver.class,导致必须import该类,从而具体驱动产生了依赖。
3. System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");
通过系统的属性设置注册驱动,如果要注册多个驱动,则System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver:com.oracle.jdbc.Driver");
这种驱动注册方式很少使用。
二、获取数据库连接对象(java.sql.Connection)
Connection conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/jtdb","root","root");
三、获取传输器
connection对象提供了两种传输器,Statement和PreparedStatement
两种传输器的区别与关系:
关系:PreparedStatement继承自Statement,都是接口
区别:PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高
四、执行sql语句,接收结果
如果是查询结果则用ResultSet 接收,如果是增删改的话,用int类型接收
如:
Statement st =(Statement) conn.createStatement();
String sql= "select * from at_school where owner ='"+user_id+"'";
ResultSet rs=st.executeQuery(sql);
sql= "UPDATE at_school SET state='1' WHERE id='"+id+"'";int rs2 = st.executeUpdate(sq);
五、遍历结果集(java.sql.ResultSet)
ResultSet对象维护指向结果集中当前行的游标。 术语“结果集”是指包含在ResultSet对象中的行和列数据。
ResultSet rs=st.executeQuery(sql);
ResultSet接口中有几种涉及移动光标的方法,其中常用的包括:public boolean next() throwsSQLException 将光标移动到下一行。 如果结果集中没有更多行,则此方法返回false。public boolean absolute(int row) throwsSQLException 将光标移动到指定的行。public void last() throwsSQLException 将光标移动到最后一行。while(rs.next()){//根据列的索引获取第一列的数据
String id = rs.getString(1);//根据列的索引获取第二列的数据
String username = rs.getString(2);//根据列的索引获取第三列的数据
String password = rs.getString(3);
}
六、释放资源
rs.close(); //释放结果集资源st.close();//释放传输器资源conn.close();//释放连接资源
demo
importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.Statement;importorg.junit.Test;importcom.mysql.jdbc.Driver;public classHelloJDBC {//单元测试方法:@Test + void
@Testpublic voidhello() throwsSQLException{//1,注册驱动com.mysql.jdbc.Driver
DriverManager.registerDriver(newDriver());//2,获取数据库连接java.sql.Connection
Connection conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/jtdb","root","root");//3,获取传输器java.sql.Statement
Statement st =conn.createStatement();//4,执行SQL。java.sql.ResultSet
String sql ="select * from user";
ResultSet rs=st.executeQuery(sql);//5,遍历结果集
while(rs.next()){//根据列的索引获取第一列的数据
String i = rs.getString(1);//根据列的索引获取第二列的数据
String username = rs.getString(2);//根据列的索引获取第三列的数据
String password = rs.getString(3);
System.out.println(id+username+password);
}//6,释放资源
rs.close();//释放结果集资源
st.close();//释放传输器资源
conn.close();//释放连接资源
}
}
JDBC工具类
importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.Statement;importjava.util.ResourceBundle;/*** 这个类用来提供jdcb的工具类
* 构造方法私有化
* 该类提供一个getConnetion()方法,提供connection对象
* 提供close()方法,释放资源
*@authorwjx
**/
public classJDBCUtils {
//私有化构造函数
privateJDBCUtils(){
}//加载配置文件
static ResourceBundle rb = null;//只加载一次属性文件
static{
rb= ResourceBundle.getBundle("jdbc");
}//提供一个数据库连接对象
public static Connection getConn() throwsClassNotFoundException {try{//注册驱动
Class.forName(rb.getString("driverClass"));//数据库名,所在地址和端口号
String url = rb.getString("jdbcUrl");//用户名
String user = rb.getString("user");//密码
String password = rb.getString("password");//获取数据库连接对象
Connection conn =DriverManager.getConnection(url, user, password);returnconn;
}catch(SQLException e) {//TODO Auto-generated catch block
e.printStackTrace();
}return null;
}public static voidclose(Connection conn,Statement st,ResultSet rs){//从结果集开始释放,依次是:ResultSet-->Statement-->Connection//非空判断,防止空指针异常
if(rs!=null){try{
rs.close();
}catch(SQLException e) {
e.printStackTrace();
}finally{
rs=null;//手动置空
}
}//释放传输器资源//非空判断,防止空指针异常
if(st!=null){try{
st.close();
}catch(SQLException e) {
e.printStackTrace();
}finally{
st=null;//手动置空
}
}//释放数据库连接资源//非空判断,防止空指针异常
if(conn!=null){try{
conn.close();
}catch(SQLException e) {
e.printStackTrace();
}finally{
conn=null;//手动置空
}
}
}public static voidclose(Connection conn,Statement st){//释放传输器资源//非空判断,防止空指针异常
if(st!=null){try{
st.close();
}catch(SQLException e) {
e.printStackTrace();
}finally{
st=null;//手动置空
}
}//释放数据库连接资源//非空判断,防止空指针异常
if(conn!=null){try{
conn.close();
}catch(SQLException e) {
e.printStackTrace();
}finally{
conn=null;//手动置空
}
}
}
}
配置文件(jdbc.properties)
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///test
user=root
password=123456
关于批处理
当我们需要向数据库中插入大量数据时,批处理会将这些数据打成多个批次addBatch(),统一发送给数据库服务器执行executeBatch(),减少与数据库的交互次数提高程序的执行效率。
如果有一个功能需求,是向数据库中插入50000条数据,这种需求如果不用批处理的话,会非常非常的耗时和资源。
不用批处理的情况下
用statement传输器
demo:
//不用批处理,Statement传输器下 用时: 1456秒
@Testpublic voidtest01(){try{//获取数据库连接
Connection conn =JDBCUtill.getConn();//创建传输器(Statement)
Statement st =conn.createStatement();//将开始插入的时间记录下来
Long begin =System.currentTimeMillis();//执行sql语句
for(int i = 1;i<50000;i++){
st.executeUpdate("insert into user (id,username,password) values ("+i+",'user "+i+"','paw "+i+"')");
}
System.out.println("用时: "+(System.currentTimeMillis()-begin)/1000+"秒");
st.close();
conn.close();
}catch(Exception e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}
用PreparedStatement传输器
demo:
//不用批处理,PreparedStatement传输器下 用时: 1391秒
@Testpublic voidtest02(){try{//获取数据库连接
Connection conn =JDBCUtill.getConn();//创建传输器(PreparedStatement)
String sql ="insert into user (id,username,password) values (?,?,?)";
PreparedStatement pst=conn.prepareStatement(sql);//将开始插入的时间记录下来
Long begin =System.currentTimeMillis();//执行sql语句
for(int i = 1;i<50000;i++){
pst.setInt(1, i);
pst.setString(2, "username "+i);
pst.setString(3, "psw "+i);
pst.executeUpdate();
}
System.out.println("用时: "+(System.currentTimeMillis()-begin)/1000+"秒");
pst.close();
conn.close();
}catch(Exception e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}
用批处理
JDBC实现批处理有两种方式:statement和preparedstatement
statement实现批处理:
demo:
//向test数据库中的user表中插入50000条数据//用批处理,Statement传输器下 用时: 12秒
@Testpublic void test05() throwsSQLException{try{
Connection conn=JDBCUtill.getConn();//将自动提交设为false
conn.setAutoCommit(false);//创建一个 PreparedStatement传输器
Statement st =conn.createStatement();//将开始插入的时间记录下来
Long begin =System.currentTimeMillis();//设置需要插入语句的总数和每一批提交的数量
int count = 50000;int batchsize = 5000;for(int i = 9 ; i < count;i++){//添加一条sql语句
String sql = "insert into user (id,username,password) values ('"+i+"','"+i+"','"+i+"')";
st.addBatch(sql);//每5000条为一批
if(i%batchsize==0){//执行一次批处理
st.executeBatch();//提交
conn.commit();
}
}//如果还有剩余,则再执行一次批处理
if ((count+1) % batchsize != 0) {
st.executeBatch();
conn.commit();
}
System.out.println("用时: "+(System.currentTimeMillis()-begin)/1000+"秒");
st.close();
conn.close();
}catch(ClassNotFoundException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}
采用Statement.addBatch(sql)方式实现批处理:
优点:可以向数据库发送多条不同的SQL语句。
缺点:SQL语句没有预编译。
当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句。
preparedstatement实现批处理:
demo:
//用批处理,PreparedStatement传输器下(不加rewriteBatchedStatements=true 参数) 用时: 12秒
@Testpublic void test03() throwsSQLException {try{
Connection conn=JDBCUtill.getConn();//将自动提交设为false
conn.setAutoCommit(false);
String sql= "insert into user (id,username,password) values (?,?,?)";//创建一个 PreparedStatement传输器
PreparedStatement pst =conn.prepareStatement(sql);//将开始插入的时间记录下来
Long begin =System.currentTimeMillis();//设置需要插入语句的总数和每一批提交的数量
int count = 50000;int batchsize = 5000;for(int i = 1 ; i < count;i++){
pst.setLong(1, i);
pst.setString(2, "batch test"+i);
pst.setString(3, "test psw"+i);//添加一条sql语句
pst.addBatch();//每5000条为一批
if(i%batchsize==0){//执行一次批处理
pst.executeBatch();//提交
conn.commit();
}
}//如果还有剩余,则再执行一次批处理
if ((count+1) % batchsize != 0) {
pst.executeBatch();
conn.commit();
}
System.out.println("用时: "+(System.currentTimeMillis()-begin)/1000+"秒");
pst.close();
conn.close();
}catch(ClassNotFoundException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}
采用PreparedStatement.addBatch()实现批处理
优点:发送的是预编译后的SQL语句,执行效率高。
缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
如果数据库连接对象没有设置rewriteBatchedStatements=true参数的话,效率会低一些
demo:
//用批处理,PreparedStatement传输器下(加上rewriteBatchedStatements=true 参数) 用时: 2秒
@Testpublic void test04() throwsSQLException {try{
Connection conn=JDBCUtill.getConn_batch();//将自动提交设为false
conn.setAutoCommit(false);
String sql= "insert into user (id,username,password) values (?,?,?)";//创建一个 PreparedStatement传输器
PreparedStatement pst =conn.prepareStatement(sql);//将开始插入的时间记录下来
Long begin =System.currentTimeMillis();//设置需要插入语句的总数和每一批提交的数量
int count = 50000;int batchsize = 5000;for(int i = 1 ; i < count;i++){
pst.setLong(1, i);
pst.setString(2, "batch test"+i);
pst.setString(3, "test psw"+i);//添加一条sql语句
pst.addBatch();//每5000条为一批
if(i%batchsize==0){//执行一次批处理
pst.executeBatch();//提交
conn.commit();
}
}//如果还有剩余,则再执行一次批处理
if ((count+1) % batchsize != 0) {
pst.executeBatch();
conn.commit();
}
System.out.println("用时: "+(System.currentTimeMillis()-begin)/1000+"秒");
pst.close();
conn.close();
}catch(ClassNotFoundException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}
关于数据库连接池
什么是数据库连接池?
用来存放数据库连接的一个容器,这个容器在整个程序中共享。提高程序的执行效率降低与数据库的交互次数。
主要有两个优点:实现数据库连接对象的复用,降低与数据库的交互次数。
自定义一个简易的数据库连接池
主要有以下几个步骤:
1、创建pool类,继承自DataSource
2、创建容器,用来存放数据库连接对象(LinkedList)
3、在静态代码块中完成容器初始化
4、提供getConnection方法,对外提供获取数据库连接对象(Connection)的方法
5、提供returnConnection方法,用来把用了的数据库连接对象放回池中。
demo:
importjava.io.PrintWriter;importjava.sql.Connection;importjava.sql.SQLException;importjava.sql.SQLFeatureNotSupportedException;importjava.util.LinkedList;importjava.util.List;importjava.util.logging.Logger;importjdbc.JDBCUtill;
importjavax.sql.DataSource;
/*** 自定义一个数据库连接池
* 步骤:
* 1、创建pool类,继承自DataSource
* 2、创建容器,用来存放数据库连接对象(LinkedList)
* 3、在静态代码块中完成容器初始化
* 4、提供getConnection方法,对外提供获取数据库连接对象(Connection)的方法
* 5、提供returnConnection方法,用来把用了的数据库连接对象放回池中。
*@authorwjx
**/public class Pool implementsDataSource{
static int total = 5;//创建容器,用来存放数据库连接对象(LinkedList)
static List pool = new LinkedList();//在静态代码块中完成容器初始化
static{for(int i = 0; i < total;i++){try{
Connection conn=JDBCUtill.getConn();
pool.add(conn);
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
}
}//提供getConnection方法,对外提供获取数据库连接对象(Connection)的方法
@Overridepublic Connection getConnection() throwsSQLException {
Connection conn= null;//remove(Object o)//从列表中删除指定元素的第一个出现(如果存在)。
conn = pool.remove(0);
System.out.println("已从池中取走一个conn对象,还剩"+pool.size()+"个conn对象");returnconn;
}//提供returnConnection方法,用来把用了的数据库连接对象放回池中。
public void returnConnection(Connection conn) throwsSQLException{if(conn!=null && !conn.isClosed()){
pool.add(conn);
System.out.println("已成功还回对象");
}else{
System.out.println("这是一个空对象!");
}
}
@Overridepublic PrintWriter getLogWriter() throwsSQLException {//TODO Auto-generated method stub
return null;
}
@Overridepublic void setLogWriter(PrintWriter out) throwsSQLException {//TODO Auto-generated method stub
}
@Overridepublic void setLoginTimeout(int seconds) throwsSQLException {//TODO Auto-generated method stub
}
@Overridepublic int getLoginTimeout() throwsSQLException {//TODO Auto-generated method stub
return 0;
}
@Overridepublic Logger getParentLogger() throwsSQLFeatureNotSupportedException {//TODO Auto-generated method stub
return null;
}
@Overridepublic T unwrap(Class iface) throwsSQLException {//TODO Auto-generated method stub
return null;
}
@Overridepublic boolean isWrapperFor(Class> iface) throwsSQLException {//TODO Auto-generated method stub
return false;
}
@Overridepublic Connection getConnection(String username, String password) throwsSQLException {//TODO Auto-generated method stub
return null;
}
}
测试一下:
importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;
importorg.junit.Test;
/*** 这个类用来测试自定义的连接池
*@authorwjx
**/
public classtest {
@Testpublic voidtest_pool(){//声明数据库连接对象
Connection conn = null;//声明传输器对象
PreparedStatement pst = null;//声明结果集对象
ResultSet rs = null;//创建连接池对象
Pool pl = newPool();try{//获取连接器
conn =pl.getConnection();//创建传输器
String sql = "select * from user";
pst=conn.prepareStatement(sql);//获取结果集
rs =pst.executeQuery();//遍历结果集
while(rs.next()){
System.out.println("id="+rs.getInt(1)+" userName="+rs.getString(2)+" password=d"+rs.getString(3));
}
}catch(SQLException e) {//TODO Auto-generated catch block
e.printStackTrace();
}finally{//释放资源
try{
rs.close();
pst.close();
pl.returnConnection(conn);
}catch(SQLException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
结果为:
已从池中取走一个conn对象,还剩4个conn对象
id=1 userName=u1 password=dp1
id=2 userName=u2 password=dp2
id=3 userName=u3 password=dp3
已成功还回对象