JDBC的典型用法:

  JDBC4.2常用接口和类简介:

    DriverManager:用于管理JDBC驱动的服务类,程序中使用该类的主要功能是获取Connection对象,该类包含如下方法:

      public static synchronized Connection getConnection(String url, String user, String  pass) throws SQLException:该方法获得url对应数据库的连接

    Connection:代表数据库连接对象,每个Connection代表一个物理连接会话。想要访问数据库,必须先获得数据库连接,该接口的常用方法如下:

      1.Statement createStatement() throws SQLException:该方法返回一个Statement对象。

      2.PreparedStatement prepareStatement(String sql) throws SQLException:该方法返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译。

      3.CallableStatement prepareCall(String sql) throws SQLException:该方法返回CallableStatement对象,该对象用于调用存储过程

    上面三个方法都返回用于执行SQL语句的Statement对象,PreparedStatement、CallableStatement是Statement的子类,只有获得了Statement之后才可执行SQL语句。

    除此之外,Connection还有如下几个用于控制事务的方法:

      1.Savepoint setSavepoint():创建一个保存点

      2.Savepoint setSavepoint(String name):以指定名字创建一个保存点

      3.void setTransactionIsolation(int level):设置事务的隔离级别

      4.void rollback():回滚事务

      5.void rollback(Savepoint savepoint):将事务回滚到指定的保存点

      6.void setAutoCommit(boolean autoCommit):关闭自动提交、打开事务

      7.void commit():提交事务

    Java7 为Connection新增了

      setSchema(String schema)、getSchema()两个方法:用于控制该Connection访问的数据库Schema

      setNetworkTimeout(Executor executor, int milliseconds)、getNetworkTimeout()两个方法:用于控制数据库连接超时行为。

    Statement:用于执行SQL语句的工具接口,常用方法如下:

      1.ResultSet executeQuery(String sql) throws SQLException:该方法用于执行查询语句,并返回查询结果对应的ResultSet对象。该方法只能用于执行查询语句

      2.int executeUpdate(String sql) throws SQLException:该方法用于执行DML(数据操作语言)语句,并返回受影响的行数;该方法也可用于执行DDL(数据定义

       语言)语句执行DDL语句将返回0

      3.boolean execute(String sql) throws SQLException:该方法可执行任何SQL语句。若执行后第一个结果为ResultSet对象,则返回true;若执行后第一个结果为受影

       响的行数或没有任何结果,则返回false。

    Java7为Statement新增了closeOnCompletion()方法:若Statement执行了该方法,则当所有依赖于该Statement的ResultSet关闭时,该Statement会自动关闭。

    Java7还为Statement提供了isCloseOnCompletion()方法:用于判断该Statement是否打开了“closeOnCompletion”.

    Java8为Statement新增了多个重载的executeLargeUpdate()方法:这些方法相当于增强版的executeUpdate()方法,返回值类型为long,即当DML语句影响的记录条数超过

     Integer.MAX_VALUE时,就应该使用executeLargeUpdate()方法。

    PreparedStatement:预编译的Statement对象。PreparedStatement是Statement的子接口,它允许数据库预编译SQL语句(这些SQL语句通常带有参数),以后每次只

     改变SQL命令的参数,避免数据库每次都需要编译SQL语句,因此性能更好。相对于Statement而言,使用PreparedStatement执行SQL语句时,无需再传入SQL语句,只

     要为预编译的SQL语句传入参数数值即可。所以它比Statement多了如下方法:

      1.void setXxx(int parameterIndex, Xxx value):该方法根据传入参数值的类型不同,需要使用不同的方法。传入的值根据索引传给SQL语句中指定位置的参数。

  ResultSet:结果集对象。该对象包含访问查询结果的方法,ResultSet可以通过列索引或列名获得列数据。它包含了如下常用方法来移动记录指针:

    1.void close():释放ResultSet对象。

    2.boolean absolute(int row):将结果集的记录指针移动到第row行,若row是负数,则移动到倒数第row行。若移动后的记录指针指向一条有效记录,则该方法返回true

    3.void beforeFirst():将ResultSet的记录指针定位到首行之前,这是ResultSet结果集记录指针的初始状态——记录指针的起始位置位于第一行之前

    4.boolean first():将ResultSet的记录指针定位到首行。若移动后的记录指针指向一条有效记录,则该方法返回true。

    5.boolean previous():将ResultSet的记录指针定位到上一行。若移动后的记录指针指向一条有效记录,则该方法返回true。

    6.boolean next():将ResultSet的记录指针定位到下一行,若移动后的记录指针指向一条有效记录,则该方法返回true。

    7.boolean last():将ResultSet的记录指针定位到最后一行,若移动后的记录指针指向一条有效记录,则该方法返回true。

    8.void afterLast():将ResultSet的记录指针定位到最后一行之后。

JDBC编程步骤:

  1.加载数据库驱动:

    通常使用Class类的forName()静态方法来加载驱动:

      Class.forName(driverClass);//driverClass就是数据库驱动类所对应的字符串。如:加载MySQL的驱动代码

      Class.forName("com.mysql.jdbc.Driver");

  2.通过DriverManager获取数据库连接:

    //获取数据库连接

    DriverManager.getConnection(String url, String user, String pass);//数据库URL、登录数据库的用户名和密码。

    数据库URL通常遵循如下写法:

    jdbc:subprotocol:other stuff

    jdbc是固定的写法,subprotocol指定连接到特定数据库的驱动,other stuff也不是固定的(由数据库定)

    MySQL数据库的URL写法如下:

    jdbc:mysql://hostname:port/databasename

  3.通过Connection对象创建Statement对象。有如下三个方法:

    1.createStatement():创建基本的Statement对象

    2.prepareStatement(String sql):根据传入的SQL语句创建预编译的Statement对象

    3.prepateCall(String sql):根据传入的SQL语句创建CllableStatement对象

  4.使用Statement执行SQL语句。有如下三个方法:

    1.execute():可执行任何SQL语句,但比较麻烦

    2.executeUpdate():主要用于执行DML和DDL语句。执行DML语句返回受SQL语句影响的行数,执行DDL语句返回0

    3.executeQuery():只能执行查询语句,执行后返回代表查询结果的ResultSet对象

  5.操作结果集:

    若执行SQL语句是查询语句,则执行结果将返回一个ResultSet对象,该对象里保存了SQL语句查询的结果。程序可通过操作该ResultSet对象来取出查询结果:

      1.next()、previous()、first()、last()、beforeFirst()、afterLast()、absolute()等移动记录指针的方法

      2.getXxx()方法获取记录指针指向行、特定列的值。该方法既可使用列索引作为参数,也可使用列名作为参数。使用列索引作为参数性能更好,使用列名作为参数可

       读性更好

  6.回收数据库资源:

    包括关闭ResultSet、Statement和Connection等资源。

  下面程序简单示范了JDBC编程,并通过ResultSet获得结果集的过程:

  1. drop database if exists select_test;
  2. create database select_test;
  3. use select_test;
  4. # 为了保证从表参照的主表存在,通常应该先建主表。
  5. create table teacher_table
  6. (
  7. # auto_increment:实际上代表所有数据库的自动编号策略,通常用作数据表的逻辑主键。
  8. teacher_id int auto_increment,
  9. teacher_name varchar(255),
  10. primary key(teacher_id)
  11. );
  12. create table student_table
  13. (
  14. # 为本表建立主键约束
  15. student_id int auto_increment primary key,
  16. student_name varchar(255),
  17. # 指定java_teacher参照到teacher_table的teacher_id列
  18. java_teacher int,
  19. foreign key(java_teacher) references teacher_table(teacher_id)
  20. );
  21. insert into teacher_table
  22. values
  23. (null , 'Yeeku');
  24. insert into teacher_table
  25. values
  26. (null , 'Leegang');
  27. insert into teacher_table
  28. values
  29. (null , 'Martine');
  30. insert into student_table
  31. values
  32. (null , '张三' , 1);
  33. insert into student_table
  34. values
  35. (null , '张三' , 1);
  36. insert into student_table
  37. values
  38. (null , '李四' , 1);
  39. insert into student_table
  40. values
  41. (null , '王五' , 2);
  42. insert into student_table
  43. values
  44. (null , '_王五' , 2);
  45.  
  46. insert into student_table
  47. values
  48. (null , null , 2);
  49. insert into student_table
  50. values
  51. (null , '赵六' , null);

数据库建表语句

将驱动(mysql-connector-java-5.1.30-bin.jar)放到java目录的下的jre/lib/ext/目录下面。或者将驱动的路径添加到classpath环境变量后面。

  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.Statement;
  4. import java.sql.ResultSet;
  5.  
  6. public class ConnMySql{
  7. public static void main(String[] args) throws Exception{
  8. //1.加载驱动,使用反射知识,现在记住这么写
  9. Class.forName("com.mysql.jdbc.Driver");
  10. try(
  11. //2.使用DriverManager获取数据库连接
  12. //其中返回的Connection就代表了Java程序和数据库的连接
  13. //不同数据库的URL写法需要查询驱动文档,用户名、密码由DBA分配
  14. Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/select_test", "root", "123456");
  15. //3.使用Connection来创建一个Statement对象
  16. Statement stmt = conn.createStatement();
  17. //4.执行SQL语句
  18. /*
  19. Statement有三种执行SQL语句的方法:
  20. 1.execute()可执行任何SQL语句-返回一个boolean值,若执行后第一个结果是ResultSet,则返回true,否则返回false
  21. 2.executeQuery()执行select语句-返回查询到的结果集
  22. 3.executeUpdate()用于执行DML语句-返回一个整数,代表被SQL语句影响的记录条数
  23. */
  24. ResultSet rs = stmt.executeQuery("select s.*, teacher_name"
  25. + " from student_table s , teacher_table t"
  26. + " where t.teacher_id = s.java_teacher")){
  27. //ResultSet有一系列的getXxx(列索引 | 列名)方法,用于获取记录指针
  28. //指向行、特定列的值,不断地使用next()将记录指针下移一行
  29. //若移动之后记录指针依然指向有效行,则next()方法返回true
  30. while(rs.next()){
  31. System.out.println(rs.getInt(1) + "\t"
  32. + rs.getString(2) + "\t"
  33. + rs.getString(3) + "\t"
  34. + rs.getString(4));
  35. }
  36. }
  37. }
  38. }

执行SQL语句的方式:

  使用Java8新增的executeLargeUpdate()方法执行DDL和DML语句:

  使用statement执行DDL和DML语句的步骤与执行普通查询语句的步骤基本相似,区别在于执行了DDL语句后返回值为0,执行了DML语句后返回值为受到影响的记录条数。

  MySQL暂不支持executeLargeUpdate()方法。所以我们使用executeUpdate()方法。

  下面的程序并没有直接把数据库连接信息写在代码中,而是使用一个mysql.ini文件(就是properties文件)来保存数据库连接信息,这是比较成熟的做法——当需要把应用程

   序从开发环境移植到生产环境时,无需修改源代码,只需要修改mysql.ini配置文件即可:

mysql.ini文件内容:

mysql.ini和ExecuteDDL.java文件所放位置:

  1. import java.io.FileInputStream;
  2. import java.util.Properties;
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.Statement;
  6.  
  7. public class ExecuteDDL{
  8. private String driver;
  9. private String url;
  10. private String user;
  11. private String pass;
  12. public void initParam(String paramFile) throws Exception{
  13. //使用Properties类来加载属性文件
  14. Properties props = new Properties();
  15. props.load(new FileInputStream(paramFile));
  16. driver = props.getProperty("driver");
  17. url = props.getProperty("url");
  18. user = props.getProperty("user");
  19. pass = props.getProperty("pass");
  20. }
  21.  
  22. public void createTable(String sql) throws Exception{
  23. //加载驱动
  24. Class.forName(driver);
  25. try(
  26. //获取数据库连接
  27. Connection conn = DriverManager.getConnection(url, user, pass);
  28. //使用Connection来创建一个Statement对象
  29. Statement stmt = conn.createStatement()){
  30. //执行DDL语句,创建数据表
  31. stmt.executeUpdate(sql);
  32. }
  33. }
  34.  
  35. public static void main(String[] args) throws Exception{
  36. ExecuteDDL ed = new ExecuteDDL();
  37. ed.initParam("mysql.ini");
  38. ed.createTable("create table jdbc_test "
  39. + "( jdbc_id int auto_increment primary key, "
  40. + "jdbc_name varchar(255), "
  41. + "jdbc_desc text);");
  42. System.out.println("------建表成功------");
  43. }
  44. }

  使用executeUpdate()方法执行DML语句:

    和上面程序的步骤是一样的,只不过程序代码需要修改:

  1. import java.io.FileInputStream;
  2. import java.util.Properties;
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.Statement;
  6.  
  7. public class ExecuteDML{
  8. private String driver;
  9. private String url;
  10. private String user;
  11. private String pass;
  12.  
  13. public void initParam(String paramFile) throws Exception{
  14. Properties props = new Properties();
  15. props.load(new FileInputStream(paramFile));
  16. driver = props.getProperty("driver");
  17. url = props.getProperty("url");
  18. user = props.getProperty("user");
  19. pass = props.getProperty("pass");
  20. }
  21.  
  22. public int insertData(String sql) throws Exception{
  23. //加载驱动
  24. Class.forName(driver);
  25. try(
  26. //获取数据库连接
  27. Connection conn = DriverManager.getConnection(url, user, pass);
  28. //使用Connection来创建一个Statement对象
  29. Statement stmt = conn.createStatement()){
  30. //执行SQL语句,返回受影响的记录条数
  31. return stmt.executeUpdate(sql);
  32. }
  33. }
  34.  
  35. public static void main(String[] args) throws Exception{
  36. ExecuteDML ed = new ExecuteDML();
  37. ed.initParam("mysql.ini");
  38. int result = ed.insertData("insert into jdbc_test(jdbc_name,jdbc_desc)"
  39. + "select s.student_name , t.teacher_name "
  40. + "from student_table s , teacher_table t "
  41. + "where s.java_teacher = t.teacher_id;");
  42. System.out.println("------系统中一共有" + result + "条记录受影响------");
  43. }
  44. }

  使用execute()方法执行SQL语句:

    Statement的execute()方法几乎可以执行任何SQL语句,但它执行SQL语句比较麻烦,通常没有必要使用execute()方法来执行SQL语句。

    使用execute()方法执行SQL语句的返回值只是boolean值,它表明执行该SQL语句是否返回了ResultSet对象,Statement提供了如下两个方法来获取执行结果:

      1.getResultSet():获取该Statement执行查询语句所返回的ResultSet对象

      2.getUpdateCount():获取该Statement执行DML语句所影响的记录行数。

    下面程序示范了使用Statement的execute()方法来执行任意的SQL语句:

  1. import java.util.Properties;
  2. import java.io.FileInputStream;
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.Statement;
  6. import java.sql.ResultSet;
  7. import java.sql.ResultSetMetaData;
  8.  
  9. public class ExecuteSQL{
  10. private String driver;
  11. private String url;
  12. private String user;
  13. private String pass;
  14.  
  15. public void initParam(String paramFile) throws Exception{
  16. //使用Properties类来加载属性文件
  17. Properties props = new Properties();
  18. props.load(new FileInputStream(paramFile));
  19. driver = props.getProperty("driver");
  20. url = props.getProperty("url");
  21. user = props.getProperty("user");
  22. pass = props.getProperty("pass");
  23. }
  24.  
  25. public void executeSql(String sql) throws Exception{
  26. //加载数据库驱动
  27. Class.forName(driver);
  28. try(
  29. //获取数据库连接
  30. Connection conn = DriverManager.getConnection(url, user, pass);
  31. //通过Connection创建一个Statement
  32. Statement stmt = conn.createStatement()){
  33. //执行SQL语句,返回boolean值表示是否包含ResultSet
  34. boolean hasResultSet = stmt.execute(sql);
  35. //若执行后有ResultSet结果集
  36. if(hasResultSet){
  37. try(
  38. //获取结果集
  39. ResultSet rs = stmt.getResultSet()){
  40. //ResultSetMetaData是用于分析结果集的元数据接口
  41. ResultSetMetaData rsmd = rs.getMetaData();
  42. int columnCount = rsmd.getColumnCount();
  43. //迭代输出ResultSet对象
  44. while(rs.next()){
  45. //依次输出每列的值
  46. for(int i = 0; i < columnCount; i++){
  47. System.out.print(rs.getString(i + 1) + "\t");
  48. }
  49. System.out.print("\n");
  50. }
  51. }
  52. }else{
  53. System.out.println("该SQL语句影响的记录有" + stmt.getUpdateCount() + "条");
  54. }
  55. }
  56. }
  57.  
  58. public static void main(String[] args) throws Exception{
  59. ExecuteSQL es = new ExecuteSQL();
  60. es.initParam("mysql.ini");
  61. System.out.println("------执行删除表的DDL语句------");
  62. es.executeSql("drop table if exists my_test");
  63. System.out.print("------执行建表的DDL语句------");
  64. es.executeSql("create table my_test"
  65. + "(test_id int auto_increment primary key, "
  66. + "test_name varchar(255))");
  67. System.out.println("------执行插入数据的DML语句------");
  68. es.executeSql("insert into my_test(test_name) "
  69. + "select student_name from student_table");
  70. System.out.println("------执行查询数据的查询语句------");
  71. es.executeSql("select * from my_test");
  72. }
  73. }

  从结果看,执行DDL语句显示受影响记录条数;执行DML显示插入、修改、删除的记录条数;执行查询语句可以输出查询结果。

  上面程序获得的SQL执行结果是没有根据各列的数据类型调用相应的getXxx()方法,而是直接使用getString()方法来取得值,这是可以的。

  ResultSet的getString()方法几乎可以获取除了Blob之外的任意类型列的值,因为所有的数据类型都可以自动转换成字符串类型。

  使用PreparedStatement执行SQL语句:

    若经常要反复执行一条结构相似的SQL语句,如下两条:

    insert into student_table values(null, '张三', 1);

    insert into student_table values(null, '李四', 2);

    对于这两条语句,它们结构相似,只是执行插入时插入的值不同而已。对于这种情况,可以使用占位符(?)参数的SQL语句来代替它:

    insert into student_table values(null, ?, ?);

    JDBC提供了PreparedStatement接口,它是Statement的子接口。它可以进行预编译SQL语句,预编译后的SQL语句被存储在PreparedStatement对象中,然后可以使用该

     对象多次高效地执行该语句。使用PreparedStatement比使用Statement的效率要高。

    创建PreparedStatement对象使用Connection的prepareStatement()方法,该方法需要传入一个SQL字符串,该SQL字符串可以包括占位符参数,如下:

      //创建一个PreparedStatement对象

      pstmt = conn.prepareStatement("insert into student_table values(null,?,1)");

    PreparedStatement也提供了execute()、executeUpdate()、executeQuery()三个方法来执行SQL语句,不过这三个方法无需参数,因为PreparedStatement已经存储了预

     编译的SQL语句。

    使用PreparedStatement预编译SQL语句时,该SQL语句可以带占位符参数,因此在执行SQL语句之前必须为这些参数传入参数值,PreparedStatement提供了一系列

     的setXxx(int index, Xxx value)方法来传入参数值。

下面程序示范了使用Statement和PreparedStatement分别插入100条记录的对比。:

  1. import java.io.FileInputStream;
  2. import java.util.Properties;
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.PreparedStatement;
  6. import java.sql.Statement;
  7.  
  8. public class PreparedStatementTest{
  9. private String driver;
  10. private String url;
  11. private String user;
  12. private String pass;
  13.  
  14. public void initParam(String paramFile) throws Exception{
  15. //使用Properties类加载属性文件
  16. Properties props = new Properties();
  17. props.load(new FileInputStream(paramFile));
  18. driver = props.getProperty("driver");
  19. url = props.getProperty("url");
  20. user = props.getProperty("user");
  21. pass = props.getProperty("pass");
  22. //加载驱动
  23. Class.forName(driver);
  24. }
  25.  
  26. public void insertUseStatement() throws Exception{
  27. long start = System.currentTimeMillis();
  28. try(
  29. //获取数据库连接
  30. Connection conn = DriverManager.getConnection(url, user, pass);
  31. //使用Connection来创建一个Statement对象
  32. Statement stmt = conn.createStatement())
  33. {
  34. //需要使用100条SQL语句来插入100条记录
  35. for(int i = 0; i < 100; i++){
  36. stmt.executeUpdate("insert into student_table values("
  37. + "null,'姓名" + i + "', 1)");
  38. }
  39. System.out.println("使用Statement费时:"
  40. + (System.currentTimeMillis() - start));
  41. }
  42. }
  43.  
  44. public void insertUsePrepare() throws Exception{
  45. long start = System.currentTimeMillis();
  46. try(
  47. //获取数据库连接
  48. Connection conn = DriverManager.getConnection(url, user, pass);
  49. //使用Connection来创建一个PreparedStatement对象
  50. PreparedStatement pstmt = conn.prepareStatement("insert into student_table values(null,?,1)"))
  51. {
  52. //100次为PreparedStatement的参数设值,就可以插入100条记录
  53. for(int i = 0; i < 100; i++){
  54. pstmt.setString(1, "姓名" + i);
  55. pstmt.executeUpdate();
  56. }
  57. System.out.println("使用PreparedStatement费时:" + (System.currentTimeMillis() - start));
  58. }
  59. }
  60.  
  61. public static void main(String[] args) throws Exception{
  62. PreparedStatementTest pt = new PreparedStatementTest();
  63. pt.initParam("mysql.ini");
  64. pt.insertUseStatement();
  65. pt.insertUsePrepare();
  66. }
  67. }

  从上面的结果看,PreparedStatement耗时少于Statement。

  使用PreparedStatement还有一个很好的作用——用于防止SQL注入。

下面以一个简单的登录窗口为例来介绍这种SQL注入的结果:

  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.Statement;
  4. import java.sql.ResultSet;
  5. import java.util.Properties;
  6. import java.io.FileInputStream;
  7. import java.awt.*;
  8. import javax.swing.*;
  9.  
  10. public class LoginFrame{
  11. private final String PROP_FILE = "mysql.ini";
  12. private String driver;
  13. //url是数据库的服务地址
  14. private String url;
  15. private String user;
  16. private String pass;
  17. //登录界面的GUI组件
  18. private JFrame jf = new JFrame("登录");
  19. private JTextField userField = new JTextField(20);
  20. private JTextField passField = new JTextField(20);
  21. private JButton loginButton = new JButton("登录");
  22.  
  23. public void init() throws Exception{
  24. Properties connProp = new Properties();
  25. connProp.load(new FileInputStream(PROP_FILE));
  26. driver = connProp.getProperty("driver");
  27. url = connProp.getProperty("url");
  28. user = connProp.getProperty("user");
  29. pass = connProp.getProperty("pass");
  30. //加载驱动
  31. Class.forName(driver);
  32. //为登录按钮添加事件监听器
  33. loginButton.addActionListener(e -> {
  34. //登录成功则显示“登录成功”
  35. if(validate(userField.getText(), passField.getText())){
  36. JOptionPane.showMessageDialog(jf, "登录成功");
  37. }else{
  38. //否则显示“登录失败”
  39. JOptionPane.showMessageDialog(jf, "登录失败");
  40. }
  41. });
  42. jf.add(userField, BorderLayout.NORTH);
  43. jf.add(passField);
  44. jf.add(loginButton, BorderLayout.SOUTH);
  45. jf.pack();
  46. jf.setVisible(true);
  47. }
  48.  
  49. private boolean validate(String userName, String userPass){
  50. //执行查询的SQL语句
  51. String sql = "select * from jdbc_test "
  52. + "where jdbc_name='" + userName
  53. + "' and jdbc_desc='" + userPass + "';";
  54. System.out.println(sql);
  55. try(
  56. Connection conn = DriverManager.getConnection(url, user, pass);
  57. Statement stmt = conn.createStatement();
  58. ResultSet rs = stmt.executeQuery(sql))
  59. {
  60. //若查询的ResultSet里有超过一条的记录,则登录成功
  61. if(rs.next()){
  62. return true;
  63. }
  64. }catch(Exception e){
  65. e.printStackTrace();
  66. }
  67.  
  68. return false;
  69. }
  70.  
  71. public static void main(String[] args) throws Exception{
  72. new LoginFrame().init();
  73. }
  74. }

登录界面:

登录成功界面:

去数据库中查询是否存在用户和密码,执行的SQL语句。从上面的结果可以看出,我们在登录用户名中输入‘ or true or ’时,竟然也登录成功了。原因就出在执行的SQL语句上。

只要密码和用户为空,但是where后的条件永远为真,这就告诉软件,数据库中存在该用户,可以登录。

  把上面的validate()方法换成使用PreparedStatement来执行验证,而不是直接使用Statement:

  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.PreparedStatement;
  4. import java.sql.ResultSet;
  5. import java.util.Properties;
  6. import java.io.FileInputStream;
  7. import java.awt.*;
  8. import javax.swing.*;
  9.  
  10. public class LoginFrame{
  11. private final String PROP_FILE = "mysql.ini";
  12. private String driver;
  13. //url是数据库的服务地址
  14. private String url;
  15. private String user;
  16. private String pass;
  17. //登录界面的GUI组件
  18. private JFrame jf = new JFrame("登录");
  19. private JTextField userField = new JTextField(20);
  20. private JTextField passField = new JTextField(20);
  21. private JButton loginButton = new JButton("登录");
  22.  
  23. public void init() throws Exception{
  24. Properties connProp = new Properties();
  25. connProp.load(new FileInputStream(PROP_FILE));
  26. driver = connProp.getProperty("driver");
  27. url = connProp.getProperty("url");
  28. user = connProp.getProperty("user");
  29. pass = connProp.getProperty("pass");
  30. //加载驱动
  31. Class.forName(driver);
  32. //为登录按钮添加事件监听器
  33. loginButton.addActionListener(e -> {
  34. //登录成功则显示“登录成功”
  35. if(validate(userField.getText(), passField.getText())){
  36. JOptionPane.showMessageDialog(jf, "登录成功");
  37. }else{
  38. //否则显示“登录失败”
  39. JOptionPane.showMessageDialog(jf, "登录失败");
  40. }
  41. });
  42. jf.add(userField, BorderLayout.NORTH);
  43. jf.add(passField);
  44. jf.add(loginButton, BorderLayout.SOUTH);
  45. jf.pack();
  46. jf.setVisible(true);
  47. }
  48.  
  49. private boolean validate(String userName, String userPass){
  50. //执行查询的SQL语句
  51. String sql = "select * from jdbc_test "
  52. + "where jdbc_name='" + userName
  53. + "' and jdbc_desc='" + userPass + "';";
  54. System.out.println(sql);
  55. try(
  56. Connection conn = DriverManager.getConnection(url, user, pass);
  57. PreparedStatement pstmt = conn.prepareStatement("select * from jdbc_test where jdbc_name=? and jdbc_desc=?;"))
  58. {
  59. pstmt.setString(1, userName);
  60. pstmt.setString(2, userPass);
  61. try(
  62. ResultSet rs = pstmt.executeQuery())
  63. {
  64. //若查询的ResultSet里有超过一条的记录,则登录成功
  65. if(rs.next()){
  66. return true;
  67. }
  68. }
  69. }catch(Exception e){
  70. e.printStackTrace();
  71. }
  72.  
  73. return false;
  74. }
  75.  
  76. public static void main(String[] args) throws Exception{
  77. new LoginFrame().init();
  78. }
  79. }

登录界面:

登录失败界面:

  从结果中可以看到,把用户中的' or true or '添加到了jdbc_name的后面,避免的SQL注入。

  使用PreparedStatement比使用Statement多了如下三个好处:

    1.PreparedStatement预编译SQL语句,性能更好

    2.PreparedStatement无需“拼接”SQL语句,编程更简单

    3.PreparedStatement可以防止SQL注入,安全性更好

  基于上面三点,通常推荐避免使用Statement来执行SQL语句,改为使用PreparedStatement执行SQL语句。

  使用PreparedStatement执行带占位符参数的SQL语句时,SQL语句中的占位符参数只能代替普通值,不能代替表名、列名等数据库对象,也不能代替的insert、select等关键字

  使用CallableStatement调用存储过程:

    

  进入一个数据库中,执行上面的命令。delimiter //将MySQL的语句结束符改为双斜线(\\),这样就可以在创建存储过程中使用分号作为分隔符(MySQL默认使用分号作为语

   句结束符)。记得执行完上面命令再将结束符改为分号。上面命令创建了名为add_pro的存储过程,该存储过程包含三个参数:a b是传入参数,sum使用out修饰,是传出

   参数

  调用存储过程使用CallableStatement,可通过Connection的prepareCall()方法来创建CallableStatement对象,创建该对象时需要传入调用存储过程的SQL语句。

  调用存储过程的SQL语句格式:{call 过程名(?, ?, ..., ?)}若下所示:

    //使用Connection来创建一个CallableStatement对象

    cstmt = conn.prepareCall("{call add_pro(?, ?, ?)"});

  存储过程有传入参数,也有传出参数。Java程序必须为这些参数传入值,可通过CallableStatement的setXxx()方法为传入参数设置值;传出参数就是Java程序可以通过该参数

   获取存储过程里的值,CallableStatement需要调用registerOutParameter()方法来注册该参数。如下所示:

    //注册CallableStatement的第三个参数是int类型

    cstmt.registerOutParameter(3, Types.INTEGER);

  经过上面步骤,就可以调用CallableStatement的execute()方法来执行存储过程了,执行结束后通过CallableStatement对象的getXxx(int index)方法来获取指定传出参数的值。

  1. import java.util.Properties;
  2. import java.io.FileInputStream;
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.CallableStatement;
  6. import java.sql.Types;
  7.  
  8. public class CallableStatementTest{
  9. private String driver;
  10. private String url;
  11. private String user;
  12. private String pass;
  13.  
  14. public void initParam(String paramFile) throws Exception{
  15. //使用Properties类来加载属性文件
  16. Properties props = new Properties();
  17. props.load(new FileInputStream(paramFile));
  18. driver = props.getProperty("driver");
  19. url = props.getProperty("url");
  20. user = props.getProperty("user");
  21. pass = props.getProperty("pass");
  22. }
  23.  
  24. public void callProcedure()throws Exception{
  25. //加载驱动
  26. Class.forName(driver);
  27. try(
  28. //获取数据库连接
  29. Connection conn = DriverManager.getConnection(url, user, pass);
  30. //使用Connection来创建一个CallableStatement对象
  31. CallableStatement cstmt = conn.prepareCall("{call add_pro(?,?,?)}")
  32. ){
  33. cstmt.setInt(1, 4);
  34. cstmt.setInt(2, 5);
  35. //注册CallableStatement的第三个参数时int类型
  36. cstmt.registerOutParameter(3, Types.INTEGER);
  37. //执行存储过程
  38. cstmt.execute();
  39. //获取并输出存储过程传出的参数的值
  40. System.out.println("执行结果是:" + cstmt.getInt(3));
  41. }
  42. }
  43.  
  44. public static void main(String[] args) throws Exception{
  45. CallableStatementTest ct = new CallableStatementTest();
  46. ct.initParam("mysql.ini");
  47. ct.callProcedure();
  48. }
  49. }

  管理结果集:

    JDBC使用ResultSet来封装执行查询得到的查询结果,后通过移动ResultSet的记录指针来取出结果集内容。除此之外,JDBC还允许ResultSet来更新记录,并提供

     ResultSetMetaData来获取ResultSet对象的相关信息

    可滚动、可更新的结果集:

      使用absolute()、previous()、afterLast()等方法自由移动记录指针的ResultSet被称为可滚动的结果集。

      以默认方式打开的ResultSet是不可更新的,若希望创建可更新的ResultSet,则必须在创建Statement或PreparedStatement时传入额外的参数。

      Connection在创建Statement或PreparedStatement时可额外传入如下两个参数:

       1.resultSetType:控制ResultSet的类型,该参数可以取如下三个值:

        1.ResultSet.TYPE_FORWARD_ONLY:该常量控制记录指针只能向前移动。

        2.ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制记录指针可以自由移动(可滚动结果集),但底层数据的改变不会影响ResultSet的内容

        3.ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可自由移动(可滚动结果集),而且底层数据的改变会影响ResultSet的内容

        TYPE_SCROLL_INSENSITIVE、TYPE_SCROLL_SENSITIVE两个常量的作用需要底层数据库驱动的支持,对于有些数据库驱动来说,这两个并没有太大的区别

       2.resultSetConcurrency:控制ResultSet并发类型,该参数可以接收如下两个值:

        1.ResultSet.CONCUR_READ_ONLY:该常量指示ResultSet是只读的并发模式(默认)。

        2.ResultSet.CONCUR_UPDATABLE:该常量指示ResultSet是可更新的并发模式。

      下面代码通过这两个参数创建了一个PreparedStatement对象,由该对象生成的ResultSet对象将是可滚动、可更新的结果集:

      //使用Connection创建一个PreparedStatement对象

      //传入控制结果集可滚动、可更新的参数:

      pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

      需要指出的是,可更新的结果集还需要满足如下两个条件:

        1.所有数据都应该来自一个表

        2.选出的数据集必须包含主键列

      可调用ResultSet的updateXxx(intcolumnIndex, Xxx value)方法来修改记录指针所指记录、特定列的值,最后调用ResultSet的updateRow()方法来提交修改。

  下面程序示范了这种创建可滚动、可更新的结果集的方法:

  1. import java.util.Properties;
  2. import java.io.FileInputStream;
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.PreparedStatement;
  6. import java.sql.ResultSet;
  7.  
  8. public class ResultSetTest{
  9. private String driver;
  10. private String url;
  11. private String user;
  12. private String pass;
  13. public void initParam(String paramFile) throws Exception{
  14. //使用Properties类加载属性文件
  15. Properties props = new Properties();
  16. props.load(new FileInputStream(paramFile));
  17. driver = props.getProperty("driver");
  18. url = props.getProperty("url");
  19. user = props.getProperty("user");
  20. pass = props.getProperty("pass");
  21. }
  22.  
  23. public void query(String sql) throws Exception{
  24. //加载驱动
  25. Class.forName(driver);
  26. try(
  27. //获取数据库连接
  28. Connection conn = DriverManager.getConnection(url, user, pass);
  29. //使用Connection来创建一个PreparedStatement对象
  30. //传入控制结果集可滚动、可更新的参数
  31. PreparedStatement pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
  32. ResultSet rs = pstmt.executeQuery()
  33. ){
  34. rs.last();
  35. int rowCount = rs.getRow();
  36. for(int i = rowCount; i > 0; i--){
  37. rs.absolute(i);
  38. System.out.println(rs.getString(1) + "\t"
  39. + rs.getString(2) + "\t" + rs.getString(3));
  40. //修改记录指针所指记录、第2列的值
  41. rs.updateString(2, "学生名" + i);
  42. //提交修改
  43. rs.updateRow();
  44. }
  45. }
  46. }
  47.  
  48. public static void main(String[] args) throws Exception{
  49. ResultSetTest rt = new ResultSetTest();
  50. rt.initParam("mysql.ini");
  51. rt.query("select * from student_table");
  52. }
  53. }

    student_table表中记录被倒序输出,且当程序运行结束后,student_table表中所有记录的student_name列的值都被修改。

    若要创建可更新的结果集,则使用查询语句查询的数据通常只能来自于一个数据表,而且查询结果集中的数据列必须包含主键列,否则会引起更新失败。

  处理Blob类型数据:

    Blob(Binary Long Object):是二进制长对象,Blob列常用于存储大文件,典型的Blob内容是一张图片或一个声音文件,由于它们的特殊性,必须使用特殊的方式来存储

    使用Blob列可以把图片、声音等文件的二进制数据保存在数据库中,并可以从数据库中恢复指定文件。

    若需要将图片插入数据库,显然不能直接通过普通的SQL语句来完成,因为有一个关键问题——Blob常量无法表示。所以将Blob数据插入数据库需要使用

     PreparedStatement,该对象有一个方法:setBinaryStream(int parameterIndex, InputStream x),该方法可以为指定参数传入二进制输入流,从而可以实现将Blob数据保存

     到数据库的功能。

    需要从ResultSet里取出Blob数据时,可以调用ResultSet的getBlob(int columnIndex)方法,该方法将返回一个Blob对象,Blob对象提供了getBinaryStream()方法来获取该

     Blob数据的输入流,也可以使用Blob对象提供的getBytes()方法直接取出该Blob对象封装的二进制数据。

    为了把图片放入数据库,使用如下SQL语句建立一个数据表:

    img_data mediumblob;创建一个mediumblob类型的数据列,用于保存图片数据

    mediumblob类型可存储16M内容,blob类型可存储64KB内容。

下面程序可以实现图片“上传”——实际上就是将图片保存到数据库,并在右边的列表框中显示图片的名字,当用户双击列表框中的图片名时,左边窗口将显示该图片——实质就是根据选中的ID从数据库里查找图片,并将其显示出来:

  1. import java.sql.*;
  2. import javax.swing.*;
  3. import java.awt.*;
  4. import java.awt.event.*;
  5. import java.util.Properties;
  6. import java.util.ArrayList;
  7. import java.io.*;
  8. import javax.swing.filechooser.FileFilter;
  9.  
  10. public class BlobTest
  11. {
  12. JFrame jf = new JFrame("图片管理程序");
  13. private static Connection conn;
  14. private static PreparedStatement insert;
  15. private static PreparedStatement query;
  16. private static PreparedStatement queryAll;
  17. // 定义一个DefaultListModel对象
  18. private DefaultListModel<ImageHolder> imageModel
  19. = new DefaultListModel<>();
  20. private JList<ImageHolder> imageList = new JList<>(imageModel);
  21. private JTextField filePath = new JTextField(26);
  22. private JButton browserBn = new JButton("...");
  23. private JButton uploadBn = new JButton("上传");
  24. private JLabel imageLabel = new JLabel();
  25. // 以当前路径创建文件选择器
  26. JFileChooser chooser = new JFileChooser(".");
  27. // 创建文件过滤器
  28. ExtensionFileFilter filter = new ExtensionFileFilter();
  29. static
  30. {
  31. try
  32. {
  33. Properties props = new Properties();
  34. props.load(new FileInputStream("mysql.ini"));
  35. String driver = props.getProperty("driver");
  36. String url = props.getProperty("url");
  37. String user = props.getProperty("user");
  38. String pass = props.getProperty("pass");
  39. Class.forName(driver);
  40. // 获取数据库连接
  41. conn = DriverManager.getConnection(url , user , pass);
  42. // 创建执行插入的PreparedStatement对象,
  43. // 该对象执行插入后可以返回自动生成的主键
  44. insert = conn.prepareStatement("insert into img_table"
  45. + " values(null,?,?)" , Statement.RETURN_GENERATED_KEYS);
  46. // 创建两个PreparedStatement对象,用于查询指定图片,查询所有图片
  47. query = conn.prepareStatement("select img_data from img_table"
  48. + " where img_id=?");
  49. queryAll = conn.prepareStatement("select img_id, "
  50. + " img_name from img_table");
  51. }
  52. catch (Exception e)
  53. {
  54. e.printStackTrace();
  55. }
  56. }
  57. public void init()throws SQLException
  58. {
  59. // -------初始化文件选择器--------
  60. filter.addExtension("jpg");
  61. filter.addExtension("jpeg");
  62. filter.addExtension("gif");
  63. filter.addExtension("png");
  64. filter.setDescription("图片文件(*.jpg,*.jpeg,*.gif,*.png)");
  65. chooser.addChoosableFileFilter(filter);
  66. // 禁止“文件类型”下拉列表中显示“所有文件”选项。
  67. chooser.setAcceptAllFileFilterUsed(false);
  68. // ---------初始化程序界面---------
  69. fillListModel();
  70. filePath.setEditable(false);
  71. // 只能单选
  72. imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  73. JPanel jp = new JPanel();
  74. jp.add(filePath);
  75. jp.add(browserBn);
  76. browserBn.addActionListener(event -> {
  77. // 显示文件对话框
  78. int result = chooser.showDialog(jf , "浏览图片文件上传");
  79. // 如果用户选择了APPROVE(赞同)按钮,即打开,保存等效按钮
  80. if(result == JFileChooser.APPROVE_OPTION)
  81. {
  82. filePath.setText(chooser.getSelectedFile().getPath());
  83. }
  84. });
  85. jp.add(uploadBn);
  86. uploadBn.addActionListener(avt -> {
  87. // 如果上传文件的文本框有内容
  88. if (filePath.getText().trim().length() > 0)
  89. {
  90. // 将指定文件保存到数据库
  91. upload(filePath.getText());
  92. // 清空文本框内容
  93. filePath.setText("");
  94. }
  95. });
  96. JPanel left = new JPanel();
  97. left.setLayout(new BorderLayout());
  98. left.add(new JScrollPane(imageLabel) , BorderLayout.CENTER);
  99. left.add(jp , BorderLayout.SOUTH);
  100. jf.add(left);
  101. imageList.setFixedCellWidth(160);
  102. jf.add(new JScrollPane(imageList) , BorderLayout.EAST);
  103. imageList.addMouseListener(new MouseAdapter()
  104. {
  105. public void mouseClicked(MouseEvent e)
  106. {
  107. // 如果鼠标双击
  108. if (e.getClickCount() >= 2)
  109. {
  110. // 取出选中的List项
  111. ImageHolder cur = (ImageHolder)imageList.
  112. getSelectedValue();
  113. try
  114. {
  115. // 显示选中项对应的Image
  116. showImage(cur.getId());
  117. }
  118. catch (SQLException sqle)
  119. {
  120. sqle.printStackTrace();
  121. }
  122. }
  123. }
  124. });
  125. jf.setSize(620, 400);
  126. jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  127. jf.setVisible(true);
  128. }
  129. // ----------查找img_table填充ListModel----------
  130. public void fillListModel()throws SQLException
  131. {
  132.  
  133. try(
  134. // 执行查询
  135. ResultSet rs = queryAll.executeQuery())
  136. {
  137. // 先清除所有元素
  138. imageModel.clear();
  139. // 把查询的全部记录添加到ListModel中
  140. while (rs.next())
  141. {
  142. imageModel.addElement(new ImageHolder(rs.getInt(1)
  143. ,rs.getString(2)));
  144. }
  145. }
  146. }
  147. // ---------将指定图片放入数据库---------
  148. public void upload(String fileName)
  149. {
  150. // 截取文件名
  151. String imageName = fileName.substring(fileName.lastIndexOf('\\')
  152. + 1 , fileName.lastIndexOf('.'));
  153. File f = new File(fileName);
  154. try(
  155. InputStream is = new FileInputStream(f))
  156. {
  157. // 设置图片名参数
  158. insert.setString(1, imageName);
  159. // 设置二进制流参数
  160. insert.setBinaryStream(2, is , (int)f.length());
  161. int affect = insert.executeUpdate();
  162. if (affect == 1)
  163. {
  164. // 重新更新ListModel,将会让JList显示最新的图片列表
  165. fillListModel();
  166. }
  167. }
  168. catch (Exception e)
  169. {
  170. e.printStackTrace();
  171. }
  172. }
  173. // ---------根据图片ID来显示图片----------
  174. public void showImage(int id)throws SQLException
  175. {
  176. // 设置参数
  177. query.setInt(1, id);
  178. try(
  179. // 执行查询
  180. ResultSet rs = query.executeQuery())
  181. {
  182. if (rs.next())
  183. {
  184. // 取出Blob列
  185. Blob imgBlob = rs.getBlob(1);
  186. // 取出Blob列里的数据
  187. ImageIcon icon=new ImageIcon(imgBlob.getBytes(1L
  188. ,(int)imgBlob.length()));
  189. imageLabel.setIcon(icon);
  190. }
  191. }
  192. }
  193. public static void main(String[] args)throws SQLException
  194. {
  195. new BlobTest().init();
  196. }
  197. }
  198. // 创建FileFilter的子类,用以实现文件过滤功能
  199. class ExtensionFileFilter extends FileFilter
  200. {
  201. private String description = "";
  202. private ArrayList<String> extensions = new ArrayList<>();
  203. // 自定义方法,用于添加文件扩展名
  204. public void addExtension(String extension)
  205. {
  206. if (!extension.startsWith("."))
  207. {
  208. extension = "." + extension;
  209. extensions.add(extension.toLowerCase());
  210. }
  211. }
  212. // 用于设置该文件过滤器的描述文本
  213. public void setDescription(String aDescription)
  214. {
  215. description = aDescription;
  216. }
  217. // 继承FileFilter类必须实现的抽象方法,返回该文件过滤器的描述文本
  218. public String getDescription()
  219. {
  220. return description;
  221. }
  222. // 继承FileFilter类必须实现的抽象方法,判断该文件过滤器是否接受该文件
  223. public boolean accept(File f)
  224. {
  225. // 如果该文件是路径,接受该文件
  226. if (f.isDirectory()) return true;
  227. // 将文件名转为小写(全部转为小写后比较,用于忽略文件名大小写)
  228. String name = f.getName().toLowerCase();
  229. // 遍历所有可接受的扩展名,如果扩展名相同,该文件就可接受。
  230. for (String extension : extensions)
  231. {
  232. if (name.endsWith(extension))
  233. {
  234. return true;
  235. }
  236. }
  237. return false;
  238. }
  239. }
  240. // 创建一个ImageHolder类,用于封装图片名、图片ID
  241. class ImageHolder
  242. {
  243. // 封装图片的ID
  244. private int id;
  245. // 封装图片的图片名字
  246. private String name;
  247. public ImageHolder(){}
  248. public ImageHolder(int id , String name)
  249. {
  250. this.id = id;
  251. this.name = name;
  252. }
  253. // id的setter和getter方法
  254. public void setId(int id)
  255. {
  256. this.id = id;
  257. }
  258. public int getId()
  259. {
  260. return this.id;
  261. }
  262. // name的setter和getter方法
  263. public void setName(String name)
  264. {
  265. this.name = name;
  266. }
  267. public String getName()
  268. {
  269. return this.name;
  270. }
  271. // 重写toString方法,返回图片名
  272. public String toString()
  273. {
  274. return name;
  275. }
  276. }

  使用ResultSetMetaData分析结果集:

    当执行SQL查询后可以通过移动记录指针来遍历ResultSet的每条记录,但程序可能不清楚该ResultSet里包含哪些数据列,以及每个数据列的数据类型,那么可以通

     过ResultSetMetaData来获取关于ResultSet的描述信息:

    MetaData的意思是元数据,即描述其他数据的数据,因此ResultSetMetaData封装了描述ResultSet对象的数据;后面还要介绍的DatabaseMetaData则封装了描述

     Database的数据。

    ResultSet中包含了一个getMetaData()方法,该方法可以返回该ResultSet对应的ResultSetMetaData对象。一旦获得了ResultSetMetaData对象就可以通过

     ResultSetMetaData提供的大量方法来返回ResultSet的描述信息。常用方法有如下三个:

      1.int getColumnCount():返回该ResultSet的列数量

      2.String getColumnName(int Column):返回指定索引的列名

      3.int getColumnType(int column):返回指定索引的列类型

下面是一个简单的查询器,当用户在文本框内输入合法的查询语句并执行成功后,下面表格将会显示查询结果:

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import javax.swing.*;
  4. import javax.swing.table.*;
  5. import java.util.*;
  6. import java.io.*;
  7. import java.sql.*;
  8.  
  9. public class QueryExecutor
  10. {
  11. JFrame jf = new JFrame("查询执行器");
  12. private JScrollPane scrollPane;
  13. private JButton execBn = new JButton("查询");
  14. // 用于输入查询语句的文本框
  15. private JTextField sqlField = new JTextField(45);
  16. private static Connection conn;
  17. private static Statement stmt;
  18. // 采用静态初始化块来初始化Connection、Statement对象
  19. static
  20. {
  21. try
  22. {
  23. Properties props = new Properties();
  24. props.load(new FileInputStream("mysql.ini"));
  25. String drivers = props.getProperty("driver");
  26. String url = props.getProperty("url");
  27. String username = props.getProperty("user");
  28. String password = props.getProperty("pass");
  29. // 加载数据库驱动
  30. Class.forName(drivers);
  31. // 取得数据库连接
  32. conn = DriverManager.getConnection(url, username, password);
  33. stmt = conn.createStatement();
  34. }
  35. catch (Exception e)
  36. {
  37. e.printStackTrace();
  38. }
  39. }
  40. // --------初始化界面的方法---------
  41. public void init()
  42. {
  43. JPanel top = new JPanel();
  44. top.add(new JLabel("输入查询语句:"));
  45. top.add(sqlField);
  46. top.add(execBn);
  47. // 为执行按钮、单行文本框添加事件监听器
  48. execBn.addActionListener(new ExceListener());
  49. sqlField.addActionListener(new ExceListener());
  50. jf.add(top , BorderLayout.NORTH);
  51. jf.setSize(680, 480);
  52. jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  53. jf.setVisible(true);
  54. }
  55. // 定义监听器
  56. class ExceListener implements ActionListener
  57. {
  58. public void actionPerformed(ActionEvent evt)
  59. {
  60. // 删除原来的JTable(JTable使用scrollPane来包装)
  61. if (scrollPane != null)
  62. {
  63. jf.remove(scrollPane);
  64. }
  65. try(
  66. // 根据用户输入的SQL执行查询
  67. ResultSet rs = stmt.executeQuery(sqlField.getText()))
  68. {
  69. // 取出ResultSet的MetaData
  70. ResultSetMetaData rsmd = rs.getMetaData();
  71. Vector<String> columnNames = new Vector<>();
  72. Vector<Vector<String>> data = new Vector<>();
  73. // 把ResultSet的所有列名添加到Vector里
  74. for (int i = 0 ; i < rsmd.getColumnCount(); i++ )
  75. {
  76. columnNames.add(rsmd.getColumnName(i + 1));
  77. }
  78. // 把ResultSet的所有记录添加到Vector里
  79. while (rs.next())
  80. {
  81. Vector<String> v = new Vector<>();
  82. for (int i = 0 ; i < rsmd.getColumnCount(); i++ )
  83. {
  84. v.add(rs.getString(i + 1));
  85. }
  86. data.add(v);
  87. }
  88. // 创建新的JTable
  89. JTable table = new JTable(data , columnNames);
  90. scrollPane = new JScrollPane(table);
  91. // 添加新的Table
  92. jf.add(scrollPane);
  93. // 更新主窗口
  94. jf.validate();
  95. }
  96. catch (Exception e)
  97. {
  98. e.printStackTrace();
  99. }
  100. }
  101. }
  102. public static void main(String[] args)
  103. {
  104. new QueryExecutor().init();
  105. }
  106. }

    虽然ResultSetMetaData可以准确的分析出ResultSet里包含多少列,以及每列的列名、数据类型等,但使用ResultSetMetaData需要一定的系统开销,因此若在编程过程中

     已经知道ResultSet里包含多少列,以及每列的列名、类型等信息,就没有必要使用ResultSetMetaData来分析该ResultSet对象了。

  Java7的RowSet1.1:

    RowSet接口继承了ResultSet接口,RowSet接口下包含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet和WebRowSet常用子接口。除了JdbcRowSet需要保

     持与数据库连接之外,其余4个子接口都是离线的RowSet,无需保持与数据库的连接。

    与ResultSet相比,RowSet默认是可滚动、可更新、可序列化的结果集,而且作为JavaBean使用,因此能方便地在网络上传输,用于同步两端的数据。对于离线RowSet而

     言,程序在创建RowSet时已经把数据从底层数据库读取到内存,因此可以充分利用计算机内存,从而降低数据库服务器的负载,提高程序性能。

  Java7新增的RowSetFactory与RowSet:

    Java7新增了RowSetProvider类和RowSetFactory接口,其中RowSetProvider负责创建RowSetFactory,而RowSetFactory则提供了如下方法来创建RowSet实例:

      1.CachedRowSet createCachedRowSet():创建一个默认的CachedRowSet。

      2.FilteredRowSet createFilteredRowSet():创建一个默认的FilteredRowSet。

      3.JdbcRowSet createJdbcRowSet():创建一个默认的JdbcRowSet。

      4.JoinRowSet createJoinRowSet():创建一个默认的JoinRowSet。

      5.WebRowSet createWebRowSet():创建一个默认的WebRowSet。

    通过使用RowSetFactory,就可以把应用程序与RowSet实现类分离开,有利于后期的升级、扩展。

下面使用RowSetFactory来创建JdbcRowSet实例:

  1. import java.util.*;
  2. import java.io.*;
  3. import java.sql.*;
  4. import javax.sql.rowset.*;
  5.  
  6. public class RowSetFactoryTest
  7. {
  8. private String driver;
  9. private String url;
  10. private String user;
  11. private String pass;
  12. public void initParam(String paramFile)throws Exception
  13. {
  14. // 使用Properties类来加载属性文件
  15. Properties props = new Properties();
  16. props.load(new FileInputStream(paramFile));
  17. driver = props.getProperty("driver");
  18. url = props.getProperty("url");
  19. user = props.getProperty("user");
  20. pass = props.getProperty("pass");
  21. }
  22.  
  23. public void update(String sql)throws Exception
  24. {
  25. // 加载驱动
  26. Class.forName(driver);
  27. // 使用RowSetProvider创建RowSetFactory
  28. RowSetFactory factory = RowSetProvider.newFactory();
  29. try(
  30. // 使用RowSetFactory创建默认的JdbcRowSet实例
  31. JdbcRowSet jdbcRs = factory.createJdbcRowSet())
  32. {
  33. // 设置必要的连接信息
  34. jdbcRs.setUrl(url);
  35. jdbcRs.setUsername(user);
  36. jdbcRs.setPassword(pass);
  37. // 设置SQL查询语句
  38. jdbcRs.setCommand(sql);
  39. // 执行查询
  40. jdbcRs.execute();
  41. jdbcRs.afterLast();
  42. // 向前滚动结果集
  43. while (jdbcRs.previous())
  44. {
  45. System.out.println(jdbcRs.getString(1)
  46. + "\t" + jdbcRs.getString(2)
  47. + "\t" + jdbcRs.getString(3));
  48. if (jdbcRs.getInt("student_id") == 3)
  49. {
  50. // 修改指定记录行
  51. jdbcRs.updateString("student_name", "孙悟空");
  52. jdbcRs.updateRow();
  53. }
  54. }
  55. }
  56. }
  57. public static void main(String[] args)throws Exception
  58. {
  59. RowSetFactoryTest jt = new RowSetFactoryTest();
  60. jt.initParam("mysql.ini");
  61. jt.update("select * from student_table");
  62. }
  63. }

上面程序使用RowSetFactory来创建JdbcRowSet对象。由于通过这种方式创建的JdbcRowSet还没有传入Connection参数,因此程序还需调用setUrl()、setUsername()、setPassword()等方法来设置数据库连接信息。

  离线RowSet:

    在使用ResultSet的时代,程序查询得到ResultSet之后必须立即读取或处理它对应的记录,否则一旦关闭Connection,再通过ResultSet读取记录就会引发异常。

    离线RowSet会直接将底层数据读入内存中,封装成RowSet对象,而RowSet对象则完全可以当成JavaBean来使用,因此不仅安全,且编程十分简单。CachedRowSet是

     所有离线RowSet的父接口。

下面以CachedRowSet为例进行介绍:

  1. import java.util.*;
  2. import java.io.*;
  3. import java.sql.*;
  4. import javax.sql.*;
  5. import javax.sql.rowset.*;
  6.  
  7. public class CachedRowSetTest
  8. {
  9. private static String driver;
  10. private static String url;
  11. private static String user;
  12. private static String pass;
  13. public void initParam(String paramFile)throws Exception
  14. {
  15. // 使用Properties类来加载属性文件
  16. Properties props = new Properties();
  17. props.load(new FileInputStream(paramFile));
  18. driver = props.getProperty("driver");
  19. url = props.getProperty("url");
  20. user = props.getProperty("user");
  21. pass = props.getProperty("pass");
  22. }
  23.  
  24. public CachedRowSet query(String sql)throws Exception
  25. {
  26. // 加载驱动
  27. Class.forName(driver);
  28. // 获取数据库连接
  29. Connection conn = DriverManager.getConnection(url , user , pass);
  30. Statement stmt = conn.createStatement();
  31. ResultSet rs = stmt.executeQuery(sql);
  32. // 使用RowSetProvider创建RowSetFactory
  33. RowSetFactory factory = RowSetProvider.newFactory();
  34. // 创建默认的CachedRowSet实例
  35. CachedRowSet cachedRs = factory.createCachedRowSet();
  36. // 使用ResultSet装填RowSet
  37. cachedRs.populate(rs); // ①
  38. // 关闭资源
  39. rs.close();
  40. stmt.close();
  41. conn.close();
  42. return cachedRs;
  43. }
  44. public static void main(String[] args)throws Exception
  45. {
  46. CachedRowSetTest ct = new CachedRowSetTest();
  47. ct.initParam("mysql.ini");
  48. CachedRowSet rs = ct.query("select * from student_table");
  49. rs.afterLast();
  50. // 向前滚动结果集
  51. while (rs.previous())
  52. {
  53. System.out.println(rs.getString(1)
  54. + "\t" + rs.getString(2)
  55. + "\t" + rs.getString(3));
  56. if (rs.getInt("student_id") == 3)
  57. {
  58. // 修改指定记录行
  59. rs.updateString("student_name", "孙悟空");
  60. rs.updateRow();
  61. }
  62. }
  63. // 重新获取数据库连接
  64. Connection conn = DriverManager.getConnection(url
  65. , user , pass);
  66. conn.setAutoCommit(false);
  67. // 把对RowSet所做的修改同步到底层数据库
  68. rs.acceptChanges(conn);
  69. }
  70. }

  从上面程序可以看到在Connection关闭的情况下,程序依然可以读取、修改RowSet里的记录。为了将程序对离线RowSet所做的修改同步到底层数据库,程序在调用RowSet的

   acceptChanges()方法时,必须传入Connection。

  离线RowSet的查询分页:

    由于CachedRowSet会将数据记录直接装载到内存中,若SQL查询返回的记录过大,CachedRowSet将会占用大量内存,在某些极端情况下,将会导致内存溢出。

    未解决上述问题,CachedRowSet提供了分页功能。即一次只装载ResultSet里的某几条记录,这样就避免了CachedRowSet占用内存过大的问题。

    CachedRowSet提供了如下方法控制分页:

      1.populate(ResultSet rs, int startRow):使用给定的ResultSet装填RowSet,从ResultSet的第startRow条记录开始装填

      2.setPageSize(int pageSize):设置CachedRowSet每次返回多少条记录

      3.previousPage():在底层ResultSet可用的情况下,让CachedRowSet读取上一页记录。

      4.nextPage():在底层ResultSet可用的情况下,让CachedRowSet读取下一页记录

下面程序示范了CachedRowSet的分页支持:

  1. import java.util.*;
  2. import java.io.*;
  3. import java.sql.*;
  4. import javax.sql.*;
  5. import javax.sql.rowset.*;
  6.  
  7. public class CachedRowSetPage
  8. {
  9. private String driver;
  10. private String url;
  11. private String user;
  12. private String pass;
  13. public void initParam(String paramFile)throws Exception
  14. {
  15. // 使用Properties类来加载属性文件
  16. Properties props = new Properties();
  17. props.load(new FileInputStream(paramFile));
  18. driver = props.getProperty("driver");
  19. url = props.getProperty("url");
  20. user = props.getProperty("user");
  21. pass = props.getProperty("pass");
  22. }
  23.  
  24. public CachedRowSet query(String sql , int pageSize
  25. , int page)throws Exception
  26. {
  27. // 加载驱动
  28. Class.forName(driver);
  29. try(
  30. // 获取数据库连接
  31. Connection conn = DriverManager.getConnection(url , user , pass);
  32. Statement stmt = conn.createStatement();
  33. ResultSet rs = stmt.executeQuery(sql))
  34. {
  35. // 使用RowSetProvider创建RowSetFactory
  36. RowSetFactory factory = RowSetProvider.newFactory();
  37. // 创建默认的CachedRowSet实例
  38. CachedRowSet cachedRs = factory.createCachedRowSet();
  39. // 设置每页显示pageSize条记录
  40. cachedRs.setPageSize(pageSize);
  41. // 使用ResultSet装填RowSet,设置从第几条记录开始
  42. cachedRs.populate(rs , (page - 1) * pageSize + 1);
  43. return cachedRs;
  44. }
  45. }
  46. public static void main(String[] args)throws Exception
  47. {
  48. CachedRowSetPage cp = new CachedRowSetPage();
  49. cp.initParam("mysql.ini");
  50. CachedRowSet rs = cp.query("select * from student_table" , 3 , 2); // ①
  51. // 向后滚动结果集
  52. while (rs.next())
  53. {
  54. System.out.println(rs.getString(1)
  55. + "\t" + rs.getString(2)
  56. + "\t" + rs.getString(3));
  57. }
  58. }
  59. }

程序中要查询第2页的记录,每页显示3条记录。

事务处理:

  事物的概念和MySQL事务支持:

    事务是由一步或几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行。

    事务具备四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持续性(Durability)。简称ACID特性。

  JDBC的事务支持:

    JDBC连接的事务支持有Connection提供,Connection默认打开自动提交,即关闭事务。这种情况下,每一条SQL语句一旦执行,便会立即提交到数据库,永久生效,无法

     对其进行回滚操作。

    可调用Connection的setAutoCommit()方法来关闭自动提交,开启事务:

    //conn.setAutoCommit(false);

    等到所有SQL语句都被执行,程序可以调用Connection的commit()方法来提交事务:

    //conn.commit();

    若任意一条SQL语句执行失败,则应该用Connection的rollback()方法来回滚事务:

    //conn.rollback();

    实际上,当Connection遇到一个未处理的SQLException异常时,系统将会非正常退出,事务也会自动回滚。但若程序捕获了该异常,则需要在异常处理块中显式地回滚

     事务

  1. import java.sql.*;
  2. import java.io.*;
  3. import java.util.*;
  4.  
  5. public class TransactionTest
  6. {
  7. private String driver;
  8. private String url;
  9. private String user;
  10. private String pass;
  11. public void initParam(String paramFile)throws Exception
  12. {
  13. // 使用Properties类来加载属性文件
  14. Properties props = new Properties();
  15. props.load(new FileInputStream(paramFile));
  16. driver = props.getProperty("driver");
  17. url = props.getProperty("url");
  18. user = props.getProperty("user");
  19. pass = props.getProperty("pass");
  20. }
  21. public void insertInTransaction(String[] sqls) throws Exception
  22. {
  23. // 加载驱动
  24. Class.forName(driver);
  25. try(
  26. Connection conn = DriverManager.getConnection(url , user , pass))
  27. {
  28. // 关闭自动提交,开启事务
  29. conn.setAutoCommit(false);
  30. try(
  31. // 使用Connection来创建一个Statment对象
  32. Statement stmt = conn.createStatement())
  33. {
  34. // 循环多次执行SQL语句
  35. for (String sql : sqls)
  36. {
  37. stmt.executeUpdate(sql);
  38. }
  39. }
  40. // 提交事务
  41. conn.commit();
  42. }
  43. }
  44. public static void main(String[] args) throws Exception
  45. {
  46. TransactionTest tt = new TransactionTest();
  47. tt.initParam("mysql.ini");
  48. String[] sqls = new String[]{
  49. "insert into student_table values(null , 'aaa' ,1)",
  50. "insert into student_table values(null , 'bbb' ,1)",
  51. "insert into student_table values(null , 'ccc' ,1)",
  52. // 下面这条SQL语句将会违反外键约束,
  53. // 因为teacher_table中没有ID为5的记录。
  54. "insert into student_table values(null , 'ccc' ,5)" //①
  55. };
  56. tt.insertInTransaction(sqls);
  57. }
  58. }

上面代码报错会因为插入语句第四条有错。正是因为这条语句出错,导致产生异常,且该异常没有得到处理,引起程序非正常结束,所以事务自动回滚,上面3条插入语句无效。

    Connection也提供了设置中间点的方法:

      1.Savepoint setSavepoint():在当前事务中创建一个未命名的中间点,并返回代表该中间点的Savepoint对象

      2.Savepoint setSavepoint(String name):在当前事务中创建一个具有指定名称的中间点,并返回代表该中间点的SavepointSavepoint对象。

    通常来说设置中间点时,没有必要指定名称,因为Connection回滚到指定中间点时,并不是根据名字回滚的,而是根据中间点对象回滚的,Connection提供了

     rollback(Savepoint savepoint)方法回滚到指定中间点。

  Java8增强的批量更新:

    JDBC还提供了一个批量更新的功能,批量更新时,多条SQL语句将被作为一批操作被同时收集,并同时提交。

    批量更新必须得到底层数据库的支持,可以通过调用DatabaseMetaData的supportsBatchUpdates()方法来查看底层数据库是否支持批量更新。

    使用批量更新需要先创建一个Statement对象,然后利用该对象的addBatch()方法将多条SQL语句同时收集,最后调用Java8位Statement对象新增的executeLargeBatch()或

     原有的executeBatch()方法同时执行这些SQL语句。只要批量操作中任何一条SQL语句影响的记录条数可能超过Integer.MAX_VALUE,就应该使用executeLargeBatch()方

     法。如下:

  1. Statement stmt = conn.createStatement();
  2. //使用Statement同时收集多条SQL语句
  3. stmt.addBatch(sql1);
  4. stmt.addBatch(sql2);
  5. stmt.addBatch(sql3);
  6. ...
  7. //同时执行所有的SQL语句
  8. stmt.executeLargeBatch();

    若在批量更新的addBatch()方法中添加了select查询语句,程序将会直接出现错误。为了让批量操作可以正确的处理错误,必须把批量执行的操作视为单个事务,若批量更

     新在执行过程中失败,则让事务回滚到批量操作开始之前的状态。为达到这种效果,程序应该在开始批量操作之前先关闭自动提交,然后开始收集更新语句,当批量操作

     执行结束后,提交事务,并恢复之前的自动提交模式,如下:

  1. //保存当前的自动的提交模式
  2. boolean autoCommit = conn.getAutoCommit();
  3. //关闭自动提交
  4. conn.setAutoCommit(false);
  5. Statement stmt = conn.createStatement();
  6. //使用Statement同时收集多条SQL语句
  7. stmt.addBatch(sql1);
  8. stmt.addBatch(sql2);
  9. stmt.addBatch(sql3);
  10. ...
  11. //同时执行所有的SQL语句
  12. stmt.executeLargeBatch();
  13. //提交修改
  14. conn.commit();
  15. //恢复原有的紫东提交模式
  16. conn.setAutocommit(autoCommit);

    MySQL的最新驱动依然不支持executeLargeBatch()方法,对于数据库驱动不支持executeLargeBatch()的情形,则只能依然使用传统的executeBatch()方法。

分析数据库信息:

  使用DatabaseMetaData分析数据库信息:

    JDBC提供了DatabaseMetaData来封装数据库连接对应数据库的信息,通过Connection提供的getMetaData()方法就可以获取数据库对应的DatabaseMetaData对象

    DatabaseMetaData接口通常由驱动程序供应商提供实现,其目的是让用户了解底层数据库的相关信息。使用该接口的目的是发现如何处理底层数据库,尤其是对于试图与

     多个数据库一起使用的应用程序——因为应用程序需要在多个数据库之间切换,所以必须利用该接口来找出底层数据库的功能,如:调用supportsCorrelatedSubqueries

     ()方法查看是否可以使用关联子查询,或者调用supportsBatchUpdates()方法查看是否可以使用批量更新。

    许多DatabaseMetaData方法以ResultSet对象的形式返回查询信息,然后使用ResultSet的常规方法(如:getString()和getInt())即可从这些ResultSet对象中获取数据。若

     查询的信息不可用,则将返回一个空ResultSet对象。

    DatabaseMetaData的很多方法都需要传入一个XXXPattern模式字符串,这里的XXXPattern不是正则表达式,而是SQL里的模式字符串,即用%代表任意多个字符,使用下

     划线代表一个字符。在通常情况下,若把该模式字符串的参数值设置为null,即表明该参数不作为过滤条件。

    下面程序通过DatabaseMetaData分析了当前Connection连接对应数据库的一些基本信息,包括当前数据库包含多少数据表,存储过程,student_table表的数据列、主键、

     外键等信息:

  1. import java.sql.*;
  2. import java.util.*;
  3. import java.io.*;
  4.  
  5. public class DatabaseMetaDataTest
  6. {
  7. private String driver;
  8. private String url;
  9. private String user;
  10. private String pass;
  11. public void initParam(String paramFile)throws Exception
  12. {
  13. // 使用Properties类来加载属性文件
  14. Properties props = new Properties();
  15. props.load(new FileInputStream(paramFile));
  16. driver = props.getProperty("driver");
  17. url = props.getProperty("url");
  18. user = props.getProperty("user");
  19. pass = props.getProperty("pass");
  20. }
  21. public void info() throws Exception
  22. {
  23. // 加载驱动
  24. Class.forName(driver);
  25. try(
  26. // 获取数据库连接
  27. Connection conn = DriverManager.getConnection(url
  28. , user , pass))
  29. {
  30. // 获取的DatabaseMetaData对象
  31. DatabaseMetaData dbmd = conn.getMetaData();
  32. // 获取MySQL支持的所有表类型
  33. ResultSet rs = dbmd.getTableTypes();
  34. System.out.println("--MySQL支持的表类型信息--");
  35. printResultSet(rs);
  36. // 获取当前数据库的全部数据表
  37. rs = dbmd.getTables(null,null, "%" , new String[]{"TABLE"});
  38. System.out.println("--当前数据库里的数据表信息--");
  39. printResultSet(rs);
  40. // 获取student_table表的主键
  41. rs = dbmd.getPrimaryKeys(null , null, "student_table");
  42. System.out.println("--student_table表的主键信息--");
  43. printResultSet(rs);
  44. // 获取当前数据库的全部存储过程
  45. rs = dbmd.getProcedures(null , null, "%");
  46. System.out.println("--当前数据库里的存储过程信息--");
  47. printResultSet(rs);
  48. // 获取teacher_table表和student_table之间的外键约束
  49. rs = dbmd.getCrossReference(null,null, "teacher_table"
  50. , null, null, "student_table");
  51. System.out.println("--teacher_table表和student_table之间"
  52. + "的外键约束--");
  53. printResultSet(rs);
  54. // 获取student_table表的全部数据列
  55. rs = dbmd.getColumns(null, null, "student_table", "%");
  56. System.out.println("--student_table表的全部数据列--");
  57. printResultSet(rs);
  58. }
  59. }
  60. public void printResultSet(ResultSet rs)throws SQLException
  61. {
  62. ResultSetMetaData rsmd = rs.getMetaData();
  63. // 打印ResultSet的所有列标题
  64. for (int i = 0 ; i < rsmd.getColumnCount() ; i++ )
  65. {
  66. System.out.print(rsmd.getColumnName(i + 1) + "\t");
  67. }
  68. System.out.print("\n");
  69. // 打印ResultSet里的全部数据
  70. while (rs.next())
  71. {
  72. for (int i = 0; i < rsmd.getColumnCount() ; i++ )
  73. {
  74. System.out.print(rs.getString(i + 1) + "\t");
  75. }
  76. System.out.print("\n");
  77. }
  78. rs.close();
  79. }
  80. public static void main(String[] args)
  81. throws Exception
  82. {
  83. DatabaseMetaDataTest dt = new DatabaseMetaDataTest();
  84. dt.initParam("mysql.ini");
  85. dt.info();
  86. }
  87. }

结果太多,只截取一部分。

  使用系统表分析数据库信息:

    除了DatabaseMetaData来分析底层数据库信息之外,若已经确定应用程序所以用的数据库系统,则可以通过数据库的系统来分析数据库信息。

    系统表又称为数据字典,数据字典的数据通常由数据库系统负责维护,用户通常只能查询数据字典,而不能修改数据字典的内容。

    MySQL数据库使用information_schema数据库来保存系统表,在数据库里包含了大量系统表,常用系统表的简单介绍如下:

      1.tables:存放数据库里所有数据表信息

      2.schemata:存放数据库里所有数据库的信息

      3.views:存放数据库里所有视图的信息

      4.columns:存放数据库里所有列的信息

      5.triggers:存放数据库里所有触发器的信息

      6.routines:存放数据库里所有存储过程和函数的信息

      7.key_column_usage:存放数据库里所有具有约束的键信息

      8.table_constraints:存放数据库里全部约束表的信息

      9.statistics:存放数据库里全部索引的信息

使用连接池管理连接:

  数据库连接的建立和关闭是极耗费系统资源的操作,数据库连接池的解决方案是:当应用程序启动时,系统主动建立足够的数据库连接,并将这些连接组成一个连接池。每次应

   用程序请求数据库连接是,无需重新打开连接,而是从连接池中取出已有的连接使用,使用完后不再关闭数据库连接,而是直接将连接归还给连接池。

  对于共享资源的情况,有一个通用的设计模式:资源池(Resource Pool),用于解决资源的频繁请求、释放所造成的性能下降。

  数据库连接池是Connection对象的工厂,数据库连接池的常用参数如下:

    1.数据库的初始连接数

    2.连接池的最大连接数

    3.连接池的最小连接数

    4.连接池每次增加的容量

  JDBC的数据库连接池使用 javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由商用服务器提供实现,也有一些开源组织提供实现(如DBCP和C3P0)。

  DBCP数据源:

    DBCP是Apache软件基金组织下的开源连接实现,该连接池依赖该组织下的另一个开源系统:common-pool。若需要使用该连接池实现,则应在系统中增加两个jar 文件:

      1.commons-dbcp.jar:连接池的实现

      2.commons-pool.jar:连接池实现的依赖库

    Tomcat的连接池正是采用该连接池实现的。数据库连接池既可以与应用服务器整合使用,也可以由应用程序独立使用。

    下面代码片段示范了使用DBCP来获得数据库连接方式:

  1. //创建连接池实例
  2. BasicDataSource ds = new BasicDataSourc();
  3. //设置连接池所需驱动
  4. ds.setDriverClassName("com.mysql.jdbc.Driver");
  5. //设置连接数据库的URL
  6. ds.setUrl("jdbc:mysql://localhost:3306/javaee");
  7. //设置连接数据库的用户名
  8. ds.setUsername("root");
  9. //设置连接数据库的密码
  10. ds.setPassword("pass");
  11. //设置连接池的初始连接数
  12. ds.setInitialSize(5);
  13. //设置连接池最多可有多少个活动连接数
  14. ds.setMaxActive(20);
  15. //设置连接池中最少有2个空闲的连接
  16. ds.setMinIdle(2);

    数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。即:一个应用,上面代码只需要执行一次即可。

    建议把上面程序中的ds设置成static成员变量,并且在应用开始时立即初始化数据源对象,程序中所有需要获取数据库连接的地方直接访问该ds对象,并获取数据库连接即

     可。

    //通过数据源获取数据库连接

    Connection conn = ds.getConnection();

    当数据库访问结束后,程序还是像以前一样关闭数据库连接:

    //释放数据库连接

    conn.close();

  C3P0数据源:

    C3P0数据源性能更胜一筹,Hibernate就推荐使用该连接池。C3P0连接池不仅可以自动清理不在使用的Connection,还可以自动清理ResultSet和Statement。

    若需要使用C3P0连接池,则应在系统中增加如下JAR文件

      1.c3p0-0.9.1.2.jar:C3P0连接池的实现

    下面代码通过C3P0连接池获得数据库连接:

  1. //创建连接池实例
  2. ComboPooledDataSource ds = new ComboPooledDataSource();
  3. //设置连接池所需驱动
  4. ds.setDriverClass("com.mysql.jdbc.Driver");
  5. //设置连接数据库的URL
  6. ds.setJdbcUrl("jdbc:mysql://localhost:3306/javaee");
  7. //设置连接数据库的用户名
  8. ds.setUser("root");
  9. //设置连接数据库的密码
  10. ds.setPassword("pass");
  11. //设置连接池的最大连接数
  12. ds.setMaxPoolSize(40);
  13. //设置连接池的最小连接数
  14. ds.setMinPoolSIze(2);
  15. //设置连接池的初始连接数
  16. ds.setInitialPoolSize(10);
  17. //设置连接池的缓存Statement的最大数
  18. ds.setMaxStatements(180);

    通过如下代码获取数据库连接:

    Connection conn = ds.getConnection();

第十三章.MySQL数据库与JDBC编程(下)的更多相关文章

  1. MySql数据库与JDBC编程

    JDBC -- Java Database Connectivity,即Java数据库连接,通过使用JDBC就可以使用同一种API访问不同的数据库 SQL语句基础(SQL结构化查询语言) 能完成的任务 ...

  2. MySql数据库与JDBC编程二

    DML语法语句:主要操作数据表中的数据,完成插入新数据,修改已有数据,删除不要的数据的任务 1,insert into 语句 用于向指定表插入数据,一次只能插入一条记录:insert into tab ...

  3. MySql数据库与JDBC编程三

    多表连接查询(两种规范 SQL92和SQL99) SQL92规范: 等值连接,非等值连接,外连接,广义笛卡儿积连接 多个表都放在from后,,连接条件放在where后,条件要求两列值相等,则为等值连接 ...

  4. MySQL性能调优与架构设计——第9章 MySQL数据库Schema设计的性能优化

    第9章 MySQL数据库Schema设计的性能优化 前言: 很多人都认为性能是在通过编写代码(程序代码或者是数据库代码)的过程中优化出来的,其实这是一个非常大的误区.真正影响性能最大的部分是在设计中就 ...

  5. MySQL性能调优与架构设计——第8章 MySQL数据库Query的优化

    第8章 MySQL数据库Query的优化 前言: 在之前“影响 MySQL 应用系统性能的相关因素”一章中我们就已经分析过了Query语句对数据库性能的影响非常大,所以本章将专门针对 MySQL 的 ...

  6. MySQL性能调优与架构设计——第7章 MySQL数据库锁定机制

    第7章 MySQL数据库锁定机制 前言: 为了保证数据的一致完整性,任何一个数据库都存在锁定机制.锁定机制的优劣直接应想到一个数据库系统的并发处理能力和性能,所以锁定机制的实现也就成为了各种数据库的核 ...

  7. 基于Mysql数据库亿级数据下的分库分表方案

    移动互联网时代,海量的用户数据每天都在产生,基于用户使用数据的用户行为分析等这样的分析,都需要依靠数据都统计和分析,当数据量小时,问题没有暴露出来,数据库方面的优化显得不太重要,一旦数据量越来越大时, ...

  8. MySQL数据库的优化(下)MySQL数据库的高可用架构方案

    MySQL数据库的优化(下)MySQL数据库的高可用架构方案 2011-03-09 08:53 抚琴煮酒 51CTO 字号:T | T 在上一篇MySQL数据库的优化中,我们跟随笔者学习了单机MySQ ...

  9. “全栈2019”Java第五十三章:向上转型和向下转型详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

随机推荐

  1. python基础,变量,if语句

     一.python初识 python是一门 解释型弱类型编程语言. 特点: 简单.明确.优雅 二.python的解释器 CPython. 官方提供的. 内部使用c语言来实现 PyPy. 一次性把我们的 ...

  2. js调试中打印语句

    document.write(); console.log(); window.alert();

  3. css中的左右垂直居中的问题,可兼容各种版本浏览器的写法

    如题分为垂直居中,左右居中,先挑简单的记录. 一.左右居中 1.我刚开始写代码的时候,老师就直接告诉我一个简单的方法,那就是: width:500px; height:200px; margin:0 ...

  4. Easyui里面动态设置输入框的可见性

    JQuery EasyUI 动态隐藏   一.隐藏datagrid某一列 $('#dg').datagrid('hideColumn', 'field'); 二.隐藏html的lable.input标 ...

  5. jupyter notebook 的安装及使用

    推荐使用Python3 版本 安装pip3 版本 打开终端输入,安装jupyter notebook(ipython4之后命名) pip3 install jupyter notebook 启动jup ...

  6. windows本地搭建nginx+php+mysql+redis环境详细步骤

    1.mysql的下载和安装 这个可参考我另外一篇文章:http://www.cnblogs.com/myIvan/p/9265645.html 2.php的下载和配置修改 下载地址:https://w ...

  7. c++ Initialization

    c++ 的初始化过程比较复杂:根据对象的storage duration来分类.所谓storage duration是对象而言的,Storage duration is the property of ...

  8. IDEA通过Maven WebApp archetype 创建Spring boot项目骨架

    springboot项目资源: GitHub地址:https://github.com/TisFreedom/springbootdome.git 码云地址:https://gitee.com/Tis ...

  9. USART列子

    #include "stm32f10x.h" void USART_INit(void) { GPIO_InitTypeDef GPIO_Initstructe; USART_In ...

  10. ubuntu安装TFTP

    参考: http://wenku.baidu.com/view/76e70cd702d276a201292e2f.html?re=view http://wenku.baidu.com/view/ce ...