Java入门指南之数据库操作

Java入门指南之数据库操作

概念

  • JDBC
    JDBC: Java DataBase Connectivity, 是Java语言中用于数据库操作的一个 API,JDBC是典型使用门面模式的用例。使用JDBC可以重用代码来访问各种类型的数据库,而不用针对每种数据库都写一个数据库操作类。当出现新型的数据库时,只要该数据库的生产商提供相应的JDBC驱动程序,就能使用原有代码对该新型数据库进行操作。

数据库基本操作步骤

  1. 取得数据库连接
想要对数据库进行操作,必须先建立和数据库的连接。而建立一个数据库连接总是需要两个步骤:
  1. 1.载入驱动程序
Class.forName("驱动程序名称");
例如,使用Sun公司提供的JDBC桥接驱动程序,该驱动程序的名称为“sun.jdbc.odbc.JdbcOdbcDriver”,就该使用下面语句来加载驱动程序:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
  1. 2.建立连接
驱动程序管理器(DriverManager)负责管理驱动程序,并使用适当的驱动程序建立和数据库的连接,可以使用下面语句建立和一个数据库的连接:
    Connect con = DriverManager.getConnection(url,"用户名","用户密码");
其中,url表示数据库统一资源定位的一个字符串,其语法常为:

jdbc:subprotorol:subname       //JDBC:子协议:副名

如:
String url="jdbc:microsoft:sqlserver://localhost"; 
   下面是一个拓展,大家可以看看。
 如果驱动程序和URL都被“硬”编程到程序应用中,一旦访问的数据库类型改变,那么就需要修改代码并重新编译运行,这不仅仅不方便操作,还削弱了JDBC数据库编程独立于数据库类型的优点。那么,该怎么办呢?  我们可以使用**配置文件**的方式在解决这个问题:将驱动程序和数据库URL等信息保存在一个配置文件里,应用程序进行数据库连接时,从配置文件中读取信息,这样就能提高应用程序的灵活性和可扩展性。  如下,我们可以在应用程序所在目录中创建这样一个配置文件db.cfg

dbDriver = com.microsoft.jdbc.sqlserver.SQLServerDriver
dbIP = 1227.0.0.1
dbPort = 1433
dbName = admin
dbPassWord = pwd
defaultDbName = dataBaseName

 Java语言中提供了一个类**java.util.Properties**,该类提供了load()方法,可以从输入流中读取属性值,下面的代码实现的是从配置文件db.cfg中读入配置信息放到对象prop中:
Properties prop = new Properties();
prop.load(new FileInputStream("db.cfg"));
 要注意的是,从配置文件中读取的信息是以**键值对**的形式存放在prop对象中的,例如,要获取配置文件中dbDriver的属性值,可以使用**getProperty()方法**:
String driver = porp.getProperty("dbDriver");

 在实际应用中,我们可以提供一个界面,让用户在该界面中设置数据库参数,比如数据库驱动名称、数据库URL等,然后把这些参数信息保存在一个配置文件中,操作数据库时需要的代码参数就从这个配置文件中读取。


2.执行SQL语句

 SQL语句用来表明要对数据库进行的操作。
 下面介绍两种向数据库发送SQL语句的方法。
 一种是使用连接对象(Connection)中的creatStatement()方法创建一个Statement对象,然后通过Statement对象对SQL语句进行编译执行。
 另一种是通过数据库连接对象(Connection)创建PrepareStatement类型的对象,然后通过PrepareStatement对象向DBMS发送SQL语句进行预编译,再执行。


2. 1. 使用Statement对象

 我们可以使用下面语句来获得一个Statement对象:

Statement stmt = con.createStatement();
 Statement类提供了几种不同的执行的SQL语句的方法,主要有:
  • executeUpdate(SQL): 这个方法用来执行那些会修改数据库的SQL语句,例如insert,delete,updare以及create等命令。假设用stmt对象向数据库bookDateBase的bookInfo表中插入一条记录,可以采用下面语句:
stmt.executeUpdate("insert into bookInfo values('ISBN-B0002','Java DataBase Operation',127.0)");
  • executeQuery(SQL):这个方法用于对数据库的查询操作,该方法返回一个ResultSet类型的对象,该对象包含了所有查询结果,如:
ResultSet rs = stmt.executeQuery("select * from bookInfo");

 如果要访问结果集中的一条记录,需要定位到该记录。ResultSet类提供了next()方法用于依次定位结果集中的每条记录;还提供了getXXX()方法用于访问当前记录中字段的值,使用getXXX()方法必须在方法参数中指明所访问字段的列索引或是列名。如:

String name = rs.getString(2);  //列索引

String name = rs.getString("bookName"); //列名

注意,与数组下标索引不同,上面的列索引是从1开始的。

 另外,如果执行的是以下查询:

ResultSet rs = stmt.executeQuery("select bookName from bookInfo");  

 要用列索引的方式查询“bookName”字段,就应该使用:

String name = rs.getString(1);  //索引不再是2  

 因为字段的列索引指的是在ResultSet返回集中所在列的位置,而不是在数据库中列中的位置,上面语句的返回集中只有“bookName”这一列,所以列索引为1。

 如果要处理返回的记录集(ResultSet对象),可以使用下面的代码段:

     while(rs.next()){
        //使用列名方式
        System.out.println("列名方式-字段内容为:"+rs.getString("bookName"));
        //使用列索引方式:
        System.out.println("列索引方式-字段内容为:"+rs.getString(1));  
     }   
  • executeBatch(SQL): 这个方法用来批量执行SQL语句。需要注意的是,这些要批量执行的语句是更新类型(既是insert、delete、delete以及create等命令)的,并且其中不能包含查询类型(selsect)的SQL语句。
    下面代码演示了executeBatch()方法的使用:
stmt.addBatch(updateSql_1);
stmt.addBatch(updateSql_2);
stmt.addBatch(updateSql_3);
int [] results = stmt.executeBatch();

 该方法返回的是一个整型数组,其中依次存放了每条SQL语句对数据库中产生影响的行序号。


2. 2. 使用PrepareStatement对象

 使用这个方法操作数据库时,先要写一条操作数据库的SQL语句,并将其作为参数传入创建方法中,这条作为参数的SQL语句中允许使用参数占位符“ ?”。使用参数占位符的好处是:每次运行这条SQL语句时,可以通过赋给参数占位符不同的参数值,从而完成不同的功能。获取一个PrepareStatement对象可以用下列语句:

PreparedStatement pstmt = con.prepareStatement(sql_statement);

 其中,sql_statement 这个参数是要执行的SQL语句,它会立即被发送到DBMS(数据库管理系统)进行预编译。

 下面我们假设要向表bookInfo中插入一条新记录(’ISBN-G006’,’Java Data Structure’,196.8),来写一段代码演示PreparedStatement的使用:

sql = " insert into bookInfo values(?,?,?) ";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1,"ISBN-G006"); //对应values的第一个占位符“?”
pstmt.setString(2,"Java Data Structure");//对应values的第二个占位符“?”
pstmt.setString(3,196.8);//对应values的第三个占位符“?”
pstmt.executeUpdate();

 在实际情况中,往往一条记录有很多个字段,那么使用setXXX()的方式来完成的话,代码量就会比较大,效率不高。这种情况下,我们可以使用setObject()方法结合循环语句来完成。同样,以上面假设的情景为例,我们可以这样写:

sql = “ insert into bookInfo values(?,?,?) ”;
PreparedStatement pstmt = con.prepareStatement(sql);
Object [] record = {"ISBN-G006","Java Data Structure",new Float(196.8)};
for(int i = 1;i<record.length;i++){
    pstmt.setObject(i,record[i-1]);
}
pstmt.executeUpdate();

与Statement相比,PrepareStatement有下列优点:

  • PreparedStatement是预编译方式执行的,其中的SQL语句参数会先被传到DBMS预编译,所以当其执行时,只需DBMS运行SQL语句,效率会比较高;
  • PreparedStatement中的SQL语句是可以带参数的,它的优势在于可以减少拼接SQL语时句出错的几率,容易阅读,方便维护。同时,还能减少SQL注入攻击的可能性,增加SQL的安全性;
  • 当批量处理SQL语句或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时不用再次编译,效率就会比较快。


    3.关闭连接,释放资源

 数据库操作完成后,需要依次将ResultSet、Statement或PreparedStatement、Connection对象关闭,以释放所占用的资源,可以使用这些代码来执行:

 if(rs != null){   // 关闭记录集   
    try{   
        rs.close() ;   
    }catch(SQLException e){   
        e.printStackTrace() ;   
    }   
        }  

if(stmt != null){   // 关闭声明   
    try{   
        stmt.close() ;   
    }catch(SQLException e){   
        e.printStackTrace() ;   
    }   
        } 

if(con != null){  // 关闭连接对象   
    try{   
        con.close() ;   
    }catch(SQLException e){   
            e.printStackTrace() ;   
    }   
        }  

数据库事务

 什么是数据库事务?
 通俗点来说,我们要对数据库进行几个操作(增删改查等)的组合,以完成一个完整的事件。比如,数据库的记录A、记录B分别代表两个银行账户,其中都有资金的这一字段。现在要将账户A的部分资金转给账户B,那么就存在A账户减去部分资金,B账户加上部分资金这两个操作的组合,以完成转账事件。如果任意一步操作出错,这一系列的操作过程就必须重新来过。比如A的资金减少了,但是B的增加资金的操作出错而没有得到转账资金,那么这整个转账事件就必须就回到初始状态,重新执行。一组数据库操作步骤要么全部发生要么一步也不执行。这整个执行过程称之为一个事务。
 当所有的步骤被完整地执行,我们称该事务被提交。如果由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。

 数据库事务有四大特性(简称ACID原则):

  • 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么均不执行。
  • 一致性(Consistency):指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
     拿上面转账情景来说,假设账户A和账户B两者的资金加起来一共是10000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是10000,这就是事务的一致性。
  • 隔离性(Isolation):事务的执行不受其他事务的干扰,多个并发事务之间要相互隔离,事务执行的中间结果对其他事务必须是不可见的。
  • 持久性(Durability):持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。持久性通过数据库备份和恢复来保证。

 JDBC 事务是用 Connection 对象控制的。JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交和手动提交。 java.sql.Connection 提供了以下控制事务的方法:
  public void setAutoCommit(boolean)
  public boolean getAutoCommit()
  public void commit()
  public void rollback()
 JDBC 事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。

 对于上面的四个方法,getAutoCommit() 是用来获取是否是自动提交模式的布尔值,若是则返回true。
 根据实际情况说一下其他三个方法的应用。
 默认情况下,创建的连接是处于自动提交(AutoCommit)模式的,每条SQL语句执行后,会立即向DBMS提交执行结果,也就是说每条SQL语句都是一个事务。为了完成若干条语句完成的事务,要在执行第一条SQL语句前关闭自动提交模式:

con.setAutoCommit(false);

 关闭自动提交模式后,所执行的SQL语句不会直接提交给DBMS,直到调用如下语句进行手动提交:

con.commit();

 为实现事务步骤出错后能恢复至原始状态。我们可以把一个事务放在一个try块中,当执行过程有SQL语句出现错误时,我们可以在catch块中将其捕获,在catch块中调用rollBack()方法进行事务回滚,恢复到事务开始时的状态。
 由于事务的回滚仍可能出现异常,因此同样需要使用try-catch块来捕获回滚异常。
 下面是执行一个数据库事务的代码:

try{
    con.setAutoCommit(false);   //设置为非自动提交模式
    PreparedStatement pstmt_1 = con.prepareStatement(sql_1);
    PreparedStatement pstmt_2 = con.prepareStatement(sql_2);
    pstmt_1.executeUpdate();
    pstmt_2.executeUpdate();
    con.commit();               //手动提交
    con.setAutoCommit(true);    //恢复自动提交模式
    pstmt_1.close();
    pstmt_2.close();
    }catch(SQLException e){
    e.printStackTrace();
    if(con != null){
        try{
            con.rollback();
            con.setAutoCommit(true);
        }catch(SQLException ex){
            ex.printStackTrace();
        }
    }       //End if
}

数据库连接池

 最后来说一说数据库连接池(Connection Pool),首先说说连接池的概念。
 在实际应用中,可能会频繁地操作数据库,那么就需要频繁地与数据库建立连接,然后执行SQL语句,最后再关闭资源。我们知道,数据库的连接期间系统要分配内存资源,而且,数据库连接是一个占用时间比较多的操作。一个网站可能同时有成百上千人在线,如果这些用户需要频繁地访问数据库,那么在没有使用连接池的情况下,系统可能会不停地创建连接,分配系统资源,连接数据库的操作过多时,就可能出现系统崩溃的情况。
 这时,数据库连接池技术就能很好地解决这个问题。我们建立一个用来存放数据库连接的缓冲池。预先在这个缓冲池中放入一定数量的连接,当我们需要连接数据库时,并不需要建立一个新的连接,而是从这个缓冲池里取出一个数据库连接,使用完毕后再放回缓冲池而无需关闭连接。
 数据库连接池通过建立一个数据库连接池以及一套连接使用、分配、管理方法,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。

 关于连接池的最小连接数和最大连接数:

  • 最小连接数:即数据库连接池中一直保持的连接数量,如上文中预先放入缓冲池(即连接池)的连接数量,无论这些数据库连接是否被使用,连接池都将一直保证至少存放着这么多的连接数量。
  • 最大连接数:最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。避免系统一时间产生大量连接,限制了系统分配的最大资源。
     另外,还可以通过设置每个连接的最大使用次数、最大空闲时间等参数来管理连接池的使用情况。

 关于数据库连接池的实现代码,我在无意中看到一位大神的实现思路,过程比较精彩,大家可以移步: 数据库连接池的实现

以上是个人学习过程中的关于Java数据库操作所作的笔记,参考了部分资料,希望对学习Java数据库的新手能有所帮助。如果大家发现文章中存在不当或错误之处,请大家批评指正。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值