标签:图片 返回 adb one nts nsis 独立 地方 系统
JDBC的典型用法:
JDBC4.2常用接口和类简介:
DriverManager:用于管理JDBC驱动的服务类,程序中使用该类的主要功能是获取Connection对象,该类包含如下方法:
public static synchronized Connection getConnection(String url, String user, String pass) throws SQLException:该方法获得url对应数据库的连接
Connection:代表数据库连接对象,每个Connection代表一个物理连接会话。想要访问数据库,必须先获得数据库连接,该接口的常用方法如下:
1.Statement createStatement() throws SQLException:该方法返回一个Statement对象。
2.PreparedStatement prepareStatement(String sql) throws SQLException:该方法返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译。
3.CallableStatement prepareCall(String sql) throws SQLException:该方法返回CallableStatement对象,该对象用于调用存储过程
上面三个方法都返回用于执行SQL语句的Statement对象,PreparedStatement、CallableStatement是Statement的子类,只有获得了Statement之后才可执行SQL语句。
除此之外,Connection还有如下几个用于控制事务的方法:
1.Savepoint setSavepoint():创建一个保存点
2.Savepoint setSavepoint(String name):以指定名字创建一个保存点
3.void setTransactionIsolation(int level):设置事务的隔离级别
4.void rollback():回滚事务
5.void rollback(Savepoint savepoint):将事务回滚到指定的保存点
6.void setAutoCommit(boolean autoCommit):关闭自动提交、打开事务
7.void commit():提交事务
Java7 为Connection新增了
setSchema(String schema)、getSchema()两个方法:用于控制该Connection访问的数据库Schema
setNetworkTimeout(Executor executor, int milliseconds)、getNetworkTimeout()两个方法:用于控制数据库连接超时行为。
Statement:用于执行SQL语句的工具接口,常用方法如下:
1.ResultSet executeQuery(String sql) throws SQLException:该方法用于执行查询语句,并返回查询结果对应的ResultSet对象。该方法只能用于执行查询语句
2.int executeUpdate(String sql) throws SQLException:该方法用于执行DML(数据操作语言)语句,并返回受影响的行数;该方法也可用于执行DDL(数据定义
语言)语句执行DDL语句将返回0
3.boolean execute(String sql) throws SQLException:该方法可执行任何SQL语句。若执行后第一个结果为ResultSet对象,则返回true;若执行后第一个结果为受影
响的行数或没有任何结果,则返回false。
Java7为Statement新增了closeOnCompletion()方法:若Statement执行了该方法,则当所有依赖于该Statement的ResultSet关闭时,该Statement会自动关闭。
Java7还为Statement提供了isCloseOnCompletion()方法:用于判断该Statement是否打开了“closeOnCompletion”.
Java8为Statement新增了多个重载的executeLargeUpdate()方法:这些方法相当于增强版的executeUpdate()方法,返回值类型为long,即当DML语句影响的记录条数超过
Integer.MAX_VALUE时,就应该使用executeLargeUpdate()方法。
PreparedStatement:预编译的Statement对象。PreparedStatement是Statement的子接口,它允许数据库预编译SQL语句(这些SQL语句通常带有参数),以后每次只
改变SQL命令的参数,避免数据库每次都需要编译SQL语句,因此性能更好。相对于Statement而言,使用PreparedStatement执行SQL语句时,无需再传入SQL语句,只
要为预编译的SQL语句传入参数数值即可。所以它比Statement多了如下方法:
1.void setXxx(int parameterIndex, Xxx value):该方法根据传入参数值的类型不同,需要使用不同的方法。传入的值根据索引传给SQL语句中指定位置的参数。
ResultSet:结果集对象。该对象包含访问查询结果的方法,ResultSet可以通过列索引或列名获得列数据。它包含了如下常用方法来移动记录指针:
1.void close():释放ResultSet对象。
2.boolean absolute(int row):将结果集的记录指针移动到第row行,若row是负数,则移动到倒数第row行。若移动后的记录指针指向一条有效记录,则该方法返回true
3.void beforeFirst():将ResultSet的记录指针定位到首行之前,这是ResultSet结果集记录指针的初始状态——记录指针的起始位置位于第一行之前
4.boolean first():将ResultSet的记录指针定位到首行。若移动后的记录指针指向一条有效记录,则该方法返回true。
5.boolean previous():将ResultSet的记录指针定位到上一行。若移动后的记录指针指向一条有效记录,则该方法返回true。
6.boolean next():将ResultSet的记录指针定位到下一行,若移动后的记录指针指向一条有效记录,则该方法返回true。
7.boolean last():将ResultSet的记录指针定位到最后一行,若移动后的记录指针指向一条有效记录,则该方法返回true。
8.void afterLast():将ResultSet的记录指针定位到最后一行之后。
JDBC编程步骤:
1.加载数据库驱动:
通常使用Class类的forName()静态方法来加载驱动:
Class.forName(driverClass);//driverClass就是数据库驱动类所对应的字符串。如:加载MySQL的驱动代码
Class.forName("com.mysql.jdbc.Driver");
2.通过DriverManager获取数据库连接:
//获取数据库连接
DriverManager.getConnection(String url, String user, String pass);//数据库URL、登录数据库的用户名和密码。
数据库URL通常遵循如下写法:
jdbc:subprotocol:other stuff
jdbc是固定的写法,subprotocol指定连接到特定数据库的驱动,other stuff也不是固定的(由数据库定)
MySQL数据库的URL写法如下:
jdbc:mysql://hostname:port/databasename
3.通过Connection对象创建Statement对象。有如下三个方法:
1.createStatement():创建基本的Statement对象
2.prepareStatement(String sql):根据传入的SQL语句创建预编译的Statement对象
3.prepateCall(String sql):根据传入的SQL语句创建CllableStatement对象
4.使用Statement执行SQL语句。有如下三个方法:
1.execute():可执行任何SQL语句,但比较麻烦
2.executeUpdate():主要用于执行DML和DDL语句。执行DML语句返回受SQL语句影响的行数,执行DDL语句返回0
3.executeQuery():只能执行查询语句,执行后返回代表查询结果的ResultSet对象
5.操作结果集:
若执行SQL语句是查询语句,则执行结果将返回一个ResultSet对象,该对象里保存了SQL语句查询的结果。程序可通过操作该ResultSet对象来取出查询结果:
1.next()、previous()、first()、last()、beforeFirst()、afterLast()、absolute()等移动记录指针的方法
2.getXxx()方法获取记录指针指向行、特定列的值。该方法既可使用列索引作为参数,也可使用列名作为参数。使用列索引作为参数性能更好,使用列名作为参数可
读性更好
6.回收数据库资源:
包括关闭ResultSet、Statement和Connection等资源。
下面程序简单示范了JDBC编程,并通过ResultSet获得结果集的过程:
1 drop database if exists select_test; 2 create database select_test; 3 use select_test; 4 # 为了保证从表参照的主表存在,通常应该先建主表。 5 create table teacher_table 6 ( 7 # auto_increment:实际上代表所有数据库的自动编号策略,通常用作数据表的逻辑主键。 8 teacher_id int auto_increment, 9 teacher_name varchar(255), 10 primary key(teacher_id) 11 ); 12 create table student_table 13 ( 14 # 为本表建立主键约束 15 student_id int auto_increment primary key, 16 student_name varchar(255), 17 # 指定java_teacher参照到teacher_table的teacher_id列 18 java_teacher int, 19 foreign key(java_teacher) references teacher_table(teacher_id) 20 ); 21 insert into teacher_table 22 values 23 (null , ‘Yeeku‘); 24 insert into teacher_table 25 values 26 (null , ‘Leegang‘); 27 insert into teacher_table 28 values 29 (null , ‘Martine‘); 30 insert into student_table 31 values 32 (null , ‘张三‘ , 1); 33 insert into student_table 34 values 35 (null , ‘张三‘ , 1); 36 insert into student_table 37 values 38 (null , ‘李四‘ , 1); 39 insert into student_table 40 values 41 (null , ‘王五‘ , 2); 42 insert into student_table 43 values 44 (null , ‘_王五‘ , 2); 45 46 insert into student_table 47 values 48 (null , null , 2); 49 insert into student_table 50 values 51 (null , ‘赵六‘ , null);
将驱动(mysql-connector-java-5.1.30-bin.jar)放到java目录的下的jre/lib/ext/目录下面。或者将驱动的路径添加到classpath环境变量后面。
1 import java.sql.Connection; 2 import java.sql.DriverManager; 3 import java.sql.Statement; 4 import java.sql.ResultSet; 5 6 public class ConnMySql{ 7 public static void main(String[] args) throws Exception{ 8 //1.加载驱动,使用反射知识,现在记住这么写 9 Class.forName("com.mysql.jdbc.Driver"); 10 try( 11 //2.使用DriverManager获取数据库连接 12 //其中返回的Connection就代表了Java程序和数据库的连接 13 //不同数据库的URL写法需要查询驱动文档,用户名、密码由DBA分配 14 Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/select_test", "root", "123456"); 15 //3.使用Connection来创建一个Statement对象 16 Statement stmt = conn.createStatement(); 17 //4.执行SQL语句 18 /* 19 Statement有三种执行SQL语句的方法: 20 1.execute()可执行任何SQL语句-返回一个boolean值,若执行后第一个结果是ResultSet,则返回true,否则返回false 21 2.executeQuery()执行select语句-返回查询到的结果集 22 3.executeUpdate()用于执行DML语句-返回一个整数,代表被SQL语句影响的记录条数 23 */ 24 ResultSet rs = stmt.executeQuery("select s.*, teacher_name" 25 + " from student_table s , teacher_table t" 26 + " where t.teacher_id = s.java_teacher")){ 27 //ResultSet有一系列的getXxx(列索引 | 列名)方法,用于获取记录指针 28 //指向行、特定列的值,不断地使用next()将记录指针下移一行 29 //若移动之后记录指针依然指向有效行,则next()方法返回true 30 while(rs.next()){ 31 System.out.println(rs.getInt(1) + "\t" 32 + rs.getString(2) + "\t" 33 + rs.getString(3) + "\t" 34 + rs.getString(4)); 35 } 36 } 37 } 38 }
执行SQL语句的方式:
使用Java8新增的executeLargeUpdate()方法执行DDL和DML语句:
使用statement执行DDL和DML语句的步骤与执行普通查询语句的步骤基本相似,区别在于执行了DDL语句后返回值为0,执行了DML语句后返回值为受到影响的记录条数。
MySQL暂不支持executeLargeUpdate()方法。所以我们使用executeUpdate()方法。
下面的程序并没有直接把数据库连接信息写在代码中,而是使用一个mysql.ini文件(就是properties文件)来保存数据库连接信息,这是比较成熟的做法——当需要把应用程
序从开发环境移植到生产环境时,无需修改源代码,只需要修改mysql.ini配置文件即可:
mysql.ini文件内容:
mysql.ini和ExecuteDDL.java文件所放位置:
1 import java.io.FileInputStream; 2 import java.util.Properties; 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.Statement; 6 7 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 }
使用executeUpdate()方法执行DML语句:
和上面程序的步骤是一样的,只不过程序代码需要修改:
1 import java.io.FileInputStream; 2 import java.util.Properties; 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.Statement; 6 7 public class ExecuteDML{ 8 private String driver; 9 private String url; 10 private String user; 11 private String pass; 12 13 public void initParam(String paramFile) throws Exception{ 14 Properties props = new Properties(); 15 props.load(new FileInputStream(paramFile)); 16 driver = props.getProperty("driver"); 17 url = props.getProperty("url"); 18 user = props.getProperty("user"); 19 pass = props.getProperty("pass"); 20 } 21 22 public int insertData(String sql) throws Exception{ 23 //加载驱动 24 Class.forName(driver); 25 try( 26 //获取数据库连接 27 Connection conn = DriverManager.getConnection(url, user, pass); 28 //使用Connection来创建一个Statement对象 29 Statement stmt = conn.createStatement()){ 30 //执行SQL语句,返回受影响的记录条数 31 return stmt.executeUpdate(sql); 32 } 33 } 34 35 public static void main(String[] args) throws Exception{ 36 ExecuteDML ed = new ExecuteDML(); 37 ed.initParam("mysql.ini"); 38 int result = ed.insertData("insert into jdbc_test(jdbc_name,jdbc_desc)" 39 + "select s.student_name , t.teacher_name " 40 + "from student_table s , teacher_table t " 41 + "where s.java_teacher = t.teacher_id;"); 42 System.out.println("------系统中一共有" + result + "条记录受影响------"); 43 } 44 }
使用execute()方法执行SQL语句:
Statement的execute()方法几乎可以执行任何SQL语句,但它执行SQL语句比较麻烦,通常没有必要使用execute()方法来执行SQL语句。
使用execute()方法执行SQL语句的返回值只是boolean值,它表明执行该SQL语句是否返回了ResultSet对象,Statement提供了如下两个方法来获取执行结果:
1.getResultSet():获取该Statement执行查询语句所返回的ResultSet对象
2.getUpdateCount():获取该Statement执行DML语句所影响的记录行数。
下面程序示范了使用Statement的execute()方法来执行任意的SQL语句:
1 import java.util.Properties; 2 import java.io.FileInputStream; 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.Statement; 6 import java.sql.ResultSet; 7 import java.sql.ResultSetMetaData; 8 9 public class ExecuteSQL{ 10 private String driver; 11 private String url; 12 private String user; 13 private String pass; 14 15 public void initParam(String paramFile) throws Exception{ 16 //使用Properties类来加载属性文件 17 Properties props = new Properties(); 18 props.load(new FileInputStream(paramFile)); 19 driver = props.getProperty("driver"); 20 url = props.getProperty("url"); 21 user = props.getProperty("user"); 22 pass = props.getProperty("pass"); 23 } 24 25 public void executeSql(String sql) throws Exception{ 26 //加载数据库驱动 27 Class.forName(driver); 28 try( 29 //获取数据库连接 30 Connection conn = DriverManager.getConnection(url, user, pass); 31 //通过Connection创建一个Statement 32 Statement stmt = conn.createStatement()){ 33 //执行SQL语句,返回boolean值表示是否包含ResultSet 34 boolean hasResultSet = stmt.execute(sql); 35 //若执行后有ResultSet结果集 36 if(hasResultSet){ 37 try( 38 //获取结果集 39 ResultSet rs = stmt.getResultSet()){ 40 //ResultSetMetaData是用于分析结果集的元数据接口 41 ResultSetMetaData rsmd = rs.getMetaData(); 42 int columnCount = rsmd.getColumnCount(); 43 //迭代输出ResultSet对象 44 while(rs.next()){ 45 //依次输出每列的值 46 for(int i = 0; i < columnCount; i++){ 47 System.out.print(rs.getString(i + 1) + "\t"); 48 } 49 System.out.print("\n"); 50 } 51 } 52 }else{ 53 System.out.println("该SQL语句影响的记录有" + stmt.getUpdateCount() + "条"); 54 } 55 } 56 } 57 58 public static void main(String[] args) throws Exception{ 59 ExecuteSQL es = new ExecuteSQL(); 60 es.initParam("mysql.ini"); 61 System.out.println("------执行删除表的DDL语句------"); 62 es.executeSql("drop table if exists my_test"); 63 System.out.print("------执行建表的DDL语句------"); 64 es.executeSql("create table my_test" 65 + "(test_id int auto_increment primary key, " 66 + "test_name varchar(255))"); 67 System.out.println("------执行插入数据的DML语句------"); 68 es.executeSql("insert into my_test(test_name) " 69 + "select student_name from student_table"); 70 System.out.println("------执行查询数据的查询语句------"); 71 es.executeSql("select * from my_test"); 72 } 73 }
从结果看,执行DDL语句显示受影响记录条数;执行DML显示插入、修改、删除的记录条数;执行查询语句可以输出查询结果。
上面程序获得的SQL执行结果是没有根据各列的数据类型调用相应的getXxx()方法,而是直接使用getString()方法来取得值,这是可以的。
ResultSet的getString()方法几乎可以获取除了Blob之外的任意类型列的值,因为所有的数据类型都可以自动转换成字符串类型。
使用PreparedStatement执行SQL语句:
若经常要反复执行一条结构相似的SQL语句,如下两条:
insert into student_table values(null, ‘张三‘, 1);
insert into student_table values(null, ‘李四‘, 2);
对于这两条语句,它们结构相似,只是执行插入时插入的值不同而已。对于这种情况,可以使用占位符(?)参数的SQL语句来代替它:
insert into student_table values(null, ?, ?);
JDBC提供了PreparedStatement接口,它是Statement的子接口。它可以进行预编译SQL语句,预编译后的SQL语句被存储在PreparedStatement对象中,然后可以使用该
对象多次高效地执行该语句。使用PreparedStatement比使用Statement的效率要高。
创建PreparedStatement对象使用Connection的prepareStatement()方法,该方法需要传入一个SQL字符串,该SQL字符串可以包括占位符参数,如下:
//创建一个PreparedStatement对象
pstmt = conn.prepareStatement("insert into student_table values(null,?,1)");
PreparedStatement也提供了execute()、executeUpdate()、executeQuery()三个方法来执行SQL语句,不过这三个方法无需参数,因为PreparedStatement已经存储了预
编译的SQL语句。
使用PreparedStatement预编译SQL语句时,该SQL语句可以带占位符参数,因此在执行SQL语句之前必须为这些参数传入参数值,PreparedStatement提供了一系列
的setXxx(int index, Xxx value)方法来传入参数值。
下面程序示范了使用Statement和PreparedStatement分别插入100条记录的对比。:
1 import java.io.FileInputStream; 2 import java.util.Properties; 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.Statement; 7 8 public class PreparedStatementTest{ 9 private String driver; 10 private String url; 11 private String user; 12 private String pass; 13 14 public void initParam(String paramFile) throws Exception{ 15 //使用Properties类加载属性文件 16 Properties props = new Properties(); 17 props.load(new FileInputStream(paramFile)); 18 driver = props.getProperty("driver"); 19 url = props.getProperty("url"); 20 user = props.getProperty("user"); 21 pass = props.getProperty("pass"); 22 //加载驱动 23 Class.forName(driver); 24 } 25 26 public void insertUseStatement() throws Exception{ 27 long start = System.currentTimeMillis(); 28 try( 29 //获取数据库连接 30 Connection conn = DriverManager.getConnection(url, user, pass); 31 //使用Connection来创建一个Statement对象 32 Statement stmt = conn.createStatement()) 33 { 34 //需要使用100条SQL语句来插入100条记录 35 for(int i = 0; i < 100; i++){ 36 stmt.executeUpdate("insert into student_table values(" 37 + "null,‘姓名" + i + "‘, 1)"); 38 } 39 System.out.println("使用Statement费时:" 40 + (System.currentTimeMillis() - start)); 41 } 42 } 43 44 public void insertUsePrepare() throws Exception{ 45 long start = System.currentTimeMillis(); 46 try( 47 //获取数据库连接 48 Connection conn = DriverManager.getConnection(url, user, pass); 49 //使用Connection来创建一个PreparedStatement对象 50 PreparedStatement pstmt = conn.prepareStatement("insert into student_table values(null,?,1)")) 51 { 52 //100次为PreparedStatement的参数设值,就可以插入100条记录 53 for(int i = 0; i < 100; i++){ 54 pstmt.setString(1, "姓名" + i); 55 pstmt.executeUpdate(); 56 } 57 System.out.println("使用PreparedStatement费时:" + (System.currentTimeMillis() - start)); 58 } 59 } 60 61 public static void main(String[] args) throws Exception{ 62 PreparedStatementTest pt = new PreparedStatementTest(); 63 pt.initParam("mysql.ini"); 64 pt.insertUseStatement(); 65 pt.insertUsePrepare(); 66 } 67 }
从上面的结果看,PreparedStatement耗时少于Statement。
使用PreparedStatement还有一个很好的作用——用于防止SQL注入。
下面以一个简单的登录窗口为例来介绍这种SQL注入的结果:
1 import java.sql.Connection; 2 import java.sql.DriverManager; 3 import java.sql.Statement; 4 import java.sql.ResultSet; 5 import java.util.Properties; 6 import java.io.FileInputStream; 7 import java.awt.*; 8 import javax.swing.*; 9 10 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 }
登录界面:
登录成功界面:
去数据库中查询是否存在用户和密码,执行的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 }
登录界面:
登录失败界面:
从结果中可以看到,把用户中的‘ or true or ‘添加到了jdbc_name的后面,避免的SQL注入。
使用PreparedStatement比使用Statement多了如下三个好处:
1.PreparedStatement预编译SQL语句,性能更好
2.PreparedStatement无需“拼接”SQL语句,编程更简单
3.PreparedStatement可以防止SQL注入,安全性更好
基于上面三点,通常推荐避免使用Statement来执行SQL语句,改为使用PreparedStatement执行SQL语句。
使用PreparedStatement执行带占位符参数的SQL语句时,SQL语句中的占位符参数只能代替普通值,不能代替表名、列名等数据库对象,也不能代替的insert、select等关键字
使用CallableStatement调用存储过程:
进入一个数据库中,执行上面的命令。delimiter //将MySQL的语句结束符改为双斜线(\\),这样就可以在创建存储过程中使用分号作为分隔符(MySQL默认使用分号作为语
句结束符)。记得执行完上面命令再将结束符改为分号。上面命令创建了名为add_pro的存储过程,该存储过程包含三个参数:a b是传入参数,sum使用out修饰,是传出
参数
调用存储过程使用CallableStatement,可通过Connection的prepareCall()方法来创建CallableStatement对象,创建该对象时需要传入调用存储过程的SQL语句。
调用存储过程的SQL语句格式:{call 过程名(?, ?, ..., ?)}若下所示:
//使用Connection来创建一个CallableStatement对象
cstmt = conn.prepareCall("{call add_pro(?, ?, ?)"});
存储过程有传入参数,也有传出参数。Java程序必须为这些参数传入值,可通过CallableStatement的setXxx()方法为传入参数设置值;传出参数就是Java程序可以通过该参数
获取存储过程里的值,CallableStatement需要调用registerOutParameter()方法来注册该参数。如下所示:
//注册CallableStatement的第三个参数是int类型
cstmt.registerOutParameter(3, Types.INTEGER);
经过上面步骤,就可以调用CallableStatement的execute()方法来执行存储过程了,执行结束后通过CallableStatement对象的getXxx(int index)方法来获取指定传出参数的值。
1 import java.util.Properties; 2 import java.io.FileInputStream; 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.CallableStatement; 6 import java.sql.Types; 7 8 public class CallableStatementTest{ 9 private String driver; 10 private String url; 11 private String user; 12 private String pass; 13 14 public void initParam(String paramFile) throws Exception{ 15 //使用Properties类来加载属性文件 16 Properties props = new Properties(); 17 props.load(new FileInputStream(paramFile)); 18 driver = props.getProperty("driver"); 19 url = props.getProperty("url"); 20 user = props.getProperty("user"); 21 pass = props.getProperty("pass"); 22 } 23 24 public void callProcedure()throws Exception{ 25 //加载驱动 26 Class.forName(driver); 27 try( 28 //获取数据库连接 29 Connection conn = DriverManager.getConnection(url, user, pass); 30 //使用Connection来创建一个CallableStatement对象 31 CallableStatement cstmt = conn.prepareCall("{call add_pro(?,?,?)}") 32 ){ 33 cstmt.setInt(1, 4); 34 cstmt.setInt(2, 5); 35 //注册CallableStatement的第三个参数时int类型 36 cstmt.registerOutParameter(3, Types.INTEGER); 37 //执行存储过程 38 cstmt.execute(); 39 //获取并输出存储过程传出的参数的值 40 System.out.println("执行结果是:" + cstmt.getInt(3)); 41 } 42 } 43 44 public static void main(String[] args) throws Exception{ 45 CallableStatementTest ct = new CallableStatementTest(); 46 ct.initParam("mysql.ini"); 47 ct.callProcedure(); 48 } 49 }
管理结果集:
JDBC使用ResultSet来封装执行查询得到的查询结果,后通过移动ResultSet的记录指针来取出结果集内容。除此之外,JDBC还允许ResultSet来更新记录,并提供
ResultSetMetaData来获取ResultSet对象的相关信息
可滚动、可更新的结果集:
使用absolute()、previous()、afterLast()等方法自由移动记录指针的ResultSet被称为可滚动的结果集。
以默认方式打开的ResultSet是不可更新的,若希望创建可更新的ResultSet,则必须在创建Statement或PreparedStatement时传入额外的参数。
Connection在创建Statement或PreparedStatement时可额外传入如下两个参数:
1.resultSetType:控制ResultSet的类型,该参数可以取如下三个值:
1.ResultSet.TYPE_FORWARD_ONLY:该常量控制记录指针只能向前移动。
2.ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制记录指针可以自由移动(可滚动结果集),但底层数据的改变不会影响ResultSet的内容
3.ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可自由移动(可滚动结果集),而且底层数据的改变会影响ResultSet的内容
TYPE_SCROLL_INSENSITIVE、TYPE_SCROLL_SENSITIVE两个常量的作用需要底层数据库驱动的支持,对于有些数据库驱动来说,这两个并没有太大的区别
2.resultSetConcurrency:控制ResultSet并发类型,该参数可以接收如下两个值:
1.ResultSet.CONCUR_READ_ONLY:该常量指示ResultSet是只读的并发模式(默认)。
2.ResultSet.CONCUR_UPDATABLE:该常量指示ResultSet是可更新的并发模式。
下面代码通过这两个参数创建了一个PreparedStatement对象,由该对象生成的ResultSet对象将是可滚动、可更新的结果集:
//使用Connection创建一个PreparedStatement对象
//传入控制结果集可滚动、可更新的参数:
pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
需要指出的是,可更新的结果集还需要满足如下两个条件:
1.所有数据都应该来自一个表
2.选出的数据集必须包含主键列
可调用ResultSet的updateXxx(intcolumnIndex, Xxx value)方法来修改记录指针所指记录、特定列的值,最后调用ResultSet的updateRow()方法来提交修改。
下面程序示范了这种创建可滚动、可更新的结果集的方法:
1 import java.util.Properties; 2 import java.io.FileInputStream; 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 8 public class ResultSetTest{ 9 private String driver; 10 private String url; 11 private String user; 12 private String pass; 13 public void initParam(String paramFile) throws Exception{ 14 //使用Properties类加载属性文件 15 Properties props = new Properties(); 16 props.load(new FileInputStream(paramFile)); 17 driver = props.getProperty("driver"); 18 url = props.getProperty("url"); 19 user = props.getProperty("user"); 20 pass = props.getProperty("pass"); 21 } 22 23 public void query(String sql) throws Exception{ 24 //加载驱动 25 Class.forName(driver); 26 try( 27 //获取数据库连接 28 Connection conn = DriverManager.getConnection(url, user, pass); 29 //使用Connection来创建一个PreparedStatement对象 30 //传入控制结果集可滚动、可更新的参数 31 PreparedStatement pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); 32 ResultSet rs = pstmt.executeQuery() 33 ){ 34 rs.last(); 35 int rowCount = rs.getRow(); 36 for(int i = rowCount; i > 0; i--){ 37 rs.absolute(i); 38 System.out.println(rs.getString(1) + "\t" 39 + rs.getString(2) + "\t" + rs.getString(3)); 40 //修改记录指针所指记录、第2列的值 41 rs.updateString(2, "学生名" + i); 42 //提交修改 43 rs.updateRow(); 44 } 45 } 46 } 47 48 public static void main(String[] args) throws Exception{ 49 ResultSetTest rt = new ResultSetTest(); 50 rt.initParam("mysql.ini"); 51 rt.query("select * from student_table"); 52 } 53 }
student_table表中记录被倒序输出,且当程序运行结束后,student_table表中所有记录的student_name列的值都被修改。
若要创建可更新的结果集,则使用查询语句查询的数据通常只能来自于一个数据表,而且查询结果集中的数据列必须包含主键列,否则会引起更新失败。
处理Blob类型数据:
Blob(Binary Long Object):是二进制长对象,Blob列常用于存储大文件,典型的Blob内容是一张图片或一个声音文件,由于它们的特殊性,必须使用特殊的方式来存储
使用Blob列可以把图片、声音等文件的二进制数据保存在数据库中,并可以从数据库中恢复指定文件。
若需要将图片插入数据库,显然不能直接通过普通的SQL语句来完成,因为有一个关键问题——Blob常量无法表示。所以将Blob数据插入数据库需要使用
PreparedStatement,该对象有一个方法:setBinaryStream(int parameterIndex, InputStream x),该方法可以为指定参数传入二进制输入流,从而可以实现将Blob数据保存
到数据库的功能。
需要从ResultSet里取出Blob数据时,可以调用ResultSet的getBlob(int columnIndex)方法,该方法将返回一个Blob对象,Blob对象提供了getBinaryStream()方法来获取该
Blob数据的输入流,也可以使用Blob对象提供的getBytes()方法直接取出该Blob对象封装的二进制数据。
为了把图片放入数据库,使用如下SQL语句建立一个数据表:
img_data mediumblob;创建一个mediumblob类型的数据列,用于保存图片数据
mediumblob类型可存储16M内容,blob类型可存储64KB内容。
下面程序可以实现图片“上传”——实际上就是将图片保存到数据库,并在右边的列表框中显示图片的名字,当用户双击列表框中的图片名时,左边窗口将显示该图片——实质就是根据选中的ID从数据库里查找图片,并将其显示出来:
1 import java.sql.*; 2 import javax.swing.*; 3 import java.awt.*; 4 import java.awt.event.*; 5 import java.util.Properties; 6 import java.util.ArrayList; 7 import java.io.*; 8 import javax.swing.filechooser.FileFilter; 9 10 public class BlobTest 11 { 12 JFrame jf = new JFrame("图片管理程序"); 13 private static Connection conn; 14 private static PreparedStatement insert; 15 private static PreparedStatement query; 16 private static PreparedStatement queryAll; 17 // 定义一个DefaultListModel对象 18 private DefaultListModel<ImageHolder> imageModel 19 = new DefaultListModel<>(); 20 private JList<ImageHolder> imageList = new JList<>(imageModel); 21 private JTextField filePath = new JTextField(26); 22 private JButton browserBn = new JButton("..."); 23 private JButton uploadBn = new JButton("上传"); 24 private JLabel imageLabel = new JLabel(); 25 // 以当前路径创建文件选择器 26 JFileChooser chooser = new JFileChooser("."); 27 // 创建文件过滤器 28 ExtensionFileFilter filter = new ExtensionFileFilter(); 29 static 30 { 31 try 32 { 33 Properties props = new Properties(); 34 props.load(new FileInputStream("mysql.ini")); 35 String driver = props.getProperty("driver"); 36 String url = props.getProperty("url"); 37 String user = props.getProperty("user"); 38 String pass = props.getProperty("pass"); 39 Class.forName(driver); 40 // 获取数据库连接 41 conn = DriverManager.getConnection(url , user , pass); 42 // 创建执行插入的PreparedStatement对象, 43 // 该对象执行插入后可以返回自动生成的主键 44 insert = conn.prepareStatement("insert into img_table" 45 + " values(null,?,?)" , Statement.RETURN_GENERATED_KEYS); 46 // 创建两个PreparedStatement对象,用于查询指定图片,查询所有图片 47 query = conn.prepareStatement("select img_data from img_table" 48 + " where img_id=?"); 49 queryAll = conn.prepareStatement("select img_id, " 50 + " img_name from img_table"); 51 } 52 catch (Exception e) 53 { 54 e.printStackTrace(); 55 } 56 } 57 public void init()throws SQLException 58 { 59 // -------初始化文件选择器-------- 60 filter.addExtension("jpg"); 61 filter.addExtension("jpeg"); 62 filter.addExtension("gif"); 63 filter.addExtension("png"); 64 filter.setDescription("图片文件(*.jpg,*.jpeg,*.gif,*.png)"); 65 chooser.addChoosableFileFilter(filter); 66 // 禁止“文件类型”下拉列表中显示“所有文件”选项。 67 chooser.setAcceptAllFileFilterUsed(false); 68 // ---------初始化程序界面--------- 69 fillListModel(); 70 filePath.setEditable(false); 71 // 只能单选 72 imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 73 JPanel jp = new JPanel(); 74 jp.add(filePath); 75 jp.add(browserBn); 76 browserBn.addActionListener(event -> { 77 // 显示文件对话框 78 int result = chooser.showDialog(jf , "浏览图片文件上传"); 79 // 如果用户选择了APPROVE(赞同)按钮,即打开,保存等效按钮 80 if(result == JFileChooser.APPROVE_OPTION) 81 { 82 filePath.setText(chooser.getSelectedFile().getPath()); 83 } 84 }); 85 jp.add(uploadBn); 86 uploadBn.addActionListener(avt -> { 87 // 如果上传文件的文本框有内容 88 if (filePath.getText().trim().length() > 0) 89 { 90 // 将指定文件保存到数据库 91 upload(filePath.getText()); 92 // 清空文本框内容 93 filePath.setText(""); 94 } 95 }); 96 JPanel left = new JPanel(); 97 left.setLayout(new BorderLayout()); 98 left.add(new JScrollPane(imageLabel) , BorderLayout.CENTER); 99 left.add(jp , BorderLayout.SOUTH); 100 jf.add(left); 101 imageList.setFixedCellWidth(160); 102 jf.add(new JScrollPane(imageList) , BorderLayout.EAST); 103 imageList.addMouseListener(new MouseAdapter() 104 { 105 public void mouseClicked(MouseEvent e) 106 { 107 // 如果鼠标双击 108 if (e.getClickCount() >= 2) 109 { 110 // 取出选中的List项 111 ImageHolder cur = (ImageHolder)imageList. 112 getSelectedValue(); 113 try 114 { 115 // 显示选中项对应的Image 116 showImage(cur.getId()); 117 } 118 catch (SQLException sqle) 119 { 120 sqle.printStackTrace(); 121 } 122 } 123 } 124 }); 125 jf.setSize(620, 400); 126 jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 127 jf.setVisible(true); 128 } 129 // ----------查找img_table填充ListModel---------- 130 public void fillListModel()throws SQLException 131 { 132 133 try( 134 // 执行查询 135 ResultSet rs = queryAll.executeQuery()) 136 { 137 // 先清除所有元素 138 imageModel.clear(); 139 // 把查询的全部记录添加到ListModel中 140 while (rs.next()) 141 { 142 imageModel.addElement(new ImageHolder(rs.getInt(1) 143 ,rs.getString(2))); 144 } 145 } 146 } 147 // ---------将指定图片放入数据库--------- 148 public void upload(String fileName) 149 { 150 // 截取文件名 151 String imageName = fileName.substring(fileName.lastIndexOf(‘\\‘) 152 + 1 , fileName.lastIndexOf(‘.‘)); 153 File f = new File(fileName); 154 try( 155 InputStream is = new FileInputStream(f)) 156 { 157 // 设置图片名参数 158 insert.setString(1, imageName); 159 // 设置二进制流参数 160 insert.setBinaryStream(2, is , (int)f.length()); 161 int affect = insert.executeUpdate(); 162 if (affect == 1) 163 { 164 // 重新更新ListModel,将会让JList显示最新的图片列表 165 fillListModel(); 166 } 167 } 168 catch (Exception e) 169 { 170 e.printStackTrace(); 171 } 172 } 173 // ---------根据图片ID来显示图片---------- 174 public void showImage(int id)throws SQLException 175 { 176 // 设置参数 177 query.setInt(1, id); 178 try( 179 // 执行查询 180 ResultSet rs = query.executeQuery()) 181 { 182 if (rs.next()) 183 { 184 // 取出Blob列 185 Blob imgBlob = rs.getBlob(1); 186 // 取出Blob列里的数据 187 ImageIcon icon=new ImageIcon(imgBlob.getBytes(1L 188 ,(int)imgBlob.length())); 189 imageLabel.setIcon(icon); 190 } 191 } 192 } 193 public static void main(String[] args)throws SQLException 194 { 195 new BlobTest().init(); 196 } 197 } 198 // 创建FileFilter的子类,用以实现文件过滤功能 199 class ExtensionFileFilter extends FileFilter 200 { 201 private String description = ""; 202 private ArrayList<String> extensions = new ArrayList<>(); 203 // 自定义方法,用于添加文件扩展名 204 public void addExtension(String extension) 205 { 206 if (!extension.startsWith(".")) 207 { 208 extension = "." + extension; 209 extensions.add(extension.toLowerCase()); 210 } 211 } 212 // 用于设置该文件过滤器的描述文本 213 public void setDescription(String aDescription) 214 { 215 description = aDescription; 216 } 217 // 继承FileFilter类必须实现的抽象方法,返回该文件过滤器的描述文本 218 public String getDescription() 219 { 220 return description; 221 } 222 // 继承FileFilter类必须实现的抽象方法,判断该文件过滤器是否接受该文件 223 public boolean accept(File f) 224 { 225 // 如果该文件是路径,接受该文件 226 if (f.isDirectory()) return true; 227 // 将文件名转为小写(全部转为小写后比较,用于忽略文件名大小写) 228 String name = f.getName().toLowerCase(); 229 // 遍历所有可接受的扩展名,如果扩展名相同,该文件就可接受。 230 for (String extension : extensions) 231 { 232 if (name.endsWith(extension)) 233 { 234 return true; 235 } 236 } 237 return false; 238 } 239 } 240 // 创建一个ImageHolder类,用于封装图片名、图片ID 241 class ImageHolder 242 { 243 // 封装图片的ID 244 private int id; 245 // 封装图片的图片名字 246 private String name; 247 public ImageHolder(){} 248 public ImageHolder(int id , String name) 249 { 250 this.id = id; 251 this.name = name; 252 } 253 // id的setter和getter方法 254 public void setId(int id) 255 { 256 this.id = id; 257 } 258 public int getId() 259 { 260 return this.id; 261 } 262 // name的setter和getter方法 263 public void setName(String name) 264 { 265 this.name = name; 266 } 267 public String getName() 268 { 269 return this.name; 270 } 271 // 重写toString方法,返回图片名 272 public String toString() 273 { 274 return name; 275 } 276 }
使用ResultSetMetaData分析结果集:
当执行SQL查询后可以通过移动记录指针来遍历ResultSet的每条记录,但程序可能不清楚该ResultSet里包含哪些数据列,以及每个数据列的数据类型,那么可以通
过ResultSetMetaData来获取关于ResultSet的描述信息:
MetaData的意思是元数据,即描述其他数据的数据,因此ResultSetMetaData封装了描述ResultSet对象的数据;后面还要介绍的DatabaseMetaData则封装了描述
Database的数据。
ResultSet中包含了一个getMetaData()方法,该方法可以返回该ResultSet对应的ResultSetMetaData对象。一旦获得了ResultSetMetaData对象就可以通过
ResultSetMetaData提供的大量方法来返回ResultSet的描述信息。常用方法有如下三个:
1.int getColumnCount():返回该ResultSet的列数量
2.String getColumnName(int Column):返回指定索引的列名
3.int getColumnType(int column):返回指定索引的列类型
下面是一个简单的查询器,当用户在文本框内输入合法的查询语句并执行成功后,下面表格将会显示查询结果:
1 import java.awt.*; 2 import java.awt.event.*; 3 import javax.swing.*; 4 import javax.swing.table.*; 5 import java.util.*; 6 import java.io.*; 7 import java.sql.*; 8 9 public class QueryExecutor 10 { 11 JFrame jf = new JFrame("查询执行器"); 12 private JScrollPane scrollPane; 13 private JButton execBn = new JButton("查询"); 14 // 用于输入查询语句的文本框 15 private JTextField sqlField = new JTextField(45); 16 private static Connection conn; 17 private static Statement stmt; 18 // 采用静态初始化块来初始化Connection、Statement对象 19 static 20 { 21 try 22 { 23 Properties props = new Properties(); 24 props.load(new FileInputStream("mysql.ini")); 25 String drivers = props.getProperty("driver"); 26 String url = props.getProperty("url"); 27 String username = props.getProperty("user"); 28 String password = props.getProperty("pass"); 29 // 加载数据库驱动 30 Class.forName(drivers); 31 // 取得数据库连接 32 conn = DriverManager.getConnection(url, username, password); 33 stmt = conn.createStatement(); 34 } 35 catch (Exception e) 36 { 37 e.printStackTrace(); 38 } 39 } 40 // --------初始化界面的方法--------- 41 public void init() 42 { 43 JPanel top = new JPanel(); 44 top.add(new JLabel("输入查询语句:")); 45 top.add(sqlField); 46 top.add(execBn); 47 // 为执行按钮、单行文本框添加事件监听器 48 execBn.addActionListener(new ExceListener()); 49 sqlField.addActionListener(new ExceListener()); 50 jf.add(top , BorderLayout.NORTH); 51 jf.setSize(680, 480); 52 jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 53 jf.setVisible(true); 54 } 55 // 定义监听器 56 class ExceListener implements ActionListener 57 { 58 public void actionPerformed(ActionEvent evt) 59 { 60 // 删除原来的JTable(JTable使用scrollPane来包装) 61 if (scrollPane != null) 62 { 63 jf.remove(scrollPane); 64 } 65 try( 66 // 根据用户输入的SQL执行查询 67 ResultSet rs = stmt.executeQuery(sqlField.getText())) 68 { 69 // 取出ResultSet的MetaData 70 ResultSetMetaData rsmd = rs.getMetaData(); 71 Vector<String> columnNames = new Vector<>(); 72 Vector<Vector<String>> data = new Vector<>(); 73 // 把ResultSet的所有列名添加到Vector里 74 for (int i = 0 ; i < rsmd.getColumnCount(); i++ ) 75 { 76 columnNames.add(rsmd.getColumnName(i + 1)); 77 } 78 // 把ResultSet的所有记录添加到Vector里 79 while (rs.next()) 80 { 81 Vector<String> v = new Vector<>(); 82 for (int i = 0 ; i < rsmd.getColumnCount(); i++ ) 83 { 84 v.add(rs.getString(i + 1)); 85 } 86 data.add(v); 87 } 88 // 创建新的JTable 89 JTable table = new JTable(data , columnNames); 90 scrollPane = new JScrollPane(table); 91 // 添加新的Table 92 jf.add(scrollPane); 93 // 更新主窗口 94 jf.validate(); 95 } 96 catch (Exception e) 97 { 98 e.printStackTrace(); 99 } 100 } 101 } 102 public static void main(String[] args) 103 { 104 new QueryExecutor().init(); 105 } 106 }
虽然ResultSetMetaData可以准确的分析出ResultSet里包含多少列,以及每列的列名、数据类型等,但使用ResultSetMetaData需要一定的系统开销,因此若在编程过程中
已经知道ResultSet里包含多少列,以及每列的列名、类型等信息,就没有必要使用ResultSetMetaData来分析该ResultSet对象了。
Java7的RowSet1.1:
RowSet接口继承了ResultSet接口,RowSet接口下包含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet和WebRowSet常用子接口。除了JdbcRowSet需要保
持与数据库连接之外,其余4个子接口都是离线的RowSet,无需保持与数据库的连接。
与ResultSet相比,RowSet默认是可滚动、可更新、可序列化的结果集,而且作为JavaBean使用,因此能方便地在网络上传输,用于同步两端的数据。对于离线RowSet而
言,程序在创建RowSet时已经把数据从底层数据库读取到内存,因此可以充分利用计算机内存,从而降低数据库服务器的负载,提高程序性能。
Java7新增的RowSetFactory与RowSet:
Java7新增了RowSetProvider类和RowSetFactory接口,其中RowSetProvider负责创建RowSetFactory,而RowSetFactory则提供了如下方法来创建RowSet实例:
1.CachedRowSet createCachedRowSet():创建一个默认的CachedRowSet。
2.FilteredRowSet createFilteredRowSet():创建一个默认的FilteredRowSet。
3.JdbcRowSet createJdbcRowSet():创建一个默认的JdbcRowSet。
4.JoinRowSet createJoinRowSet():创建一个默认的JoinRowSet。
5.WebRowSet createWebRowSet():创建一个默认的WebRowSet。
通过使用RowSetFactory,就可以把应用程序与RowSet实现类分离开,有利于后期的升级、扩展。
下面使用RowSetFactory来创建JdbcRowSet实例:
1 import java.util.*; 2 import java.io.*; 3 import java.sql.*; 4 import javax.sql.rowset.*; 5 6 public class RowSetFactoryTest 7 { 8 private String driver; 9 private String url; 10 private String user; 11 private String pass; 12 public void initParam(String paramFile)throws Exception 13 { 14 // 使用Properties类来加载属性文件 15 Properties props = new Properties(); 16 props.load(new FileInputStream(paramFile)); 17 driver = props.getProperty("driver"); 18 url = props.getProperty("url"); 19 user = props.getProperty("user"); 20 pass = props.getProperty("pass"); 21 } 22 23 public void update(String sql)throws Exception 24 { 25 // 加载驱动 26 Class.forName(driver); 27 // 使用RowSetProvider创建RowSetFactory 28 RowSetFactory factory = RowSetProvider.newFactory(); 29 try( 30 // 使用RowSetFactory创建默认的JdbcRowSet实例 31 JdbcRowSet jdbcRs = factory.createJdbcRowSet()) 32 { 33 // 设置必要的连接信息 34 jdbcRs.setUrl(url); 35 jdbcRs.setUsername(user); 36 jdbcRs.setPassword(pass); 37 // 设置SQL查询语句 38 jdbcRs.setCommand(sql); 39 // 执行查询 40 jdbcRs.execute(); 41 jdbcRs.afterLast(); 42 // 向前滚动结果集 43 while (jdbcRs.previous()) 44 { 45 System.out.println(jdbcRs.getString(1) 46 + "\t" + jdbcRs.getString(2) 47 + "\t" + jdbcRs.getString(3)); 48 if (jdbcRs.getInt("student_id") == 3) 49 { 50 // 修改指定记录行 51 jdbcRs.updateString("student_name", "孙悟空"); 52 jdbcRs.updateRow(); 53 } 54 } 55 } 56 } 57 public static void main(String[] args)throws Exception 58 { 59 RowSetFactoryTest jt = new RowSetFactoryTest(); 60 jt.initParam("mysql.ini"); 61 jt.update("select * from student_table"); 62 } 63 }
上面程序使用RowSetFactory来创建JdbcRowSet对象。由于通过这种方式创建的JdbcRowSet还没有传入Connection参数,因此程序还需调用setUrl()、setUsername()、setPassword()等方法来设置数据库连接信息。
离线RowSet:
在使用ResultSet的时代,程序查询得到ResultSet之后必须立即读取或处理它对应的记录,否则一旦关闭Connection,再通过ResultSet读取记录就会引发异常。
离线RowSet会直接将底层数据读入内存中,封装成RowSet对象,而RowSet对象则完全可以当成JavaBean来使用,因此不仅安全,且编程十分简单。CachedRowSet是
所有离线RowSet的父接口。
下面以CachedRowSet为例进行介绍:
1 import java.util.*; 2 import java.io.*; 3 import java.sql.*; 4 import javax.sql.*; 5 import javax.sql.rowset.*; 6 7 public class CachedRowSetTest 8 { 9 private static String driver; 10 private static String url; 11 private static String user; 12 private static String pass; 13 public void initParam(String paramFile)throws Exception 14 { 15 // 使用Properties类来加载属性文件 16 Properties props = new Properties(); 17 props.load(new FileInputStream(paramFile)); 18 driver = props.getProperty("driver"); 19 url = props.getProperty("url"); 20 user = props.getProperty("user"); 21 pass = props.getProperty("pass"); 22 } 23 24 public CachedRowSet query(String sql)throws Exception 25 { 26 // 加载驱动 27 Class.forName(driver); 28 // 获取数据库连接 29 Connection conn = DriverManager.getConnection(url , user , pass); 30 Statement stmt = conn.createStatement(); 31 ResultSet rs = stmt.executeQuery(sql); 32 // 使用RowSetProvider创建RowSetFactory 33 RowSetFactory factory = RowSetProvider.newFactory(); 34 // 创建默认的CachedRowSet实例 35 CachedRowSet cachedRs = factory.createCachedRowSet(); 36 // 使用ResultSet装填RowSet 37 cachedRs.populate(rs); // ① 38 // 关闭资源 39 rs.close(); 40 stmt.close(); 41 conn.close(); 42 return cachedRs; 43 } 44 public static void main(String[] args)throws Exception 45 { 46 CachedRowSetTest ct = new CachedRowSetTest(); 47 ct.initParam("mysql.ini"); 48 CachedRowSet rs = ct.query("select * from student_table"); 49 rs.afterLast(); 50 // 向前滚动结果集 51 while (rs.previous()) 52 { 53 System.out.println(rs.getString(1) 54 + "\t" + rs.getString(2) 55 + "\t" + rs.getString(3)); 56 if (rs.getInt("student_id") == 3) 57 { 58 // 修改指定记录行 59 rs.updateString("student_name", "孙悟空"); 60 rs.updateRow(); 61 } 62 } 63 // 重新获取数据库连接 64 Connection conn = DriverManager.getConnection(url 65 , user , pass); 66 conn.setAutoCommit(false); 67 // 把对RowSet所做的修改同步到底层数据库 68 rs.acceptChanges(conn); 69 } 70 }
从上面程序可以看到在Connection关闭的情况下,程序依然可以读取、修改RowSet里的记录。为了将程序对离线RowSet所做的修改同步到底层数据库,程序在调用RowSet的
acceptChanges()方法时,必须传入Connection。
离线RowSet的查询分页:
由于CachedRowSet会将数据记录直接装载到内存中,若SQL查询返回的记录过大,CachedRowSet将会占用大量内存,在某些极端情况下,将会导致内存溢出。
未解决上述问题,CachedRowSet提供了分页功能。即一次只装载ResultSet里的某几条记录,这样就避免了CachedRowSet占用内存过大的问题。
CachedRowSet提供了如下方法控制分页:
1.populate(ResultSet rs, int startRow):使用给定的ResultSet装填RowSet,从ResultSet的第startRow条记录开始装填
2.setPageSize(int pageSize):设置CachedRowSet每次返回多少条记录
3.previousPage():在底层ResultSet可用的情况下,让CachedRowSet读取上一页记录。
4.nextPage():在底层ResultSet可用的情况下,让CachedRowSet读取下一页记录
下面程序示范了CachedRowSet的分页支持:
1 import java.util.*; 2 import java.io.*; 3 import java.sql.*; 4 import javax.sql.*; 5 import javax.sql.rowset.*; 6 7 public class CachedRowSetPage 8 { 9 private String driver; 10 private String url; 11 private String user; 12 private String pass; 13 public void initParam(String paramFile)throws Exception 14 { 15 // 使用Properties类来加载属性文件 16 Properties props = new Properties(); 17 props.load(new FileInputStream(paramFile)); 18 driver = props.getProperty("driver"); 19 url = props.getProperty("url"); 20 user = props.getProperty("user"); 21 pass = props.getProperty("pass"); 22 } 23 24 public CachedRowSet query(String sql , int pageSize 25 , int page)throws Exception 26 { 27 // 加载驱动 28 Class.forName(driver); 29 try( 30 // 获取数据库连接 31 Connection conn = DriverManager.getConnection(url , user , pass); 32 Statement stmt = conn.createStatement(); 33 ResultSet rs = stmt.executeQuery(sql)) 34 { 35 // 使用RowSetProvider创建RowSetFactory 36 RowSetFactory factory = RowSetProvider.newFactory(); 37 // 创建默认的CachedRowSet实例 38 CachedRowSet cachedRs = factory.createCachedRowSet(); 39 // 设置每页显示pageSize条记录 40 cachedRs.setPageSize(pageSize); 41 // 使用ResultSet装填RowSet,设置从第几条记录开始 42 cachedRs.populate(rs , (page - 1) * pageSize + 1); 43 return cachedRs; 44 } 45 } 46 public static void main(String[] args)throws Exception 47 { 48 CachedRowSetPage cp = new CachedRowSetPage(); 49 cp.initParam("mysql.ini"); 50 CachedRowSet rs = cp.query("select * from student_table" , 3 , 2); // ① 51 // 向后滚动结果集 52 while (rs.next()) 53 { 54 System.out.println(rs.getString(1) 55 + "\t" + rs.getString(2) 56 + "\t" + rs.getString(3)); 57 } 58 } 59 }
程序中要查询第2页的记录,每页显示3条记录。
事务处理:
事物的概念和MySQL事务支持:
事务是由一步或几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行。
事务具备四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持续性(Durability)。简称ACID特性。
JDBC的事务支持:
JDBC连接的事务支持有Connection提供,Connection默认打开自动提交,即关闭事务。这种情况下,每一条SQL语句一旦执行,便会立即提交到数据库,永久生效,无法
对其进行回滚操作。
可调用Connection的setAutoCommit()方法来关闭自动提交,开启事务:
//conn.setAutoCommit(false);
等到所有SQL语句都被执行,程序可以调用Connection的commit()方法来提交事务:
//conn.commit();
若任意一条SQL语句执行失败,则应该用Connection的rollback()方法来回滚事务:
//conn.rollback();
实际上,当Connection遇到一个未处理的SQLException异常时,系统将会非正常退出,事务也会自动回滚。但若程序捕获了该异常,则需要在异常处理块中显式地回滚
事务
1 import java.sql.*; 2 import java.io.*; 3 import java.util.*; 4 5 public class TransactionTest 6 { 7 private String driver; 8 private String url; 9 private String user; 10 private String pass; 11 public void initParam(String paramFile)throws Exception 12 { 13 // 使用Properties类来加载属性文件 14 Properties props = new Properties(); 15 props.load(new FileInputStream(paramFile)); 16 driver = props.getProperty("driver"); 17 url = props.getProperty("url"); 18 user = props.getProperty("user"); 19 pass = props.getProperty("pass"); 20 } 21 public void insertInTransaction(String[] sqls) throws Exception 22 { 23 // 加载驱动 24 Class.forName(driver); 25 try( 26 Connection conn = DriverManager.getConnection(url , user , pass)) 27 { 28 // 关闭自动提交,开启事务 29 conn.setAutoCommit(false); 30 try( 31 // 使用Connection来创建一个Statment对象 32 Statement stmt = conn.createStatement()) 33 { 34 // 循环多次执行SQL语句 35 for (String sql : sqls) 36 { 37 stmt.executeUpdate(sql); 38 } 39 } 40 // 提交事务 41 conn.commit(); 42 } 43 } 44 public static void main(String[] args) throws Exception 45 { 46 TransactionTest tt = new TransactionTest(); 47 tt.initParam("mysql.ini"); 48 String[] sqls = new String[]{ 49 "insert into student_table values(null , ‘aaa‘ ,1)", 50 "insert into student_table values(null , ‘bbb‘ ,1)", 51 "insert into student_table values(null , ‘ccc‘ ,1)", 52 // 下面这条SQL语句将会违反外键约束, 53 // 因为teacher_table中没有ID为5的记录。 54 "insert into student_table values(null , ‘ccc‘ ,5)" //① 55 }; 56 tt.insertInTransaction(sqls); 57 } 58 }
上面代码报错会因为插入语句第四条有错。正是因为这条语句出错,导致产生异常,且该异常没有得到处理,引起程序非正常结束,所以事务自动回滚,上面3条插入语句无效。
Connection也提供了设置中间点的方法:
1.Savepoint setSavepoint():在当前事务中创建一个未命名的中间点,并返回代表该中间点的Savepoint对象
2.Savepoint setSavepoint(String name):在当前事务中创建一个具有指定名称的中间点,并返回代表该中间点的SavepointSavepoint对象。
通常来说设置中间点时,没有必要指定名称,因为Connection回滚到指定中间点时,并不是根据名字回滚的,而是根据中间点对象回滚的,Connection提供了
rollback(Savepoint savepoint)方法回滚到指定中间点。
Java8增强的批量更新:
JDBC还提供了一个批量更新的功能,批量更新时,多条SQL语句将被作为一批操作被同时收集,并同时提交。
批量更新必须得到底层数据库的支持,可以通过调用DatabaseMetaData的supportsBatchUpdates()方法来查看底层数据库是否支持批量更新。
使用批量更新需要先创建一个Statement对象,然后利用该对象的addBatch()方法将多条SQL语句同时收集,最后调用Java8位Statement对象新增的executeLargeBatch()或
原有的executeBatch()方法同时执行这些SQL语句。只要批量操作中任何一条SQL语句影响的记录条数可能超过Integer.MAX_VALUE,就应该使用executeLargeBatch()方
法。如下:
1 Statement stmt = conn.createStatement(); 2 //使用Statement同时收集多条SQL语句 3 stmt.addBatch(sql1); 4 stmt.addBatch(sql2); 5 stmt.addBatch(sql3); 6 ... 7 //同时执行所有的SQL语句 8 stmt.executeLargeBatch();
若在批量更新的addBatch()方法中添加了select查询语句,程序将会直接出现错误。为了让批量操作可以正确的处理错误,必须把批量执行的操作视为单个事务,若批量更
新在执行过程中失败,则让事务回滚到批量操作开始之前的状态。为达到这种效果,程序应该在开始批量操作之前先关闭自动提交,然后开始收集更新语句,当批量操作
执行结束后,提交事务,并恢复之前的自动提交模式,如下:
1 //保存当前的自动的提交模式 2 boolean autoCommit = conn.getAutoCommit(); 3 //关闭自动提交 4 conn.setAutoCommit(false); 5 Statement stmt = conn.createStatement(); 6 //使用Statement同时收集多条SQL语句 7 stmt.addBatch(sql1); 8 stmt.addBatch(sql2); 9 stmt.addBatch(sql3); 10 ... 11 //同时执行所有的SQL语句 12 stmt.executeLargeBatch(); 13 //提交修改 14 conn.commit(); 15 //恢复原有的紫东提交模式 16 conn.setAutocommit(autoCommit);
MySQL的最新驱动依然不支持executeLargeBatch()方法,对于数据库驱动不支持executeLargeBatch()的情形,则只能依然使用传统的executeBatch()方法。
分析数据库信息:
使用DatabaseMetaData分析数据库信息:
JDBC提供了DatabaseMetaData来封装数据库连接对应数据库的信息,通过Connection提供的getMetaData()方法就可以获取数据库对应的DatabaseMetaData对象
DatabaseMetaData接口通常由驱动程序供应商提供实现,其目的是让用户了解底层数据库的相关信息。使用该接口的目的是发现如何处理底层数据库,尤其是对于试图与
多个数据库一起使用的应用程序——因为应用程序需要在多个数据库之间切换,所以必须利用该接口来找出底层数据库的功能,如:调用supportsCorrelatedSubqueries
()方法查看是否可以使用关联子查询,或者调用supportsBatchUpdates()方法查看是否可以使用批量更新。
许多DatabaseMetaData方法以ResultSet对象的形式返回查询信息,然后使用ResultSet的常规方法(如:getString()和getInt())即可从这些ResultSet对象中获取数据。若
查询的信息不可用,则将返回一个空ResultSet对象。
DatabaseMetaData的很多方法都需要传入一个XXXPattern模式字符串,这里的XXXPattern不是正则表达式,而是SQL里的模式字符串,即用%代表任意多个字符,使用下
划线代表一个字符。在通常情况下,若把该模式字符串的参数值设置为null,即表明该参数不作为过滤条件。
下面程序通过DatabaseMetaData分析了当前Connection连接对应数据库的一些基本信息,包括当前数据库包含多少数据表,存储过程,student_table表的数据列、主键、
外键等信息:
1 import java.sql.*; 2 import java.util.*; 3 import java.io.*; 4 5 public class DatabaseMetaDataTest 6 { 7 private String driver; 8 private String url; 9 private String user; 10 private String pass; 11 public void initParam(String paramFile)throws Exception 12 { 13 // 使用Properties类来加载属性文件 14 Properties props = new Properties(); 15 props.load(new FileInputStream(paramFile)); 16 driver = props.getProperty("driver"); 17 url = props.getProperty("url"); 18 user = props.getProperty("user"); 19 pass = props.getProperty("pass"); 20 } 21 public void info() throws Exception 22 { 23 // 加载驱动 24 Class.forName(driver); 25 try( 26 // 获取数据库连接 27 Connection conn = DriverManager.getConnection(url 28 , user , pass)) 29 { 30 // 获取的DatabaseMetaData对象 31 DatabaseMetaData dbmd = conn.getMetaData(); 32 // 获取MySQL支持的所有表类型 33 ResultSet rs = dbmd.getTableTypes(); 34 System.out.println("--MySQL支持的表类型信息--"); 35 printResultSet(rs); 36 // 获取当前数据库的全部数据表 37 rs = dbmd.getTables(null,null, "%" , new String[]{"TABLE"}); 38 System.out.println("--当前数据库里的数据表信息--"); 39 printResultSet(rs); 40 // 获取student_table表的主键 41 rs = dbmd.getPrimaryKeys(null , null, "student_table"); 42 System.out.println("--student_table表的主键信息--"); 43 printResultSet(rs); 44 // 获取当前数据库的全部存储过程 45 rs = dbmd.getProcedures(null , null, "%"); 46 System.out.println("--当前数据库里的存储过程信息--"); 47 printResultSet(rs); 48 // 获取teacher_table表和student_table之间的外键约束 49 rs = dbmd.getCrossReference(null,null, "teacher_table" 50 , null, null, "student_table"); 51 System.out.println("--teacher_table表和student_table之间" 52 + "的外键约束--"); 53 printResultSet(rs); 54 // 获取student_table表的全部数据列 55 rs = dbmd.getColumns(null, null, "student_table", "%"); 56 System.out.println("--student_table表的全部数据列--"); 57 printResultSet(rs); 58 } 59 } 60 public void printResultSet(ResultSet rs)throws SQLException 61 { 62 ResultSetMetaData rsmd = rs.getMetaData(); 63 // 打印ResultSet的所有列标题 64 for (int i = 0 ; i < rsmd.getColumnCount() ; i++ ) 65 { 66 System.out.print(rsmd.getColumnName(i + 1) + "\t"); 67 } 68 System.out.print("\n"); 69 // 打印ResultSet里的全部数据 70 while (rs.next()) 71 { 72 for (int i = 0; i < rsmd.getColumnCount() ; i++ ) 73 { 74 System.out.print(rs.getString(i + 1) + "\t"); 75 } 76 System.out.print("\n"); 77 } 78 rs.close(); 79 } 80 public static void main(String[] args) 81 throws Exception 82 { 83 DatabaseMetaDataTest dt = new DatabaseMetaDataTest(); 84 dt.initParam("mysql.ini"); 85 dt.info(); 86 } 87 }
结果太多,只截取一部分。
使用系统表分析数据库信息:
除了DatabaseMetaData来分析底层数据库信息之外,若已经确定应用程序所以用的数据库系统,则可以通过数据库的系统来分析数据库信息。
系统表又称为数据字典,数据字典的数据通常由数据库系统负责维护,用户通常只能查询数据字典,而不能修改数据字典的内容。
MySQL数据库使用information_schema数据库来保存系统表,在数据库里包含了大量系统表,常用系统表的简单介绍如下:
1.tables:存放数据库里所有数据表信息
2.schemata:存放数据库里所有数据库的信息
3.views:存放数据库里所有视图的信息
4.columns:存放数据库里所有列的信息
5.triggers:存放数据库里所有触发器的信息
6.routines:存放数据库里所有存储过程和函数的信息
7.key_column_usage:存放数据库里所有具有约束的键信息
8.table_constraints:存放数据库里全部约束表的信息
9.statistics:存放数据库里全部索引的信息
使用连接池管理连接:
数据库连接的建立和关闭是极耗费系统资源的操作,数据库连接池的解决方案是:当应用程序启动时,系统主动建立足够的数据库连接,并将这些连接组成一个连接池。每次应
用程序请求数据库连接是,无需重新打开连接,而是从连接池中取出已有的连接使用,使用完后不再关闭数据库连接,而是直接将连接归还给连接池。
对于共享资源的情况,有一个通用的设计模式:资源池(Resource Pool),用于解决资源的频繁请求、释放所造成的性能下降。
数据库连接池是Connection对象的工厂,数据库连接池的常用参数如下:
1.数据库的初始连接数
2.连接池的最大连接数
3.连接池的最小连接数
4.连接池每次增加的容量
JDBC的数据库连接池使用 javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由商用服务器提供实现,也有一些开源组织提供实现(如DBCP和C3P0)。
DBCP数据源:
DBCP是Apache软件基金组织下的开源连接实现,该连接池依赖该组织下的另一个开源系统:common-pool。若需要使用该连接池实现,则应在系统中增加两个jar 文件:
1.commons-dbcp.jar:连接池的实现
2.commons-pool.jar:连接池实现的依赖库
Tomcat的连接池正是采用该连接池实现的。数据库连接池既可以与应用服务器整合使用,也可以由应用程序独立使用。
下面代码片段示范了使用DBCP来获得数据库连接方式:
1 //创建连接池实例 2 BasicDataSource ds = new BasicDataSourc(); 3 //设置连接池所需驱动 4 ds.setDriverClassName("com.mysql.jdbc.Driver"); 5 //设置连接数据库的URL 6 ds.setUrl("jdbc:mysql://localhost:3306/javaee"); 7 //设置连接数据库的用户名 8 ds.setUsername("root"); 9 //设置连接数据库的密码 10 ds.setPassword("pass"); 11 //设置连接池的初始连接数 12 ds.setInitialSize(5); 13 //设置连接池最多可有多少个活动连接数 14 ds.setMaxActive(20); 15 //设置连接池中最少有2个空闲的连接 16 ds.setMinIdle(2);
数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。即:一个应用,上面代码只需要执行一次即可。
建议把上面程序中的ds设置成static成员变量,并且在应用开始时立即初始化数据源对象,程序中所有需要获取数据库连接的地方直接访问该ds对象,并获取数据库连接即
可。
//通过数据源获取数据库连接
Connection conn = ds.getConnection();
当数据库访问结束后,程序还是像以前一样关闭数据库连接:
//释放数据库连接
conn.close();
C3P0数据源:
C3P0数据源性能更胜一筹,Hibernate就推荐使用该连接池。C3P0连接池不仅可以自动清理不在使用的Connection,还可以自动清理ResultSet和Statement。
若需要使用C3P0连接池,则应在系统中增加如下JAR文件
1.c3p0-0.9.1.2.jar:C3P0连接池的实现
下面代码通过C3P0连接池获得数据库连接:
1 //创建连接池实例 2 ComboPooledDataSource ds = new ComboPooledDataSource(); 3 //设置连接池所需驱动 4 ds.setDriverClass("com.mysql.jdbc.Driver"); 5 //设置连接数据库的URL 6 ds.setJdbcUrl("jdbc:mysql://localhost:3306/javaee"); 7 //设置连接数据库的用户名 8 ds.setUser("root"); 9 //设置连接数据库的密码 10 ds.setPassword("pass"); 11 //设置连接池的最大连接数 12 ds.setMaxPoolSize(40); 13 //设置连接池的最小连接数 14 ds.setMinPoolSIze(2); 15 //设置连接池的初始连接数 16 ds.setInitialPoolSize(10); 17 //设置连接池的缓存Statement的最大数 18 ds.setMaxStatements(180);
通过如下代码获取数据库连接:
Connection conn = ds.getConnection();
标签:图片 返回 adb one nts nsis 独立 地方 系统
原文地址:http://www.cnblogs.com/lanshanxiao/p/7372778.html