(1)JDBC编程步骤:
第一步:加载数据库驱动。通常使用Class类的forName()静态方法来加载驱动:
//加载驱动
Class.forNamed(DriverClass);
其中DriverClass就是数据库驱动类所对应的字符串。例如,加载MySQL的驱动代码如下
//加载MySQL驱动
Class.forName(“com.mysql.jdbc.Driver”);
第二步:通过DriverManager获取数据库连接,DriverManager提供了如下方法:
//获取数据库连接
DriverManager.getConnection(String url,String user,String pass);
第三步:通过Connection对象创建Statement对象。Connection创建Statement的方法有三个:
**createStatement() 创建基本的Statement对象
**prepareStatement(String sql) 根据传入的sql语句创建预编译的Statement对象
**prepareCall(String sql) 根据传入的sql语句创建CallableStatement对象
使用Statement执行SQL语句。所有的Statement都有以下三个方法来执行SQL语句:
****excute() : 可以执行任何SQL语句,但是比较麻烦
****excuteUpdate() 主要用于执行DDL和DML语句,执行DML语句返回受SQL语句影响的行数,执行DDL语句返回0
****excuteQuary() 只能执行查询语句,执行后返回代表查询结果的ResultSet对象。
第五步:操作结果集
如果执行的SQL语句是查询语句,则执行结果将返回一个ResultSet对象,该对象里保存跑SQL语句的查询结果。程序可以通过操作该ResultSet对象来取出查询结果。ResultSet对象提供了如下两类方法:
**next() previous() first() last() beforeFirst() afterLast() absolute() 等移动记录指针的方法
**getXxx()方法获取记录指针指向行 特定列的值,该方法既可以使用列索引作为参数,也可以使用列名作为参数。使用列索引做参数的性能更好,使用列名的可读性更好。
第六步:回收数据库资源,包括关闭ResuleSet,Statement和Connection等资源。
简单的JDBC编程程序;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class ConnectionMysql {
public static void main(String[] args)
throws Exception
{
//使用DriverManager获取数据库连接,其中返回的connection就代表Java金额数据库的连接
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "root");
//使用Connection来创建一个Statement对象
Statement stmt =conn.createStatement();
//执行SQL语句
ResultSet rs=stmt.executeQuery("select s. *,teacher_name"+"from student_table s,teacher_table t"
+"where t.teacher_id=s.java_teacher");
{
//ResultSet有一系列的getXxx()方法,用于获取记录指针指向特定行、列的值,不断的用next()方法将指针移动到下一行,如果移动之后记录指针依然
//指向有效行,则next()方法返回true
while(rs.next()){
System.out.println(rs.getInt(1)+"\t"+rs.getString(2)+"\t"+rs.getString(3)+"\t"+rs.getString(4));
}
}
}
}
//本程序可以使用自动关闭资源的try语句来关闭各种数据库资源,Java7改写了Connection Statement ResultSet等接口,他们都继承了AutoCloseable接口,因此他们都有可以用try语句来关闭。
————————————————————————————————————————
执行SQL语句的方式:JDBC不仅可以实现查询,也可以实现DDL、DML等语句。
(1)实例:下面程序使用excuteUpdate()方法(没有使用excuteLargeUpdate(),因为目前MySQL驱动还不支持),来创建数据表,该实例并没有把数据库连接信息写在程序里,而是使用一个mysql.ini文件(properties文件)来保存数据库连接信息,这是比较成熟的做法–当需要将应用程序从开发环境移到生产环境时,无需修改源代码,只需要修改mysql.ini文件即可。
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;
public class ExecuteDDL {
String driver;
String url;
String user;
String pass;
public void initParam(String paramFile)
throws Exception
{
//使用Propertics类来加载属性文件
Properties pro=new Properties();
pro.load(new FileInputStream(paramFile));
driver=pro.getProperty(driver);
url=pro.getProperty(url);
user=pro.getProperty(user);
pass=pro.getProperty(pass);
}
public void createTable(String sql)
throws Exception{
//加载驱动
Class.forName(driver);
//获取数据库连接
Connection conn=DriverManager.getConnection(url, user, pass);
//使用Connection 来创建一个Statement对象
Statement st=conn.createStatement();
{
//执行DDL语句,创建数据表
st.executeUpdate(sql);
}
}
public static void main(String[] args){
ExecuteDDL ed =new ExecuteDDL();
try {
ed.initParam("mysql.ini");
ed.createTable("create table jdbc_test" + "(jdbc_id int auto_increment primary,"
+"jdbc_name varchar(255),"
+"jdbc_desc text);");
System.out.println("建表成功");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
使用excute()方法执行SQL语句:
(1)Statement的excute()几乎可以执行任何的SQL语句,但他执行SQL语句时比较麻烦,通常没有必要使用其来执行SQL语句,但是当不清楚SQL语句的类型,则只能使用excute()来执行SQL语句了。
(2)使用excute()方法执行查询语句返回值只能是boolean值,它表明该SQL语句是否返回了ResultSet对象。但是如何获取执行语句后得到的ResultSet对象呢?Statement提供了如下两个方法来获取执行结果:
***getResultSet() 获取该Statement执行查询语句所返回的ResultSet对象
***getUpdateCount() 获取该Statement()执行DML语句所影响的记录行数。
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import com.mysql.jdbc.ResultSetMetaData;
public class ExcuteSQL {
String driver;
String url;
String user;
String pass;
public void initParam(String paramFile)
throws Exception
{
//使用Propertics类来加载属性文件
Properties pro=new Properties();
pro.load(new FileInputStream(paramFile));
driver=pro.getProperty(driver);
url=pro.getProperty(url);
user=pro.getProperty(user);
pass=pro.getProperty(pass);
}
public void executeSql(String sql)
throws Exception{
//加载驱动
Class.forName(driver);
//获取数据库连接
Connection conn=DriverManager.getConnection(url, user, pass);
//使用Connection 来创建一个Statement对象
Statement st=conn.createStatement();
{
//执行sql语句,返回boolean值表示是否包含ResultSet
boolean hasResultSet=st.execute(sql);
//如果执行后有ResultSet结果集
if(hasResultSet){
//获取结果集
ResultSet rs=st.getResultSet();
{
//ResultSetMetaData是用于分析结果集的元数据入口
java.sql.ResultSetMetaData rsmd = rs.getMetaData();
int columnCount =rsmd.getColumnCount();
//迭代输出ResultSet对象
while(rs.next()){
//依次输出每列的值
for(int i=0;i
System.out.println(rs.getString(i+1)+"\t");
}
System.out.println("\n");
}
}
}
else{
System.out.println("该SQL语句影响的记录有"+st.getUpdateCount()+"条");
}
}
}
public static void main(String[] args){
ExcuteSQL es =new ExcuteSQL();
try {
es.executeSql("drop table if exists my_test");
System.out.println("-----执行建表的sql---语句");
es.executeSql("create table my_test"+"(test_id int auto_increment primary_key,"
+"test_name varchar(255))");
System.out.println("----执行插入数据的DML语句-----");
es.executeSql("insert into my_test(test_name)"+"select student_name from student_table");
System.out.println("---执行查询数据的查询语句----");
es.executeSql("select * from my_test");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
使用PraparedStatement执行SQL语句:
(1)如果经常需要反复执行一条结构相似的SQL语句,如:
insert into student_table values(null,’zzz’,1);
insert into student_table values(null,’lll’,2);
JDBC提供了PreparedStatement接口,他是Statement的子接口,它可以预编译SQL语句,预编译后的SQL语句被存储在PreparedStatement
对象中,然后可以使用该对象多次高效的执行该语句。简而言之,使用PreparedStatement比Statement的效率更高。
(2)创建PreparedStatement对象使用Connection的preparedStatement()方法,该方法需要传入一个人SQL字符串,该SQL字符串可以包含占位符。
//创建一个PreparedStatement对象
pre =conn.preparedStatement(“insert into student_table values(null,?,1)”);
(3)PreparedStatement也提供了excute() excuteUpdate() excuteQuary()三个方法来执行SQL语句,不过这三个方法无需参数,因为PreparedStatement已存储了预编译的SQL语句。
(4)使用PreparedStatement预编译SQL语句时,该SQL语句可以带占位符参数,因此在执行SQL语句之前必须为这些参数传入参数值,PreparedStatement提供了一系列的setXxx(int index,Xxx value)方法来传入参数值。
(5)使用Statement和PreparedStatement插入100条记录的对比:
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Properties;
public class PreparedStatementTest {
String driver;
String url;
String user;
String pass;
public void initParam(String paramFile)
throws Exception
{
//使用Propertics类来加载属性文件
Properties pro=new Properties();
pro.load(new FileInputStream(paramFile));
driver=pro.getProperty(driver);
url=pro.getProperty(url);
user=pro.getProperty(user);
pass=pro.getProperty(pass);
//加载驱动
Class.forName(driver);
}
public void insertUseStatement() throws Exception{
long start =System.currentTimeMillis();
//获取数据库连接
Connection conn=DriverManager.getConnection(url, user, pass);
//使用Connection创建Statement对象
Statement st =conn.createStatement();
{
//需要使用100条语句来插入100条记录
for(int i=0;i<100;i++){
st.executeUpdate("insert into student_table values("
+ "null,'姓名"+i+"',1");
}
System.out.println("使用Statement用时"+(System.currentTimeMillis() - start));
}
}
public void insertUsePreparedStatement() throws Exception{
long start =System.currentTimeMillis();
//获取数据库连接
Connection conn=DriverManager.getConnection(url, user, pass);
//使用Connection创建PreparedStatement对象
PreparedStatement ps=conn.prepareStatement(
"insert into student_table values(null,?,1)");
{
//100次为PreparedStatement的参数设值,就可以插入100条记录
for(int i=0;i<100;i++){
ps.setString(1, "姓名"+i);
ps.executeUpdate();
}
System.out.println("使用PreparedStatement用时"+(System.currentTimeMillis() - start));
}
}
public static void main(String [] args) throws Exception{
PreparedStatementTest pt=new PreparedStatementTest();
pt.initParam("mysql.ini");
pt.insertUsePreparedStatement();
pt.insertUseStatement();
}
}
使用PreparedStatement有一个很好的作用,用于防止SQL注入。(SQL注入是一种比较常见的Cracker入侵方式,它利用SQL语句的漏洞来入侵)
//下面以一个简单的登录窗口为例来说明SQL注入的结果:
import java.awt.BorderLayout;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
public class LoginFrame {
private final String PROP_FILE ="mysql.ini";
private String driver;
private String url;
private String user;
private String pass;
//登录界面的GUI组件
JFrame jf=new JFrame();
JTextField userFiled =new JTextField(20);
JTextField passFiled =new JTextField(20);
JButton loginButton=new JButton("登录");
public void init() throws Exception
{
Properties pro =new Properties();
pro.load(new FileInputStream(PROP_FILE));
driver=pro.getProperty(driver);
url=pro.getProperty(url);
user=pro.getProperty(user);
pass=pro.getProperty(pass);
//加载驱动
Class.forName(driver);
//为登录按钮添加事件监听器
loginButton.addActionListener(e ->
{
//登录成功则显示登录成功
try {
if(validate(userFiled.getText(),passFiled.getText())){
JOptionPane.showMessageDialog(jf, "登录成功");
}
else{
JOptionPane.showMessageDialog(jf, "登录失败");
}
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
});
jf.add(userFiled, BorderLayout.NORTH);
jf.add(passFiled);
jf.add(loginButton,BorderLayout.SOUTH);
jf.pack();
jf.setVisible(true);
}
private boolean validate(String userName,String userPass) throws SQLException{
//执行查询的sql语句
String sql ="select * from jdbc_test" +"where jdbc_name='" +userName+" ' and jdbc_desc='"+userPass + " ' " ;
System.out.println(sql);
Connection conn =DriverManager.getConnection(sql, user, pass);
Statement st =conn.createStatement();
ResultSet rs =st.executeQuery(sql);
{
//如果查询的ResultSet里面有超过一条记录,则登录成功
if(rs.next())
{
return true;
}
}
return false;
}
public static void main(String [] args) throws Exception{
new LoginFrame().init();
}
}
**运行程序,如果正常输入用户名、密码当然没有问题,输入正确可以正常登录,输入错误将提示失败。
但是如果输入 ’ or true or’ 可以登录成功,可以在程序运行的后台看到如下SQL语句:
//利用SQL注入后生成的SQL语句
select * from jdbc_test where jdbc_name =’ ’ or true or ’ ’ and jdbc_desc=’ ‘
因为Cracker直接输入了true,而SQL把这个true当成了直接量。
****如果把上面的validate() 方法换为使用PreparedStatement来执行验证:
private boolean validate(String userName,String userPass) throws SQLException{
Connection conn=DriverManager.getConnection(url, user, pass);
PreparedStatement pre =conn.prepareStatement(
"select * from jdbc_test where jdbc_name=? and jdbc_desc=?");
{
pre.setString(1, userName);
pre.setString(2, userPass);
ResultSet rs=pre.executeQuery();
//如果查询的ResultSet里面有超过一条记录,则登录成功
if(rs.next())
{
return true;
}
}
return false;
}
private boolean validate(String userName,String userPass) throws SQLException{
Connection conn=DriverManager.getConnection(url, user, pass);
PreparedStatement pre =conn.prepareStatement(
"select * from jdbc_test where jdbc_name=? and jdbc_desc=?");
{
pre.setString(1, userName);
pre.setString(2, userPass);
ResultSet rs=pre.executeQuery();
//如果查询的ResultSet里面有超过一条记录,则登录成功
if(rs.next())
{
return true;
}
}
return false;
}
总体来看,使用PreparedStatement比使用Statement多几个优点:
PreparedStatement预编译SQL语句,性能更好
PreparedStatement无需“拼接”SQL语句,编程更简单
PreparedStatement可以防止SQL注入,安全性更好。