第十三章.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();

原文地址:https://www.cnblogs.com/lanshanxiao/p/7372778.html