JavaSE(四)数据库

JDBC

  java数据库连接,是java语言访问数据库的途径,对于使用者JDBC是一组API,对于JDBC驱动的实现者,JDBC是一组SPI。

JDBC驱动

  sun公司提供了数据库操作的统一接口(JDBC API),但不同的数据库有自己的访问协议,于是需要各个公司把针对自己数据库的具体操作封装成类,该类需要实现java.sql.Driver接口,这个类就是JDBC驱动,这就像linux系统调用有统一的API,根据不同的硬件,只要驱动实现相应的接口就可以对硬件进行操作。

JDBC访问数据库步骤

  1. 加载数据库驱动:Class.forName(“驱动类全名如com.mysql.jdbc.Driver”);驱动类的静态块里面会new一个驱动类实例并注册进DriverManager,当然也可以直接注册驱动DriverManager.register(new com.mysql.jdbc.Driver()),从JDBC4.0(jdk1.6)开始支持自动加载驱动,不需要这一步了,他会在DriverManager的静态块中读取META-INF/services/java.sql.Driver文件中定义的驱动名,并通过反射创建一个驱动实例注册到DriverManager,JDBC4.0要求提供的驱动包里面包含文件META-INF/services/java.sql.Driver文件,文件里有注册的驱动类全名,一个驱动管理器里可以注册多个驱动。
  2. 获取数据库连接:Connection connection = DriverManager.getConnection(“url”, “user”, “password”);会遍历所有注册的驱动,并找到和url中子协议匹配的驱动,并用该驱动的connect方法创建一个连接返回。
  3. 数据库操作:通过Connection创建SQL语句并执行。
  4. 关闭连接:connection.close()。

数据库操作

  步骤:创建语句->执行语句
  创建并执行普通语句
    connection.createStatement(); // 创建普通语句Statement。
    statement.executeUpdate(“sql语句”); // 执行更新操作。
    statement.executeQuery(“sql语句”); // 执行查询操作。
  创建并执行预备语句
    connection.preparedStatement(“sql预备语句”); // 创建预备语句preparedStatement,sql预备语句可以用?占位,但这个占位的?应该是某个类型的变量位置,PreparedStatement继承自Statement。
    preparedStatement.setInt(1, 3); // 将第1个?用整形3代替,如果多次使用同一预备语句而没调用set,那么对应的参数(这里是第1个)是没有变的。
    preparedStatement.setString(2, “wanglang”); // 将第2个参数用字符串wanglang代替。
    preparedStatement.executeUpdate(); // 执行更新操作。
    preparedStatement.executeQuery(); // 执行查询操作。  
  创建并执行存储过程语句
    connection.prepareCall(“call 过程名(?,?)”); // 创建存储过程语句callableStatement,可以没有参数,CallableStatement继承自PreparedStatement。
    callableStatement.setString(1, “wl”); // 设置每一个输入参数的值,多个参数多次调用。
    callableStatement.registerOutParameter(2, java.sql.Types.INTEGER); // 注册输出参数,如第二个参数既是输入又是输出参数,须设置并注册。
    callableStatement.execute(); // 当然如果该调用返回的只是一个结果集或更新计数也可以用executeQuery或executeUpdate。
    callableStatement.getXxx(2); // 返回注册的指定参数,返回类型和注册的数据库类型对应,调用存储过程可能为了分析输出参数或返回。
  凡是非查询操作的sql语句都是通过executeUpdate方法执行,返回sql语句影响的行数,对于一些DDL和插入语句等不影响行数的操作返回0
  查询操作(包括统计查询等)的sql语句都是通过executeQuery方法执行,返回一个结果集ResultSet,结果集默认顺序是不确定的,除非语句中用了order by子句指定了顺序,要想得到查询结果,需要遍历结果集,而且结果集是不能随机访问的。
  遍历结果集的方式 while (rs.next()) {rs.getInt(可以是列字段名或列别名字符串,也可以是查询结果的第几列索引); rs.getString(…); rs.getDouble(…)等},我们可以把一个具有n条记录的结果集看成是一个n+2元素的列表,中间n个刚好存放数据,列表的游标最开始指向第一个,调用next游标往下移动一个,如果到了最后一个就返回false,否则返回true,而rs.getXxx返回的一定是游标指向的当前记录的某个字段,对于不可滚动的结果集,游标是不能随机手动移动的,只能通过next让游标指向下一个。findCloumn(“列或别名”)返回该列对应的列索引,通过列索引获取结果集的值效率会高些。
  一个connection可以创建多个statement,一个statement可以进行多次查询,但一个statement只能有一个打开的结果集,可以执行完一次查询然后分析结果集,再执行下一次查询然后分析结果集,同一个语句获得新的结果集之前会关闭上一个结果集,如果需要同时分析多个查询的结果集就需要创建多个statement了,使用DatabaseMetaData的getMaxStatements方法可以获取JDBC驱动支持的同时活动的statement个数,比如SQL server只支持一个活动的statement,所以建议一个connect只创建一个statement。connection、statement和resultSet用完后都需要关闭,关闭connection会关闭它创建的所有statement,关闭statement会关闭其打开的结果集,可以用带资源的try块获取连接。
  大对象Blob和Clob的操作,读时用getBlob或getClob获取大对象,然后通过大对象的getBinaryStream()或getCharacterStream()获取字节流inputString或字符流reader,有了inputString或reader对象,一切就是流操作了;简便的方法就是直接通过结果集的getBinaryStream()或getCharacterStream()方法获取字节流inputString或字符流reader。写大对象先要构造出大对象,connection.createBlob或connection.createClob返回Blob和Clob,然后通过blob.setBinaryStream(0)和clob.setCharacterStream(0)获得一个OutputString或Writer,再往里面添加数据,最后通过setBlob或setClob往预备语句里面添加大对象;但简单的方法就是setBlob或sheClob时调用直接放入输入流的方法而无须构造大对象。
  自动生成键的获取,在插入数据时使用statement/preperedstatement.executeUpdate(“插入语句”, Statement.RETURN_GENERATED_KEYS/Statement.NO_GENERATED_KEYS默认);然后通过statement/preperedstatement.getGeneratedKeys()获得一个结果集,该结果集就有自动生成的键。
  statement.execute(“sql语句”);得到多个结果集和更新计数,如果该语句的第一个结果为结果集就返回true,否则返回false。在使用时可以调用该语句,如果返回的是true,便通过statement.getResultSet获得结果集,处理了再调用statement.getMoreResults();如果返回true则是结果集,同样地处理,如果返回false还需要调用statement.getUpdateCount,如果返回-1那么表 示下一个也不是更新计数,这时才表示把结果集和更新计数全部做完了。一般用在编译时无法确定sql语句返回的是结果集或更新计数的情况下,或在执行存储过程时使用它得到多个结果集或更新计数。对于可以用SQL语句解决的问题就不要用java代码,这样效率会高很多。
  无论是set设置预备语句参数还是从结果集中返回列的数据,都是从1开始不是像数组从0开始

可滚动的结果集和可更新的结果集

  可更新的结果集和可滚动的结果集并不是所有的驱动都支持,所以建议程序中不使用可滚动和可更新的结果集
  可滚动的结果集就是可以随机指定游标的位置,rs.first()游标跑到第一个记录、rs.last()、rs.beforeFirst()、rs.afterLast()、rs.isFirst()、rs.isLast()、rs.isBeforeFirst()、rs.isAfterLast()、re.previous()、rs.next()、rs.absolute(n)移动到指定的行、rs.relative(n)前进n行,负数就退回,除了beforeFirst和afterLast,其他函数一般都是先判断定位的位置是否是有意义的行,是就返回true并跳过去,否则就返回false并不动。
  可更新的结果集就是在更新结果集的时候自动更新数据库,rs.updateXxx(列号或列名, Xxx类型新值)更新结果集中当前行的值;rs.updateRow()将rs行的所有更改写入数据库,也可以通过cancelRowUpdates()来取消对当前行的修改。rs.deleteRow()同时从结果集中和数据库中删除当前行。rs.moveToInsertRow()建立一个空的插入行并将游标移动过去,rs.updateXxx()对插入行添加数据,没有添加数据的行默认为null,rs.insertRow()将该行添加到数据库,rs.moveToCurrentRow()跳回原来的行,插入的行位置是不确定的。
  要获得可滚动和可更新的结果集,在创建语句时需要特殊参数,connection.createStatement(resultSetType, resultSetConcurrency)/connection.prepareCall(sql, resultSetType, resultSetConcurrency),resultSetType可以取ResultSet.TYPE_FORWARD_ONLY不可更新的结果集、ResultSet.TYPE_SCROLL_INSENSITIVE对数据库变化不敏感的结果集、ResultSet.TYPE_SCROLL_SENSITIVE对数据库变化敏感(如果数据库变化了,新的结果集滚动时也会更新),resultSetConcurrency可以取ResultSet.CONCUR_READ_ONLY不可更新的结果集、ResultSet.CONCUR_UPDATABLE可更新的结果集,虽然参数指定更新结果集,但返回的结果集也不一定是可更新的,必须是有主键的单表或只通过主键连接的多个表联合查询才会返回可更新的结果集,可以通过resultSet.getType和resultSet.getConcurrency查看其结果集的实际状况是否可滚动和是否可更新。
  可滚动的结果集和可更新结果集整个过程中都保持着数据库的连接,很耗资源

行集

  行集RowSet是ResultSet的子接口。行集有很多实现类,主要介绍CachedRowSet。
  结果集有一个弊端就是要始终保持连接不关闭,因为断开连接就会销毁语句和结果集,这是很耗资源的,而行集无须始终保持数据库连接。
  得到一个行集的步骤为先通过RowSetProvider.newFactory()获得行集工厂RowSetFactory,再通过工厂创建行集rowSetFactory.createCachedRowSet(),再往行集里面添加内容rowSetFactory.populate(resultSet);这样得到一个行集,相当于把resultSet克隆下来了,然后关闭连接把原来的语句和结果集一起关掉了,但我们任然可以分析查询结果。在普通的结果集依次分析结果集的情况倒没什么,分析完就可以关闭连接,不怎么消耗资源,但如果是可滚动的结果集,由于需要长期滚动查询,所以结果集不能关,所以语句和连接也不能关,浪费资源,这时候就可以用行集。
  另一种方式就是不用结果集来克隆行集,而是通过行集执行语句用返回结果填充行集,步骤先获得行集对象同上,然后设置数据库参数cachedRowSet.setUrl(“url”);cachedRowSet.setUserName(“userName”);cachedRowSet.setPassword(“password”);再设置预备语句cachedRowSet.setCommand(“sql预备语句”);cachedRowSet.setXxx(第n个?, value);最后执行语句并用结果填充行集cachedRowSet.execute();在执行语句之前可以cachedRowSet.setPageSize(n)来设置一次查询的行数; cachedRowSet.nextPage()加载下一页; cachedRowSet.previousPage()加载上一页。
  将行集的改变更新到数据,cachedRowSet.acceptChanges(); cachedRowSet.acceptChanges(connection)如果行集没有设置数据库参数;同结果集一样,也并不是所有的行集都是可更新的。

元数据

  描述数据库、表本身而非业务的数据,主要有关于数据库、结果集和预备语句参数的元数据。
  connection.getMetaData()获取数据库相关的元数据对象DatabaseMetaData,然后通过数据库元数据对象获取需要的数据库信息,如获取所有的表databaseMetaData.getTables(catalog, schemaPattern, tableNamePattern, types表类型可以是表、视图、系统表等null为所有)返回一个结果集,每一个结果集代表一张表,第3列就是表的名称,具体每一列什么意思可以查文档,databaseMetaData.getMaxConnections()获取数据库的最大并行连接数,databaseMetaData.supportsBatchUpdates()是否支持批量更新。
  resultSet.getMetaData()获取结果集相关的元数据对象ResultSetMetaData,然后通过结果集元数据对象获取结果集相关的结果集信息,如resultSetMetaData.getColumnCount()获取结果集列数,resultSetMetaData.getColumnName(column)获取某一列的名称等。

事务

  事务具有ACID四大特性,即原子性(Atomicity)、一致性(Correspondence)、隔离性(Isolation)、持久性(Durability)。一个事务里面的多个语句要么都执行,要么都不执行,具有原子性。
  默认情况下获得的连接在执行语句时都是自动提交的,如果要使用事务,需要让连接禁止自动提交connection.setAutoCommit(false);然后创建并执行一系列语句,最后需要提交connection.commit();当然中途如果发生意外或自己想回到上次提交之前,可以回滚事务connection.rollback()。
  如果不想完全回滚到事务的开头,而是回滚到某一个位置,可以在事务的一系列语句中某处添加保存点connection.setSavePoint()返回一个savePoint对象,在需要回滚到savePoint的时候调用connection.rollback(savePoint),在不使用保存点的时候一定要释放保存点connection.releaseSavePoint(savePoint)。
  将一些列的更新操作作为批量操作可以提高效率,批量操作不能添加查询语句,statement.addBatch(“sql更新语句1”);statement.addBatch(“sql更新语句2”); statement.executeBatch();返回一个int[]对应每一个操作的返回值,也可以用预处理语句,preparedStatement.set(1,3); preparedStatement.addBatch(); preparedStatement.set(1,5); preparedStatement.addBatch(); preparedStatement.executeBatch; 如果批处理语句过多,需要分批操作,比如100条分为一批进行操作,批量操作并不是原子性的,所以我们可以把批量操作用事务处理,在开始关闭自动提交,再是批量操作,然后手动提交,如果出错回滚事务。

数据源

  数据源都须要实现javax.sql.DataSource接口,其最重要的接口函数就是getConnection(),通常具有连接池的功能,一个数据源可以有多个连接池。
  自己写连接池要注意,连接池中空闲的连接可能由于某些原因已经断开,所以可以在每次从连接池中获取连接时先检查连接是否可用connection.isValid(超时时间单位秒),或者用新的线程不断检测连接池中连接的可用状态(这种方式交互性好,因为检查连接可用否是需要时间的),另外将用过的连接放回连接池时应该释放连接的statement资源,可以在每次创建statement的时候,用个链表把同一个Connection里面所有的statement连起来,这样在释放一个连接(实际上并未真正地关闭它)的时候就知道该释放哪些statement了,否则同一个连接的statement会越来越多。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值