什么是JDBC?JDBC就是Java程序访问数据库的规范,是一个规范定义接口,各种数据库厂家实现了JDBC这个接口,这些实现类就是数据库驱动,使用时只需要调用接口中的方法即可,不用关注类是如何实现的。
JDBC的核心API有以下几种:
DriverManager类:管理和注册数据库驱动,获取数据库连接对象Connection接口:一个数据库连接对象,用于创建Statement和PreparedStatement对象Statement接口:一个数据库操作对象,用于执行sql语句PreparedStatement:一个数据库操作对象,用于执行sql语句,Statement的子接口ResultSet:用于封装数据库查询的结果集,返回给客户端Java程序
这些API怎么用后面会逐个介绍到。
JDBC经典6步:
public class Demo01 { public static void main(String[] args) throws ClassNotFoundException, SQLException { //1.加载注册驱动,省略了注册 Class.forName("com.mysql.jdbc.Driver"); //2.获取数据库连接对象 String url="jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=false"; String username="root"; String password="root"; Connection connection = DriverManager.getConnection(url, username, password); //3.获取数据库操作对象 Statement statement = connection.createStatement(); //4.执行sql语句,返回结果或者行数 String sql="SELECT * FROM users"; ResultSet resultSet = statement.executeQuery(sql); //5.处理结果 while(resultSet.next()){ System.out.println("id="+resultSet.getObject("id")); System.out.println("NAME="+resultSet.getObject("NAME")); System.out.println("PASSWORD="+resultSet.getObject("PASSWORD")); System.out.println("email="+resultSet.getObject("email")); System.out.println("birthday="+resultSet.getObject("birthday")); } //6.释放连接 resultSet.close(); statement.close(); connection.close(); }}
id=1NAME=zhansanPASSWORD=123456email=zs@sina.combirthday=1980-12-04id=2NAME=lisiPASSWORD=123456email=lisi@sina.combirthday=1981-12-04id=3NAME=wangwuPASSWORD=111111email=wangwu@qq.combirthday=1996-05-22
第一步:注册、加载驱动
实际上应该是这样的:
Driver driver=new com.mysql.jdbc.Driver();DriverManager.registerDriver(driver);
为什么这样只加载不注册也可以呢(mysql5之后)?
//1.加载注册驱动,省略了注册Class.forName("com.mysql.jdbc.Driver");
点开Driver类的源码发现:
类Driver实现了java.sql.Driver接口,在静态代码块中,DriverManager类的registerDriver方法注册了驱动,这样我们使用注解Class.forName加全类名就会自动执行静态代码块的内容,即自动注册了驱动,只需要加载即可。
第二步:获取数据库连接对象connection
DriverManager类:管理和注册数据库驱动,获取数据库连接对象DriverManager类的getConnection方法返回一个数据库连接对象
//2.获取数据库连接对象String url="jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=false";String username="root";String password="root"; Connection connection = DriverManager.getConnection(url, username, password);
第三步:获取数据库操作对象statement
Connection接口:一个数据库连接对象,用于创建Statement和PreparedStatement对象数据库连接对象connection的createStatement方法可以创建数据库操作对象Statementconnection还可以管理事务:connection.setAutoCommit(false);//开启事connection.commit();//提交事务connection.rollback();//回滚事务
Statement statement = connection.createStatement();
第四步:执行sql语句,返回结果或者行数
boolean execute(String sql):可以执行任意sqlint executeUpdate(String sql):执行DML语句(insert、delete、update),返回影响的行数ResultSet executeQuery(String sql):执行DQL(select)语句,返回结果集
//4.执行sql语句,返回结果或者行数String sql="SELECT * FROM users";ResultSet resultSet = statement.executeQuery(sql);
第五步:处理结果
如果执行的是DML语句则返回行数大于0表示执行成功,如果执行的是DQL语句,则返回结果集。
ResultSet:用于封装数据库查询的结果集,返回给客户端Java程序boolean next():游标向下移动一行,判断当前行是否有数据getXxx():获取数据,Xxx表示不同数据类型
while(resultSet.next()){ System.out.println("id="+resultSet.getObject("id")); System.out.println("NAME="+resultSet.getObject("NAME")); System.out.println("PASSWORD="+resultSet.getObject("PASSWORD")); System.out.println("email="+resultSet.getObject("email")); System.out.println("birthday="+resultSet.getObject("birthday"));}
第六步:释放连接
从小到大释放连接。
//6.释放连接resultSet.close();statement.close();connection.close();
以上六步只是最基本的,为了避免空指针异常,改进如下:
public class Demo02 { public static void main(String[] args) { Connection conn=null; Statement st=null; ResultSet rs=null; try { //1.加载驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获取数据库连接对象 conn= DriverManager.getConnection(); //3.获取数据库操作对象 st= conn.createStatement(); //4.执行sql语句,返回结果或者行数 String sql="SELECT * FROM users"; ResultSet resultSet = st.executeQuery(sql); //5.处理结果 while(resultSet.next()){ System.out.println("id="+resultSet.getObject("id")); System.out.println("NAME="+resultSet.getObject("NAME")); System.out.println("PASSWORD="+resultSet.getObject("PASSWORD")); System.out.println("email="+resultSet.getObject("email")); System.out.println("birthday="+resultSet.getObject("birthday")); } } catch (SQLException throwables) { throwables.printStackTrace(); }finally { if(rs!=null) { try { rs.close(); } catch (Exception e) { e.printStackTrace(); } } if(st!=null) { try { st.close(); } catch (Exception e) { e.printStackTrace(); } } if(conn!=null) { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } } }}
上面的代码重复的太多,因此进行封装。
首先是数据库的注册与连接写入db.properties文件。
driver=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=falseusername=rootpassword=root
接着是获取连接对象和释放资源的代码抽取在jdbcutils中。
public class jdbcutils { private static String driver=null; private static String url=null; private static String username=null; private static String password=null; static{ try { InputStream in = jdbcutils.class.getClassLoader().getResourceAsStream("db.properties"); Properties properties = new Properties(); properties.load(in); driver=properties.getProperty("driver"); url=properties.getProperty("url"); username=properties.getProperty("username"); password=properties.getProperty("password"); Class.forName(driver); }catch (Exception e) { e.printStackTrace(); } } //获取连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url,username,password); } //释放连接资源 public static void release(Connection conn, Statement st, ResultSet rs){ if(rs!=null) { try { rs.close(); } catch (Exception e) { e.printStackTrace(); } } if(st!=null) { try { st.close(); } catch (Exception e) { e.printStackTrace(); } } if(conn!=null) { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } }}
将注册驱动、获取连接对象和释放连接的代码抽取后,以上代码就可以写成如下,以后只需修改db.properties配置文件即可。
public class Demo02 { public static void main(String[] args) { Connection conn=null; Statement st=null; ResultSet rs=null; try { conn= jdbcutils.getConnection(); st= conn.createStatement(); String sql="SELECT * FROM users"; ResultSet resultSet = st.executeQuery(sql); while(resultSet.next()){ System.out.println("id="+resultSet.getObject("id")); System.out.println("NAME="+resultSet.getObject("NAME")); System.out.println("PASSWORD="+resultSet.getObject("PASSWORD")); System.out.println("email="+resultSet.getObject("email")); System.out.println("birthday="+resultSet.getObject("birthday")); } } catch (SQLException throwables) { throwables.printStackTrace(); }finally { jdbcutils.release(conn,st,rs); } }}
工具齐全之后,开始看JDBC中存在的问题了:sql注入问题。
看一个sql语句:
select * from user where name="zhulin" and password='a' or '1'=1name="zhulin" and password='a'为假,'1'=1为真,or拼接之后,相当于select * from user where true;
这就是sql注入问题:用户输入的内容作为了SQL语句中的一部分,改变了原SQL的真正意义。
如何解决呢?还记得:
Connection接口:一个数据库连接对象,用于创建Statement和PreparedStatement对象
PreparedStatement对象正是用于解决这一问题。
public class Demo03 { public static void main(String[] args) { Connection conn=null; PreparedStatement pstm=null; try { conn=jdbcutils.getConnection(); String sql="delete from users where id=?"; pstm=conn.prepareStatement(sql);//预编译 pstm.setInt(1,3); int i=pstm.executeUpdate(); if(i>0){ System.out.println("删除成功"); } } catch (SQLException throwables) { throwables.printStackTrace(); }finally { jdbcutils.release(conn,pstm,null); } }}
mysql数据库产商在实现PreparedStatement接口的实现类中的setString(int parameterIndex, String x)函数中做了一些处理,把单引号做了转义(只要用户输入的字符串中有单引号,那mysql数据库产商的setString()这个函数,就会把单引号做转义)。
PreparedStatement会将SQL先发给数据库预编译,引用预编译后的结果,可以多次传入不同的参数给PreparedStatement对象并执行,减少SQL编译次数,提高效率,虽然多了几行代码,但安全性更高,解决了sql注入的隐患,提高了程序的可读性。
在前面提到Connection还可以管理事务,接着就简单介绍一下JDBC控制事务。
void setAutoCommit(false);//开启事void commit();//提交事务void rollback();//回滚事务
以一个转账的例子介绍:Navicat中建account表
public class DemoTrasaction { public static void main(String[] args) { Connection conn = null; PreparedStatement pstmt1 = null; PreparedStatement pstmt2 = null; try { //1.获取连接 conn = jdbcutils.getConnection(); //开启事务 conn.setAutoCommit(false); //2.定义sql //2.1 张三 - 500 String sql1 = "update account set balance = balance - ? where id = ?"; //2.2 李四 + 500 String sql2 = "update account set balance = balance + ? where id = ?"; //3.获取执行sql对象 pstmt1 = conn.prepareStatement(sql1); pstmt2 = conn.prepareStatement(sql2); //4. 设置参数 pstmt1.setDouble(1,500); pstmt1.setInt(2,1); pstmt2.setDouble(1,500); pstmt2.setInt(2,2); //5.执行sql pstmt1.executeUpdate(); // 手动制造异常 //int i = 3/0; pstmt2.executeUpdate(); //提交事务 conn.commit(); } catch (Exception e) { //事务回滚 try { if(conn != null) { conn.rollback(); } } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { jdbcutils.release(conn,pstmt1,null); jdbcutils.release(null,pstmt2,null); } }}
正常结果如下:
如果手动制造异常,则事务回滚,转账失败。
最后,介绍一下数据库连接池JDBC Template。数据库连接池就是一个容器,存放数据库连接对象,用户访问数据库时直接从连接池中获取数据库连接对象,访问完再归还给容器。使用数据库连接池可以解决资源,提高访问效率。
连接池常用的有C3P0和Druid。两者的使用都要导入jar包。
C3P0:先导入两个jar包,然后在src目录下定义配置文件:
c3p0.properties 或者 c3p0-config.xml。
//1.创建数据库连接池对象DataSource ds = new ComboPooledDataSource();//2. 获取连接对象Connection conn = ds.getConnection();
Druid:先导入jar包 ,在任意目录下定义druid.properties配置文件。
#驱动加载driverClassName=com.mysql.jdbc.Driver#注册驱动url=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=false#连接数据库的用户名username=root#连接数据库的密码password=root#属性类型的字符串,通过别名的方式配置扩展插件, 监控统计用的stat 日志用log4j 防御sql注入:wallfilters=stat#初始化时池中建立的物理连接个数。initialSize=2#最大的可活跃的连接池数量maxActive=300#获取连接时最大等待时间,单位毫秒,超过连接就会失效。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降, 如果需要可以通过配置useUnfairLock属性为true使用非公平锁。maxWait=60000#连接回收器的运行周期时间,时间到了清理池中空闲的连接,testWhileIdle根据这个判断timeBetweenEvictionRunsMillis=60000minEvictableIdleTimeMillis=300000#用来检测连接是否有效的sql,要求是一个查询语句。validationQuery=SELECT 1#建议配置为true,不影响性能,并且保证安全性。 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis, 执行validationQuery检测连接是否有效。testWhileIdle=true#申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。设置为falsetestOnBorrow=false#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能,设置为flasetestOnReturn=false#是否缓存preparedStatement,也就是PSCache。poolPreparedStatements=false#池中能够缓冲的preparedStatements语句数量maxPoolPreparedStatementPerConnectionSize=200
//1.加载配置文件Properties pro = new Properties();InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");pro.load(is);//2.获取连接池对象DataSource ds = DruidDataSourceFactory.createDataSource(pro);//3.获取连接Connection conn = ds.getConnection();
同样的,为了简化书写,编写JDBCUtils来加载配置文件,初始化连接对象,释放资源。
public class JDBCUtils { //1.定义成员变量 DataSource private static DataSource ds ; static{ try { //1.加载配置文件 Properties pro = new Properties(); pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties")); //2.获取DataSource ds = DruidDataSourceFactory.createDataSource(pro); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } //获取连接 public static Connection getConnection() throws SQLException { return ds.getConnection(); } //释放资源 public static void close(ResultSet rs , Statement stmt, Connection conn){ if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close();//归还连接 } catch (SQLException e) { e.printStackTrace(); } } } //获取连接池方法 public static DataSource getDataSource(){ return ds; }}
这样,我们就可以方便高效的使用JDBC了。
最后,还有一个关于JDBC的知识点,前面说过Spring可以集成Mybatis,同样,Spring也可以集成JDBC!会在后面单独写一章。