Java JDBC和MySQL

一.JDBC驱动程序

Java为数据库编程设计了一套统一的API,它们在Java中只是接口,具体由数据库厂商实现.因此在编程时使用java中的API接口,再导入具体的JDBC驱动程序即可.

JDBC驱动的类型有4种:

(1). JDBC-ODBC桥 ,它是将JDBC API映射到ODBC API,从而和数据库连接.

(2). 纯java驱动 , 直接和数据库实例交互.这是目前最流行的JDBC驱动.

(3).本地API , 将JDBC API 映射为数据库客户端的API .

(4)网络API , 支持三层结构的JDBC访问方式.主要用于通过Applet访问数据库.

二.JDBC 常用接口和类简介

1. DriverManaget

用于管理驱动程序的服务类.主要用来获取数据库连接对象.

2. Connection

数据库连接对象.一个Connection代表一次会话.主要用来获取执行SQL语句的对象.

Connection还支持事务控制.

3. Statement

用于执行SQL语句的接口,从Connection中获取.

可执行DDL,DCL,DML以及SQL查询,查询结果返回一个结果集.ResultSet

4. PreparedStatement

预编译的Statement对象,预编译含有参数的SQL语句,在执行时,只需要设置参数即可.

因此拥有较高的性能.

5. ResultSet

结果集对象.拥有获取结果的方法.

拥有一个记录指针来标记当前行,通过设置当前行,调用方法即可获取当前行,对于列的值.

注意:结果集的行标和列标都是从1开始的哦.

JDK1.4时结果集默认只能向下依次获取.JDK1.5后默认为可滚动.

在创建Statement 和PrepareStatement传入ResultSet的静态参数可使结果集具有相应的操作,例如,可滚动,可更新.等.

//结果集类型
    public void resultSetType(){
        //只向前滚动
        int a=ResultSet.TYPE_FORWARD_ONLY;
        //自由滚动,底层数据改变无影响
        int b=ResultSet.TYPE_SCROLL_INSENSITIVE;
        //自由滚动,底层数据改变有影响
        int c=ResultSet.TYPE_SCROLL_SENSITIVE;
    }
    //并发类型
    public void resultSetConcurrency(){
        //只读(默认)
        int a=ResultSet.CONCUR_READ_ONLY;
        //可更新
        int b=ResultSet.CONCUR_UPDATABLE;
        /**
         * 设置新的值 updateXXX(index,value)
         * 提交更新 updateRow()
         */
    }

6. CallableStatement

调用存储过程时使用.通过Connection的prepareCall()方法创建实例,并传入调用存储过程的SQL语句.

7. ResultSetMetaData

由ResultSet对象获取.用于查看结果集的元数据,例如列数量,列名称,列类型等.

8. DatanaseMetaData

由Connection.getMetaData()获取,用来查看数据库的元数据.

DatanaseMetaData中的很多方法都需要使用SQL模式串:

%代表任意字符,下划线_代表一个字符,null表示不过滤.

使用DatanaseMetaData可以获取数据库支持的功能,数据库版本等信息.

数据库还提供了系统表视图可以查询,不过系统表是依赖于底层数据库的.

三.JDBC编程基本步骤

1. 加载数据库驱动;

2. 获取数据库连接;

3. 执行SQL语句获取结果集;

4.由结果集取得具体值;

示例,(运行时需要有对于数据库和表,你可以更改):

 
 
  1: public static void main(String[] args) throws ClassNotFoundException, SQLException {
  2:     String driverClass="com.mysql.jdbc.Driver";
  3:     String url="jdbc:mysql://localhost:3306/sakila";
  4:     String user="jdbc_test";
  5:     String password="1234567890";
  6:     String SQL="select * from staff";
  7: //    1. 加载数据库驱动;
  8:     Class.forName(driverClass);
  9: //    2. 获取数据库连接;
 10:     Connection conn=DriverManager.getConnection(url, user, password);
 11: //    3. 获取执行对象
 12:     Statement stat=conn.createStatement();
 13: //    4. 执行SQL语句获取结果集;
 14:     ResultSet re=stat.executeQuery(SQL);
 15: //    5.由结果集取得具体值;
 16:     ResultSetMetaData rsmd=re.getMetaData();
 17:     int m=rsmd.getColumnCount();
 18:     for(int i=1;i<=m;i++){
 19:       System.out.print(rsmd.getColumnLabel(i)+"\t");
 20:     }
 21:     System.out.println();
 22:     while(re.next()){
 23:       for(int i=1;i<=m;i++){
 24:         System.out.print(re.getObject(i)+"\t");
 25:       }
 26:       System.out.println();
 27:     }
 28:   }
 

四.使用Statement和PrepareStatement执行SQL语句

为了方便,我们编写一个数据库接口,通过配置文件连接数据库.

 
 
  1:   statement=connection.createStatement();
  2:   /*//////////////////////////////////////////////////////////////////
  3:   下面是使用Statement操作SQL语句的操作
  4:   /////////////////////////////////////////////////////////////////*/
  5:   /**
  6:    * 更新数据库表,返回受影响的记录条数.
  7:    * @param sql 支持DDL语句,操作返回0,如CREATE
  8:    * 或者是DML语句,返回受影响的条数,如INSERT,UPDATE or DELETE.
  9:    * @return 受影响的记录条数,操作失败返回-1
 10:    * @throws SQLException
 11:    */
 12:   public int update(String sql) throws SQLException{
 13:     if(statement==null)return -1;
 14:     return statement.executeUpdate(sql);
 15:   }
 16:   /**
 17:    * 仅支持查询操作,返回结果集对象
 18:    * @param sql  SQL SELECT 
 19:    * @return 操作失败返回null,操作成功返回ResultSet
 20:    * @throws SQLException
 21:    */
 22:   public ResultSet query(String sql) throws SQLException{
 23:     if(statement==null)return null;
 24:     return statement.executeQuery(sql);
 25:   }
 26:   /**
 27:    * 如果你不知道SQL的语句类型,可以使用此方法.
 28:    * 操作返回一个Map,通过KEY(ResultSet/updateCount)你可以得到操作后的ResultSet和updateCount.
 29:    * @param sql SQL  
 30:    * @return 返回Map,如果操作失败返回null.
 31:    * @throws SQLException
 32:    */
 33:   public Map execute(String sql) throws SQLException{
 34:     Map<String,Object> map=new HashMap<>();
 35:     if(statement==null)return null;
 36:     statement.execute(sql);
 37:     map.put("ResultSet", statement.getResultSet());
 38:     map.put("updateCount", statement.getUpdateCount());
 39:     return map;
 40:   }
--------------------------------------------------------------------------------------------------------------
 
 
  1: /*//////////////////////////////////////////////////////////////////
  2:   下面是与预编译SQL语句有关的操作
  3:   参数类型是 Ref、Blob、Clob、NClob、Struct、java.net.URL、RowId、SQLXML ,Array,基本数据类型,流
  4:   都有对应的set方法,或者直接使用setObject().
  5:   PrapareStatement也有对execute(),executeQuery(),executeUpdate()方法,都不需要参数
  6:   /////////////////////////////////////////////////////////////////*/
  7:   /**
  8:    * 预编译SQL语句 ,设置参数并执行,这个例子将两部合并,实际应用时具体来写.
  9:    * 如果你不知道SQL的语句类型,可以使用此方法.
 10:    * 操作返回一个Map,通过KEY(ResultSet/updateCount)你可以得到操作后的ResultSet和updateCount.
 11:    * @return
 12:    * @throws SQLException
 13:    */
 14:   public Map execute(String sql,Object[] objs) throws SQLException{
 15:     Map<String,Object> map=new HashMap<>();
 16:     preStatement=connection.prepareStatement(sql);
 17:     for(int i=1;i<objs.length;i++){
 18:       preStatement.setObject(i, objs[i]);
 19:     }
 20:     preStatement.execute();
 21:     map.put("ResultSet", statement.getResultSet());
 22:     map.put("updateCount", statement.getUpdateCount());
 23:     return map;
 24:   }

使用PrepareStatement执行大量相同的SQL语句(参数不同)比Statement有更好的性能,而且PrepaerStatement可以阻止SQL注入攻击.

五.使用CallableStatement调用存储过程

执行存储过程的SQL语句格式:

{call 过程名(?,?,...,?)}

 
 
  1: /**
  2:    * 调用存储过程示例
  3:    * @throws SQLException 
  4:    */
  5:   public void callableTest() throws SQLException{
  6:     //存储过程语句
  7:     String sql="{call sum(?,?,?)}";
  8:     //创建实例
  9:     CallableStatement call=connection.prepareCall(sql);
 10:     /**
 11:      * 设置参数,如PrepareStatement一样可以设置多种类型的参数.
 12:      */
 13:     call.setInt(1, 10);
 14:     call.setInt(1, 20);
 15:     /**
 16:      * 注册传回参数,执行后取值
 17:      */
 18:     call.registerOutParameter(2, Types.INTEGER);
 19:     //执行
 20:     call.execute();
 21:     
 22:     System.out.println("sum="+call.getInt(3));
 23:   }

六.处理Blob(Binary Long Object)数据

Blob(Binary Long Object)就是长二进制对象,例如视频,文件,声音等.

通过PrepareStatement的setBinaryStream(index ,stream)传入二进制数据到数据库.

调用ResultSet的getBlob(index)方法获取二进制数据

在MySQL中blob数据最大为64KB,mediumblob可存储16MB内容.

七. 离线RowSet

1.使用RowSet

使用ResultSet获取数据需要保证Connection一直连接.

为了在关闭数据连接的情况下使用数据,可以将ResultSet中的数据封装成javaBean对象,但是这种方式非常繁琐.

java提供了RowSet对象可以封装ResultSet中的结果,以便于在断开数据库时读取数据.

RowSet的子接口有

CachedRowSet, FilteredRowSet, JdbcRowSet, JoinRowSet,  WebRowSet

除了JdbcRowSet外,其他的都可以在离线使用.

以CachedRowSet为例说明:

 
 
  1: public class CacheRowSetDemo {
  2:   public static void main(String[] args) throws SQLException {
  3:     String sql="select * from staff";
  4:     CacheRowSetDemo demo=new CacheRowSetDemo();
  5:     
  6:     CachedRowSet crs=demo.query(sql);
  7:     crs.afterLast();
  8:     while(crs.previous()){
  9:       System.out.println(crs.getObject(1)+" "+crs.getObject(2));
 10:       crs.updateString("first_name", "zhang");
 11:     }
 12:     dao.connect(file);
 13:     Connection conn=dao.getConnection();
 14:     conn.setAutoCommit(false);
 15:     crs.acceptChanges(conn);
 16:     dao.close();
 17:     /**
 18:      * 更新结果不成功,MySQL5.6
 19:      */
 20:   }
 21:   private static String file="connect.properties";
 22:   private static DaoDemo dao=new DaoDemo();
 23:   public CachedRowSet query(String sql) throws SQLException{
 24:     dao.connect(file);
 25:     //获取结果集
 26:     ResultSet rs = dao.query(sql);
 27:     //获取工厂
 28:     RowSetFactory f= RowSetProvider.newFactory();
 29:     //创建实例
 30:     CachedRowSet crs=f.createCachedRowSet();
 31:     //包装结果集
 32:     crs.populate(rs);
 33:     //关闭连接
 34:     dao.close();
 35:     return crs;
 36:   }
 37: }
 

2.分页查询

CacheRowSet会将结果集中的数据全部加载到内存中,如果数据很多的话,将会占用大量的内存.

CacheRowSet提供了分页功能,一次只将部分数据加载到内存中.

API:

populate(ResultSet,startRow),设置从ResultSet的第几条数据开始装填.

setPageSize(int pageSize),设置每一页的数据条数.

previousPage(),在ResultSet可用下,读取上一页.

nextPage(),在ResultSet可用下,读取下一页.

八.事务支持

事务是由一步或多步数据库操作序列组成的逻辑单元.这些操作要么全部执行,要么全部放弃.

事务的特性:

原子性,事务是应用逻辑中的最小分割单位.

一致性,事务执行的结果必须保证数据库一个一致性状态,变为另一个一致性状态.

隔离性,事务的执行互不干扰,并发执行的事务也不能相互影响.

持续性,事务操作后的数据都要保存到数据库.

事务操作的几个动作:

设置事务开始,设置回滚点,回滚,提交.

jdbc的事务支持由Connection提供,默认不支持事务,即自动提交,每一次操作都被反映到数据库.通过关闭自动提交来开启事务.

开启/关闭自动提交: setAutoCommit(boolean)

...中间进行数据库操作.

提交事务: commit()

回滚事务: rollback(),出错时调用

Connection也提供了回滚点设置

setSavepoint()

setSavepoint(name)

调用rollback(name)回滚到指定点.

九批量更新

使用批量更新,可以将多条语句绑定到一个执行对象上,并同时提交.

通过查看DatabaseMetaData.supportsBatchUpdates(),判断数据库是否支持批量更新.

为保证批量更新成功,设置为一个事务

示例:

connection.setAutoCommit(false);

Statement stmt=connection.createStatement()

stmt.addBatch(sql1);

stmt.addBatch(sql2);

stmt.addBatch(sql);

stmt.executeBatch();

conn.commit();

conn.setAutoCommit(true);

十.数据连接池

建立数据库连接是相当耗费资源的,而且一个数据库服务器能够同时建立的连接数是有限的,在大兴的Web应用中,可能同时会有成百上千个访问数据库的请求,如果web应用程序
    为每一个客户请求分配一个数据库连接,将导致性能的急剧下降.为了能够重复利用数据库连接,提高对请求的响应时间和服务器性能,可以采用连接池技术.连接池技术预先建立多个数据库连接对象
    ,然后将连接对象保存到连接池中,当客户请求到来时,从池中选择一个连接对象为客户服务,当请求完成后,客户程序调用close()方法,将连接对象放回池中.
    在普通连接中,客户程序得到的是物理连接,调用连接对象的close() 方法将关闭连接,而采用连接池技术,客户程序得到的是连接池中物理连接的一个句柄,调用连接对象的close()
    的方法,物理连接并未关闭,数据源的实现只是删除了客户程序中的连接对象和池中的连接对象之间的联系.

DBCP数据源

DBCP是Apache软件基金组织的开源产品.Tomcat的连接池就是采用DBCP连接的

使用该连接池需要包:

image

使用示例

 
 
  1: 
  2: /**
  3:  * 
  4:  *DBCP连接池用法示例
  5:  */
  6: public class DBCP_DEMO {
  7: 
  8:   public static void main(String[] args) throws FileNotFoundException, SQLException, IOException {
  9:     DBCP_DEMO demo=new DBCP_DEMO();
 10:     String SQL="select * from staff";
 11:     Statement stat=demo.getConnection(filename).createStatement();
 12: 
 13:     ResultSet re=stat.executeQuery(SQL);
 14: 
 15:     ResultSetMetaData rsmd=re.getMetaData();
 16:     int m=rsmd.getColumnCount();
 17:     for(int i=1;i<=m;i++){
 18:       System.out.print(rsmd.getColumnLabel(i)+"\t");
 19:     }
 20:     System.out.println();
 21:     while(re.next()){
 22:       for(int i=1;i<=m;i++){
 23:         System.out.print(re.getObject(i)+"\t");
 24:       }
 25:       System.out.println();
 26:     }
 27:   }
 28:   static String filename="dbpool.properties";
 29:   
 30:   public Connection getConnection(String filename) throws SQLException, FileNotFoundException, IOException{
 31:     Properties p=new Properties();
 32:     p.load(new FileInputStream(filename));
 33:     BasicDataSource ds=new BasicDataSource();
 34:     ds.setDriverClassName(p.getProperty("driverClass"));
 35:     ds.setUrl(p.getProperty("url"));
 36:     ds.setUsername(p.getProperty("user"));
 37:     ds.setPassword(p.getProperty("password"));
 38:     ds.setInitialSize(Integer.valueOf(p.getProperty("initialSize")));
 39:     ds.setMaxIdle(Integer.valueOf(p.getProperty("maxIdle")));
 40:     ds.setMinIdle(Integer.valueOf(p.getProperty("maxIdle")));
 41:     return ds.getConnection();
 42:   }
 43: }
属性文件
 
 
  1: driverClass=com.mysql.jdbc.Driver
  2: url=jdbc:mysql://localhost:3306/sakila
  3: user=jdbc_test
  4: password=1234567890
  5: 
  6: 
  7: #连接池初始连接数
  8: initialSize=5
  9: #连接池最大连接数
 10: maxIdle=20
 11: #连接池最少空闲连接
 12: minIdle=2
 13: 

猜你喜欢

转载自blog.csdn.net/mydream20130314/article/details/44889895