zoukankan      html  css  js  c++  java
  • 第十三章.MySQL数据库与JDBC编程(下)

    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) + "	"
    32                             + rs.getString(2) + "	"
    33                             + rs.getString(3) + "	"
    34                             + rs.getString(4));
    35                     }
    36                 }
    37     }
    38 }
    View Code

     执行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 
     8 public class ExecuteDDL{
     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 createTable(String sql) throws Exception{
    24         //加载驱动
    25         Class.forName(driver);
    26         try(
    27             //获取数据库连接
    28             Connection conn = DriverManager.getConnection(url, user, pass);
    29             //使用Connection来创建一个Statement对象
    30             Statement stmt = conn.createStatement()){
    31                 //执行DDL语句,创建数据表
    32                 stmt.executeUpdate(sql);
    33             }
    34     }
    35     
    36     public static void main(String[] args) throws Exception{
    37         ExecuteDDL ed = new ExecuteDDL();
    38         ed.initParam("mysql.ini");
    39         ed.createTable("create table jdbc_test "
    40             + "( jdbc_id int auto_increment primary key, "
    41             + "jdbc_name varchar(255), "
    42             + "jdbc_desc text);");
    43             System.out.println("------建表成功------");
    44     }
    45 }
    View Code

      使用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 }
    View Code

      使用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) + "	");
    48                                 }
    49                                 System.out.print("
    ");
    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 }
    View Code

      从结果看,执行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 }
    View Code

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

    登录界面:

    登录成功界面:

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

    登录界面:

    登录失败界面:

      从结果中可以看到,把用户中的' 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 }
    View Code

       管理结果集:

        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) + "	"
    39                     + rs.getString(2) + "	" + 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 }
    View Code

        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 }
    View Code

      使用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 }
    View Code

        虽然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                     + "	" + jdbcRs.getString(2)
    47                     + "	" + 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 }
    View Code

    上面程序使用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                 + "	" + rs.getString(2)
    55                 + "	" + 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 }
    View Code

      从上面程序可以看到在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                 + "	" + rs.getString(2)
    56                 + "	" + rs.getString(3));
    57         }
    58     }
    59 }
    View Code

    程序中要查询第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 }
    View Code

     上面代码报错会因为插入语句第四条有错。正是因为这条语句出错,导致产生异常,且该异常没有得到处理,引起程序非正常结束,所以事务自动回滚,上面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();
    View Code

        若在批量更新的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);
    View Code

        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) + "	");
    67         }
    68         System.out.print("
    ");
    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) + "	");
    75             }
    76             System.out.print("
    ");
    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 }
    View Code

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

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

        除了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);
    View Code

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

        建议把上面程序中的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);
    View Code

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

        Connection conn = ds.getConnection();

  • 相关阅读:
    C语言I博客作业03
    C语言I博客作业06
    C语言I博客作业01
    C语言I博客作业04
    C语言I博客作业05
    How kNN algorithm works(kNN算法原理讲解)
    《机器学习笔记》环境配置(Windows64位)
    git 命令使用
    关于推荐系统中的冷启动问题探讨(Approaching the Cold Start Problem in Recommender Systems)
    WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping。
  • 原文地址:https://www.cnblogs.com/lanshanxiao/p/7372778.html
Copyright © 2011-2022 走看看