JDBC ,Java DataBase Connectivity ,是一种用于执行SQL语句的Java API;
所有数据库都支持的一套链接方式;
高级的持久化框架也是基于JDBC来实现的,比如Mybatis;
这里我用的是mysql的数据库,使用的是Java 8 的环境;
1 2 3 4 5 6 <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.13</version> </dependency>
粗略的总结写JDBC的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 // 获取Connection Connection connection = DriverManager.getConnection(dbUrl, name, pwd); // 获取Statement Statement statement = connection.createStatement(); PrepareStatement statement = connection.prepareStatement(sql); // execute SQL statement.execute // 获取结果集 ResultSet rs = statement.getResultSet();
连接数据库 驱动方式 连接驱动数据库:这里采用直接通过类加载的方式加载驱动文件,不推荐使用DriverManager.registerDriver()的方式驱动;
缺点:使用DriverManager.registerDriver()驱动的方式会驱动文件会在内存中注册两次,重复注册;
原因:使用DriverManager.registerDriver()注册的时候,需要new 一个 Driver的实例当参数;
而在Driver加载的时候就会执行其中的静态代码块注册;所有使用DriverManager.registerDriver() 内存中有两个Driver的实例;
1 2 3 4 5 6 7 8 9 10 public class Driver extends NonRegisteringDriver implements java.sql.Driver { // // Register ourselves with the DriverManager // static { ... java.sql.DriverManager.registerDriver(new Driver()); ... } }
驱动文件 新驱动文件:com.mysql.cj.jdbc.Driver
旧驱动文件:com.mysql.jdbc.Driver :这个已经被废弃了,推荐使用新的;效果是一样的,只是有废弃的提示log;
1 2 3 4 5 6 7 8 9 10 11 12 private static Connection getConnection() throws ClassNotFoundException, SQLException { //加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 这里不推荐使用 DriverManager.registerDriver(new Driver()); 加载驱动 // 如果是用registerDriver 内存中会有注册两次 String dbUrl = "jdbc:mysql://localhost:3306/conan"; String name = "root"; String pwd = "12345678"; return DriverManager.getConnection(dbUrl, name, pwd); }
数据库连接Connection Connection 是一个数据库的链接的接口;提供获取Statement(其中有 session 最终真正执行Sql )的方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // 获取connection // Worker method called by the public getConnection() methods. private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. Connection con = aDriver.driver.connect(url, info); return (con); } } } //完成数据驱动注册 public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { ... /* Register the driver if it has not already been added to our list */ registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); ... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // com.mysql.cj.jdbc.NonRegisteringDriver#connect // Connection con = aDriver.driver.connect(url, info); public java.sql.Connection connect(String url, Properties info) throws SQLException { ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info); switch (conStr.getType()) { case SINGLE_CONNECTION: return com.mysql.cj.jdbc.ConnectionImpl.getInstance(conStr.getMainHost()); case LOADBALANCE_CONNECTION: return LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl) conStr); case FAILOVER_CONNECTION: return FailoverConnectionProxy.createProxyInstance(conStr); case REPLICATION_CONNECTION: return ReplicationConnectionProxy.createProxyInstance((ReplicationConnectionUrl) conStr); default: return null; } }
com.mysql.cj.jdbc.NonRegisteringDriver#connect 方法根据Url 中的scheme 规则 返回对应的Connection对象;
连接协议: SINGLE_CONNECTION:单一数据库连接;
jdbc:mysql://localhost:3306/conan 我这里就是一个主机而已;
LOADBALANCE_CONNECTION: 负载均衡连接;可以是一个集群,里面有很多主机地址;
jdbc:mysql://localhost:3306,[host]:[port],…/[database]
FAILOVER_CONNECTION :Failover协议,不知道怎么翻译比较好,如果主机失败,就连接到从机上;
jdbc:mysql:loadbalance:[host]:[port],…/[database]
REPLICATION_CONNECTION :Replication的思想是将数据在集群的多个节点同步、备份,以提高集群数据的可用性;
jdbc:mysql:replication:[host]:[port],…/[database]
上面的connect()方法,根据URL的scheme类型得到不同的Connection,只有SINGLE_CONNECTION类型,是直接创建了一个ConnectionImpl的实例,其他的都是通过动态代理的方式生成Connection; 这就是上一篇为什么总结动态代理的一个原因;
LOADBALANCE_CONNECTION,FAILOVER_CONNECTION,REPLICATION_CONNECTION:
其最终都是通过代理的方式创建Connection;
1 2 ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
以LOADBALANCE_CONNECTION类型为例:
LoadBalancedConnectionProxy.createProxyInstance 实现了InvocationHandler接口;
1 2 3 4 5 6 7 LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl) conStr); public static LoadBalancedConnection createProxyInstance(LoadbalanceConnectionUrl connectionUrl) throws SQLException { LoadBalancedConnectionProxy connProxy = new LoadBalancedConnectionProxy(connectionUrl); return (LoadBalancedConnection) java.lang.reflect.Proxy.newProxyInstance(LoadBalancedConnection.class.getClassLoader(), INTERFACES_TO_PROXY, connProxy); }
代理类真正代理的被代理对象是com.mysql.cj.conf.url.LoadbalanceConnectionUrl;
1 2 3 4 case LOADBALANCE_CONNECTION: connectionString = (ConnectionUrl) Util.getInstance("com.mysql.cj.conf.url.LoadbalanceConnectionUrl", new Class<?>[] { ConnectionUrlParser.class, Properties.class }, new Object[] { connStrParser, info }, null); break;
Statement 获取到Connection之后说明已经连接上数据库了,接下来我们可以通过connection获取statement接口的实例,来执行sql和获取ResultSet;
Statement 是一个接口;
PreparedStatement 也是一个接口,而且继承了Statement;
CallableStatement 也是一个接口,而且继承了PreparedStatement;
Statement SINGLE_CONNECTION协议下,获取到的Connection真正的实现是ConnectionImpl;
可以通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 createStatement() 获取默认配置的Statement的实现StatementImpl createStatement(int resultSetType, int resultSetConcurrency) /** * resultSetType ResultSet.TYPE_FORWARD_ONLY 默认的只能向前滚动; * TYPE_SCROLL_INSENSITIVE 双向滚动,但不及时更新,在查询过程中,如果其他线程更新数据也可以查询不到; * TYPE_SCROLL_SENSITIVE 双向滚动,及时更新,在查询过程中,如果其他线程更新数据也可以查询到; * * resultSetConcurrency ResultSet.CONCUR_READ_ONLY 并发性:ResultSet 只能Read * CONCUR_UPDATABLE 并发性:ResultSet 支持更新 * * resultSetHoldability 只能是 java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT 否则报错,查看代码,只有坚持是不是该类型,也没设置到ResultSet上; */ createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
三个创建statement的方法最终都是创建了一个StatementImpl实例,并把并设置其ResultSet的两个属性resultSetType,resultSetConcurrency;
但是并没有看到设置resultSetHoldability的属性;
Statement 接口方法可以分为以下几类
设置类方法 设置类的方法,没什么好多解释的,直接看方法名称就知道什么意思;
1 2 3 4 5 6 7 8 setFetchSize(int rows) setFetchDirection(ResultSet.FETCH_FORWARD); setMaxRows(int max) setLargeMaxRows(long max) setQueryTimeout(int seconds) setCursorName(String name) close()
excute类方法 Statement接口不能设置sql参数,其中execute方法是用来执行sql的方法,execute ,executeUpdate 一系列的方法最终都会和autoGeneratedKeys这个参参数有关,最简单的是使用默认的autoGeneratedKeys;
当Sql语句是Insert,autoGeneratedKeys 的RETURN_GENERATED_KEYS、NO_GENERATED_KEYS ,关系到自增属性字段的查询问题,只有配置了RETURN_GENERATED_KEYS属性的时候,才可以使用getGeneratedKeys() 方法获取ResultSet。然后获取自增属性字段的值;
PreparedStatement, CallableStatement 的实例对象不能调用下面一系列的excute方法;
Statement 执行execute的时候就已经可以获取结果集; 1 2 3 4 5 execute(String sql) boolean execute(String sql, int autoGeneratedKeys) executeUpdate(String sql) executeLargeUpdate(String sql, int autoGeneratedKeys) executeLargeUpdate(String sql, int columnIndexes[])
批处理方法(前提是connection 关闭自动提交功能) 1 2 3 4 addBatch( String sql ) clearBatch() int[] executeBatch() long[] executeLargeBatch()
获取类 1 2 3 boolean getMoreResults() ResultSet getResultSet() ResultSet getGeneratedKeys()
示例代码 自增属性 1 2 3 4 5 6 String creatSql = "insert into users( name, password, email, birthday) values ( \"222\",\"222\",\"2222\",\"222222\")"; //其中有一个id 是自增属性 Statement statement = connection.createStatement(); int i = statement.executeUpdate(creatSql, Statement.NO_GENERATED_KEYS); ResultSet resultSet = statement.getResultSet(); resultSet.next(); int anInt = resultSet.getInt(1);
批处理 1 2 3 4 5 connection.setAutoCommit(false); for(..){ statement.addBatch(sql); //如果是PreparedStatement 也是类似 } statement.executeBatch(); //statement. executeLargeBatch();
ResultSet ResultSet 作为查询放回的结果集,有必要也介绍一下其中的方法;
ResultSet 可以分为如下几类方法
查询字段valvue类:此类方法很多,这里不一一列举 1 2 3 4 5 int getInt(int columnIndex) int getInt(String columnLabel) String getString(int columnIndex) String getString(String columnLabel)
操作游标类: 1 2 3 4 5 6 boolean next() boolean previous() boolean absolute( int row ) boolean relative( int rows ) void afterLast() void moveToInsertRow()
修改系列 1 2 3 4 5 6 void insertRow() boolean rowInserted() //增加了返回而已 void updateRow() boolean rowUpdated() void deleteRow() boolean rowDeleted()
修改系列的方法相对特别,查询的时候还可以操作数据库;前面说过的resultSetConcurrency属性 ResultSet.CONCUR_READ_ONLY 并发性:ResultSet 只能Read ,CONCUR_UPDATABLE 并发性:ResultSet 支持更新;如果需要修改就必须设置为CONCUR_UPDATABLE;
如果需要插入行,得先执行moveToInsertRow();示例代码如下
1 2 3 4 5 6 7 8 String sql = "select u.name ,u.password,u.birthday,u.id from users as u"; PreparedStatement statement = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); statement.setFetchSize(1); statement.setFetchDirection(ResultSet.FETCH_FORWARD); resultSet.moveToInsertRow(); resultSet.updateInt("id",14); resultSet.updateString("name","xxxxx"); resultSet.insertRow();
PreparedStatement PreparedStatement 区别于 Statement ,PreparedStatement可以在执行之前修改sql语句中的占位符?参数;有一系列的setXxx()方法;
设置好sql中的数据之后,执行execute()方法执行sql;
CallableStatement CallableStatement 是用来执行存储过程的Statement;
有一系列的方法用于设置输入参数,还有一系列方法用于获取输出参数;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 CREATE PROCEDURE add_pro(a int,b int,out sum int) BEGIN set sum = a + b; END; ==================================================================================== CallableStatement cstmt = conn.prepareCall("{call add_pro(?,?,?)}") cstmt.setInt(1,4); cstmt.setInt(2,5); cstmt.registerOutParameter(3,Types.INTEGER); cstmt.execute(); System.out.println("执行结果是:"+cstmt.getInt(3));
事务管理 JDBC的事务操作是基于Connection接口如下几个方法展开的;
采用这些API设计的一套编程式事务管理方式;其中connection自动提交应该设置为 false ;connection.setAutoCommit(false);
1 2 3 4 5 6 void commit() void rollback() void rollback(Savepoint savepoint) Savepoint setSavepoint() Savepoint setSavepoint(String name)
GitHub登录不了 ?信任该网站之后再登录。