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登录不了 ?信任该网站之后再登录。