JAVA JDBC详解
一、相关概念
JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。JDBC制定了统一访问各类关系数据库的标准接口,为各个数据库厂商提供了标准接口的实现。
JDBC规范将驱动程序归结为以下几类(选自Core Java Volume Ⅱ——Advanced Features):
- 第一类驱动程序将JDBC翻译成ODBC,然后使用一个ODBC驱动程序与数据库进行通信。
- 第二类驱动程序是由部分Java程序和部分本地代码组成的,用于与数据库的客户端API进行通信。
- 第三类驱动程序是纯Java客户端类库,它使用一种与具体数据库无关的协议将数据库请求发送给服务器构件,然后该构件再将数据库请求翻译成数据库相关的协议。
- 第四类驱动程序是纯Java类库,它将JDBC请求直接翻译成数据库相关的协议。
我们安装好数据库之后,我们的应用程序也是不能直接使用数据库的,必须要通过相应的数据库驱动程序,通过驱动程序去和数据库打交道。其实也就是数据库厂商的JDBC接口实现,即对Connection等接口的实现类的jar文件。
二、常用接口
Driver接口由数据库厂家提供,作为java开发人员,只需要使用Driver接口就可以了。在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序,不同的数据库有不同的装载方法。如:
-
装载MySql驱动:Class.forName(“com.mysql.jdbc.Driver”);
-
装载Oracle驱动:Class.forName(“oracle.jdbc.driver.OracleDriver”);
-
完整:
Connection与特定数据库的连接(会话),在连接上下文中执行sql语句并返回结果。DriverManager.getConnection(url, user, password)方法建立在JDBC URL中定义的数据库Connection连接上。
-
连接MySql数据库:Connection conn = DriverManager.getConnection(“jdbc:mysql://host:port/database”, “user”, “password”);
-
连接Oracle数据库:Connection conn = DriverManager.getConnection(“jdbc:oracle:thin:@host:port:database”, “user”, “password”);
-
连接SqlServer数据库:
Connection conn = DriverManager.getConnection(“jdbc:microsoft:sqlserver://host:port; DatabaseName=database”, “user”, “password”); -
完整实例:
常用方法:
- createStatement():创建向数据库发送sql的statement对象。
- prepareStatement(sql):创建向数据库发送预编译sql的PrepareSatement对象。
- prepareCall(sql):创建执行存储过程的callableStatement对象。
- setAutoCommit(boolean autoCommit):设置事务是否自动提交。
- commit() :在链接上提交事务。
- rollback() :在此链接上回滚事务。
用于执行静态SQL语句并返回它所生成结果的对象。
三种Statement类:
- Statement:由createStatement创建,用于发送简单的SQL语句(不带参数)。
- PreparedStatement :继承自Statement接口,由preparedStatement创建,用于发送含有一个或多个参数的SQL语句。PreparedStatement对象比Statement对象的效率更高,并且可以防止SQL注入,所以我们一般都使用PreparedStatement。
- CallableStatement:继承自PreparedStatement接口,由方法prepareCall创建,用于调用存储过程。
常用Statement方法:
- execute(String sql):运行语句,返回是否有结果集
- executeQuery(String sql):运行select语句,返回ResultSet结果集。
- executeUpdate(String sql):运行insert/update/delete操作,返回更新的行数。
- addBatch(String sql) :把多条sql语句放到一个批处理中。
- executeBatch():向数据库发送一批sql语句执行。
ResultSet提供检索不同类型字段的方法,常用的有:
- getString(int index)、getString(String columnName):获得在数据库里是varchar、char等类型的数据对象。
- getFloat(int index)、getFloat(String columnName):获得在数据库里是Float类型的数据对象。
- getDate(int index)、getDate(String columnName):获得在数据库里是Date类型的数据。
- getBoolean(int index)、getBoolean(String columnName):获得在数据库里是Boolean类型的数据。
- getObject(int index)、getObject(String columnName):获取在数据库里任意类型的数据。
ResultSet还提供了对结果集进行滚动的方法:
- next():移动到下一行 Previous():移动到前一行 absolute(int row):移动到指定行
- beforeFirst():移动resultSet的最前面。 afterLast() :移动到resultSet的最后面。
使用后依次关闭对象及连接:ResultSet → Statement → Connection
三、使用JDBC的步骤
加载JDBC驱动程序 → 建立数据库连接Connection(可以从配置文件中获取参数) → 创建执行SQL的语句Statement → 处理执行结果ResultSet → 释放资源- 方式一:Class.forName(“com.MySQL.jdbc.Driver”)推荐这种方式,不会对具体的驱动类产生依赖。
- 方式二:DriverManager.registerDriver(com.mysql.jdbc.Driver)会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖。
Connection conn = DriverManager.getConnection(url, user, password);
URL用于标识数据库的位置,通过URL地址告诉JDBC程序连接哪个数据库,URL写法
其他参数如:useUnicode=true&characterEncoding=utf8
5.释放资源
//数据库连接(Connection)非常耗资源,尽量晚创建,尽量早的释放//都要加try catch 以防前面关闭出错,后面的就不执行了 try {if (rs != null) {rs.close();} } catch (SQLException e) {e.printStackTrace(); } finally {try {if (st != null) {st.close();}} catch (SQLException e) {e.printStackTrace();} finally {try {if (conn != null) {conn.close();}} catch (SQLException e) {e.printStackTrace();}} }四、事务(ACID特点、隔离级别、提交commit、回滚rollback)
五、完整例子
import java.io.*; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties;public class DBUtil {private final String dbConnFile = "resource/database/jdbc.properties";private Connection conn=null;private String dbDriver; //定义驱动private String dbURL; //定义URLprivate String userName; //定义用户名private String password; //定义密码//从配置文件取数据库链接参数private void loadConnProperties(){Properties props = new Properties();try {props.load(new FileInputStream(dbConnFile));//根据配置文件路径Conf加载配置文件} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}this.dbDriver = props.getProperty("driver");//从配置文件中取得相应的参数并设置类变量this.dbURL = props.getProperty("url");this.userName = props.getProperty("username");this.password = props.getProperty("password");}public boolean openConnection(){try {loadConnProperties();Class.forName(dbDriver);this.conn = DriverManager.getConnection(dbURL,userName,password);return true;} catch(ClassNotFoundException classnotfoundexception) {classnotfoundexception.printStackTrace();System.err.println("db: " + classnotfoundexception.getMessage());} catch(SQLException sqlexception) {System.err.println("db.getconn(): " + sqlexception.getMessage());}return false;}protected void finalize() throws Exception{try {if(null!=conn)conn.close();}catch (SQLException e) {e.printStackTrace();}}// 查询并得到结果集public ResultSet execQuery(String sql) throws Exception {ResultSet rstSet = null;try {if (null == conn)throw new Exception("Database not connected!");Statement stmt = conn.createStatement();rstSet = stmt.executeQuery(sql);} catch (SQLException e) {e.printStackTrace();}return rstSet;}// 插入一条新纪录,并获取标识列的值public ResultSet getInsertObjectIDs(String insertSql) throws Exception{ResultSet rst = null;try {if(null==conn)throw new Exception("Database not connected!");Statement stmt = conn.createStatement();stmt.executeUpdate(insertSql, Statement.RETURN_GENERATED_KEYS);rst = stmt.getGeneratedKeys();} catch (SQLException e) {e.printStackTrace();}return rst;}//以参数SQL模式插入新纪录,并获取标识列的值public ResultSet getInsertObjectIDs(String insertSql, Object[] params) throws Exception {ResultSet rst = null;PreparedStatement pstmt = null ;try {if (null == conn)throw new Exception("Database not connected!");pstmt = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);if(null != params){for (int i = 0; i < params.length; i++) {pstmt.setObject(i + 1, params[i]);}}pstmt.executeUpdate();rst = pstmt.getGeneratedKeys();} catch (SQLException e) {e.printStackTrace();}return rst;}// 插入、更新、删除public int execCommand(String sql) throws Exception{int flag = 0;try {if(null==conn)throw new Exception("Database not connected!");Statement stmt = conn.createStatement();flag = stmt.executeUpdate(sql);stmt.close();} catch (SQLException e) {e.printStackTrace();}return flag;}/* // 存储过程调用public void callStordProc(String sql, Object[] inParams, SqlParameter[] outParams) throws Exception {CallableStatement cst = null ;try {if (null == conn)throw new Exception("Database not connected!");cst = conn.prepareCall(sql);if(null != inParams){for (int i = 0; i < inParams.length; i++) {cst.setObject(i + 1, inParams[i]);}}if (null!=outParams){for (int i = 0; i < inParams.length; i++) {cst.registerOutParameter(outParams[i].getName(), outParams[i].getType());}}cst.execute();} catch (SQLException e) {e.printStackTrace();}} */// 释放资源public void close(ResultSet rst) throws Exception {try {Statement stmt = rst.getStatement();rst.close();stmt.close();} catch (SQLException e) {e.printStackTrace();}}public PreparedStatement execPrepared(String psql) throws Exception {PreparedStatement pstmt = null ;try {if (null == conn)throw new Exception("Database not connected!");pstmt = conn.prepareStatement(psql);} catch (SQLException e) {e.printStackTrace();}return pstmt;}// 释放资源public void close(Statement stmt) throws Exception {try {stmt.close();} catch (SQLException e) {e.printStackTrace();}}// 释放资源public void close() throws SQLException, Exception{if(null!=conn){conn.close();conn=null;}}public Connection getConn() {return conn;}public static void main(String[] args) {} }六、统一总结
什么是JDBC?JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。使用Java程序访问数据库时,java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
例如,我们在Java代码中如果要访问MySQL,那么必须编写代码操作JDBC接口。注意到JDBC接口是Java标准库自带的,所以可以直接编译。而具体的JDBC驱动是由数据库厂商提供的,例如,MySQL的JDBC驱动由Oracle提供。因此,访问某个具体的数据库,我们只需要引入该厂商提供的JDBC驱动,就可以通过JDBC接口来访问,这样保证了Java程序编写的是一套数据库访问代码,却可以访问各种不同的数据库,因为他们都提供了标准的JDBC驱动:
Java标准库自带的JDBC接口其实就是定义了一组接口,而某个具体的JDBC驱动其实就是实现了这些接口的类
实际上,一个MySQL的JDBC的驱动就是一个jar包,它本身也是纯Java编写的。我们自己编写的代码只需要引用Java标准库提供的java.sql包下面的相关接口,由此再间接地通过MySQL驱动的jar包通过网络访问MySQL服务器,所有复杂的网络通讯都被封装到JDBC驱动中,因此,Java程序本身只需要引入一个MySQL驱动的jar包就可以正常访问MySQL服务器:
所以我们首先得找一个MySQL的JDBC驱动。所谓JDBC驱动,其实就是一个第三方jar包,我们直接添加一个Maven依赖就可以了:
注意到这里添加依赖的scope是runtime,因为编译Java程序并不需要MySQL的这个jar包,只有在运行期才需要使用。如果把runtime改成compile,虽然也能正常编译,但是在IDE里写程序的时候,会多出来一大堆类似com.mysql.jdbc.Connection这样的类,非常容易与Java标准库的JDBC接口混淆,所以坚决不要设置为compile。
- 获取链接
核心代码是DriverManager提供的静态方法getConnection()。DriverManager会自动扫描classpath,找到所有的JDBC驱动,然后根据我们传入的URL自动挑选一个合适的驱动。
因为JDBC连接是一种昂贵的资源,所以使用后要及时释放。使用try (resource)来自动释放JDBC连接是一个好方法
获取到JDBC连接后,下一步我们就可以查询数据库了。查询数据库分以下几步:
-
第一步,通过Connection提供的createStatement()方法创建一个Statement对象,用于执行一个查询;
-
第二步,执行Statement对象提供的executeQuery("SELECT * FROM students")并传入SQL语句,执行查询并获得返回的结果集,使用ResultSet来引用这个结果集;
-
第三步,反复调用ResultSet的next()方法并读取每一行结果。
demo:
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {try (Statement stmt = conn.createStatement()) {try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=1")) {while (rs.next()) {long id = rs.getLong(1); // 注意:索引从1开始long grade = rs.getLong(2);String name = rs.getString(3);int gender = rs.getInt(4);}}} }注意使用后依次关闭对象及连接:ResultSet → Statement → Connection
- SQL注入风险
使用Statement拼字符串非常容易引发SQL注入的问题,这是因为SQL参数往往是从方法参数传入的。
//Statement String id = "5"; String sql = "delete from table where id=" + id; Statement st = conn.createStatement(); st.executeQuery(sql); //存在sql注入的危险 //如果用户传入的id为“5 or 1=1”,那么将删除表中的所有记录PreparedStatement 有效的防止sql注入(SQL语句在程序运行前已经进行了预编译,
//当运行时动态地把参数传给PreprareStatement时,即使参数里有敏感字符如 //or '1=1'数据库也会作为一个参数一个字段的属性值来处理而不会作为一个SQL指令) String sql = “insert into user (name,pwd) values(?,?)”; PreparedStatement ps = conn.preparedStatement(sql); ps.setString(1, “col_value”); //占位符顺序从1开始 ps.setString(2, “123456”); //也可以使用setObject ps.executeQuery();我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。
JDBC连接池有一个标准的接口javax.sql.DataSource,注意这个类位于Java标准库中,但仅仅是接口。要使用JDBC连接池,我们必须选择一个JDBC连接池的实现。常用的JDBC连接池有:
- HikariCP
- C3P0
- BoneCP
- Druid
目前使用最广泛的是HikariCP。我们以HikariCP为例,要使用JDBC连接池,先添加HikariCP的依赖如下:
<dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId><version>2.7.1</version> </dependency>紧接着,我们需要创建一个DataSource实例,这个实例就是连接池:
HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/test"); config.setUsername("root"); config.setPassword("password"); config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒 config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒 config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10 DataSource ds = new HikariDataSource(config);注意创建DataSource也是一个非常昂贵的操作,所以通常DataSource实例总是作为一个全局变量存储,并贯穿整个应用程序的生命周期。
有了连接池以后,我们如何使用它呢?和前面的代码类似,只是获取Connection时,把DriverManage.getConnection()改为ds.getConnection():
try (Connection conn = ds.getConnection()) { // 在此获取连接... } // 在此“关闭”连接通过连接池获取连接时,并不需要指定JDBC的相关URL、用户名、口令等信息,因为这些信息已经存储在连接池内部了(创建HikariDataSource时传入的HikariConfig持有这些信息)。一开始,连接池内部并没有连接,所以,第一次调用ds.getConnection(),会迫使连接池内部先创建一个Connection,再返回给客户端使用。当我们调用conn.close()方法时(在try(resource){...}结束处),不是真正“关闭”连接,而是释放到连接池中,以便下次获取连接时能直接返回。
因此,连接池内部维护了若干个Connection实例,如果调用ds.getConnection(),就选择一个空闲连接,并标记它为“正在使用”然后返回,如果对Connection调用close(),那么就把连接再次标记为“空闲”从而等待下次调用。这样一来,我们就通过连接池维护了少量连接,但可以频繁地执行大量的SQL语句。通常连接池提供了大量的参数可以配置,例如,维护的最小、最大活动连接数,指定一个连接在空闲一段时间后自动关闭等,需要根据应用程序的负载合理地配置这些参数。此外,大多数连接池都提供了详细的实时状态以便进行监控。
参考文章1
参考文章2
参考文章3
总结
以上是生活随笔为你收集整理的JAVA JDBC详解的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: IO流——流的分类、InputStrea
- 下一篇: 产品经理和程序员之间的“潜台词”,你能听