JDBC详解 事无巨细的保姆级教程

总结

太长不看系列的总结:最有用的是(二)JDBC 工具类 JdbcUtils ;增删改查的写法可以直接看(六)改进增删改查。

一、JDBC简介

  • JDBC(全称为Java Data Base Connectivity,即java数据库连接),是由一些接口和类构成的API。
  • 是J2SE的一部分,由java.sql包和javax.sql包组成。
  • 应用程序、JDBC API、数据库驱动及数据库之间的关系,如图:
    JDBC与数据库的关系

简单来说,我们可以通过JDBC来访问并操纵数据库。
来看一下具体使用方法吧。

二、连接数据的步骤

  1. 注册驱动 (只做一次)
  2. 建立连接 (Connection)
  3. 创建SQL语句
  4. 执行SQL语句
  5. 处理执行结果 (ResultSet)
  6. 释放资源 (很重要!千万不要忘了!)

0. 导入jar包

不同数据库的厂商会把驱动写成jar包,里面写了很多 Java 的类和接口,通过调用里面的内容完成对数据库操作,不同数据库的 jar 包不同,这里我们使用MySQL。

首先来做准备工作,先要导入 MySQL 的 jar 包 :

在工程下新建一个文件夹lib,然后把 MySQL 提供的 jar 包粘贴到 lib 文件夹下,选中 jar 包,然后单击右键选择 Build Path 下的 Add to Build Path,然后上面会多一个 Referenced Libraries,它下面就有一个小奶瓶的 jar 包,并且 lib 文件夹下的 jar 包多了一摞小书本(对比5.10.txt文件可以看见 MySQL jar包左下角的小书本),这样就导入成功啦。

如果导入错误也没关系,在 Referenced Libraries 中选中你导错的 jar 包,右键选择Build Path 下的 Remove from Build Path 就可以移除了。
导入mysql jar包

1. 注册驱动

准备工作完成,现在来做连接数据库的第一步:注册驱动。

下面列举三种注册驱动的方式,建议大家使用第一种方法。

  • Class.forName(“com.mysql.jdbc.Driver”);
    在实施了java.sql.Driver接口的类上使用Class.forName()。对于MySQL Connector/J,该类的名称是com.mysql.jdbc.Driver。采用该方法,可使用外部配置文件来提供连接到数据库时将使用的驱动类名和驱动参数。推荐这种方式,不会对具体的驱动类产生依赖。

  • DriverManager.registerDriver(com.mysql.jdbc.Driver);
    会造成 DriverManager 中产生两个一样的驱动,并会对具体的驱动类产生依赖。

  • System.setProperty(“jdbc.drivers”, “driver1:driver2”);
    虽然不会对具体的驱动类产生依赖;但注册不太方便,所以很少使用。

//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());

2. 建立连接

Connection conn = DriverManager.getConnection(url, user, password);

导包别导错了,是 java.sql 下的 Connection。

这三个参数分别是数据库的地址、数据库的用户名和数据库的密码

  • url格式:
    JDBC:子协议:子名称//主机名:端口/数据库名?属性名=属性值&…
  • User,password 可以用 “ 属性名=属性值 ” 方式告诉数据库。
  • 其他参数如:useUnicode=true&characterEncoding=GBK
//2.建立连接
//Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/test?user=monty&password=greatsqldb");
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/zmb2020","root","root");
//System.out.println("connection sccess");//检测连接是否成功

3. 创建SQL语句

根据自己的需求创建 SQL 语句;
这里我们写一个查询 user 表中的数据,在这我给出我的 user 表结构,如图:user表结构

Statement 对象用 Connection 的方法 createStatement 创建。

//3.创建语句
String sql = "select * from user";
Statement st = conn.createStatement();

创建出的 Statement 对象用于执行 SQL 语句,所以第四步就是在连接的数据库中执行 SQL 语句

4. 执行语句

为了执行 sql 语句,我们把 SQL 语句将被作为参数提供给 Statement 的方法。

Statement 接口提供了三种执行 SQL 语句的方法:executeQuery、executeUpdate 和 execute。使用哪一个方法由 SQL 语句所产生的内容决定。

  • executeQuery() :用于执行返回单个结果集的语句,多用于 select 语句,在后续的操作中我们可以把查到的结果输出。
  • executeUpdate() :用于执行 insert、update 或 delete 语句以及或不返回任何内容的SQL语句,如DDL语句 ,使用这个方法的的话我们后续是不输出执行后的结果的。
  • execute() :用于执行返回多个结果集、多个更新计数或二者组合的语句。

我们需要把返回的结果集赋给 ResultSet 对象,如果没有返回结果集的话可以不创建 ResultSet 对象。

//4.执行sql语句
ResultSet rs = st.executeQuery(sql);

5. 处理结果

根据实际情况对执行完 SQL 语句的结果进行处理,查询 select 一般都需要对执行结果进行输出或进一步操作,修改 update、删除 delete 等就很少对结果进行处理。

这里我们把查询到的数据输出。

//5.处理结果
while(rs.next()) {
    System.out.println(rs.getInt("id")+","
                      +rs.getString("name")+","
                      +rs.getDate("birthday")+","
       		      +rs.getFloat("money"));
}

6. 释放资源

数据库资源开销很大,所以千万不要忘了释放资源!

//6.释放资源
rs.close();
st.close();
 

三、JDBC工具类

如果每次都写一遍上面的注册驱动啊连接啊就很麻烦,所以我们来写一个连接数据库的工具类,这样可以提高代码的复用性。

我把这个类命名为 JdbcUtils,并且让它是 final 的,这个类我不希望别人再继承或重写它了

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
//数据库连接的工具类
public final class JdbcUtils{
    //MySQL数据库的地址
    private static String url = "jdbc:mysql://127.0.0.1:3306/zmb2020";//3306端口号可以只限制为本机访问
    //MySQL数据库的登陆用户名
    private static String user = "root";
    //MySQL数据库的登录密码
    private static String password = "root";
    
    //不让别人创建当前类的实例,都通过类名来执行方法,所以方法也都是静态的
    private jdbcUtils() {}
    
    //静态代码块,实现静态加载,只要你访问到我我就给你加载
    static {
    	    try {
    	        //注册驱动
                Class.forName("com.mysql.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
     }
     
     //建立连接
     public static Connection getConnection() throws SQLException {
  return DriverManager.getConnection(url, user, password);
 }
     //关闭连接,释放资源
     public static void free(ResultSet rs,Statement st,Connection conn) {
   	 try {
    	     if(rs!=null) {
     	         rs.close();
    	     }
         } catch (SQLException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }finally {
             try {
                 if(st!=null) {
                     st.close();
                 }
             } catch (SQLException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
 }

写完了可以在 main 方法测试一下

四、实现增删改查

基于刚才写的数据库连接的工具类 JdbcUtils,我们可以实现一下数据库最基本的增删改查,用到的表还是最开始的 user

1. 查询

//查询
public static void read() {
    Connection conn =null;
    Statement st = null;
    ResultSet rs = null;  
	try {
	    conn = JdbcUtils.getConnection();
	    st = conn.createStatement();
	    rs = st.executeQuery("select id,name,birthday,money from user");
	    while(rs.next()) {
	        System.out.println(rs.getInt("id")+","
          		          +rs.getString("name")+","
          		          +rs.getDate("birthday")+","
          		          +rs.getFloat("money"));
	    }
	} catch (SQLException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}finally {
	    JdbcUtils.free(rs, st, conn);
	}
}

2. 增加

//增加
public static void save() {
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();
        st = conn.createStatement();
        int i = st.executeUpdate("insert into user(name,birthday,money)
        values('tom','1999-10-16','6000')");
        System.out.println(i);
    } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }finally {
        JdbcUtils.free(rs, st, conn);
    }
}

3. 修改

//修改
public static void update() {
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    try {
	conn = JdbcUtils.getConnection();
	st = conn.createStatement();
	String sql = "update user set money=money+10";
	int i = st.executeUpdate(sql);
	System.out.println(i);
    } catch (Exception e) {
	// TODO: handle exception
    }finally {
	JdbcUtils.free(rs, st, conn);
    }
}

4. 删除

//删除
public static void backspace(String sql) {
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    try {
	conn = JdbcUtils.getConnection();
	st = conn.createStatement();
	int i = st.executeUpdate(sql);
	System.out.println(i);
    } catch (SQLException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
    }finally {
	JdbcUtils.free(rs, st, conn);
    }
}

五、SQL注入及改进方法

1. 什么是SQL注入

举一个小例子说明一下
现有的 user 表的数据如图:
user表
通过名字来查找信息
SQL 语句:SELECT * FROM user where name = ‘Tom’
得到了正确的信息
right result
但是如果 SQL 语句:SELECT * FROM user where name = ‘’ or 1 or’ June’
即使名字错误确仍然得到信息
error result
这就是 SQL 注入的表现

关于 SQL 注入的详解可以参考博客 三种数据库的 SQL 注入详解

2.改进方法——PreparedStatement

为了避免 SQL 注入对我们项目的影响,我们使用 PreparedStatement 代替 Statement 对 SQL 语句进行传输

关于 PreparedStatement 防止 SQL 注入的原理可以参考博客 java中PreparedStatement和Statement详细讲解

PreparedStatement 支持占位符“ ? ”的使用,每个占位符的值必须在该语句执行之前,通过适当的 set 方法来提供。set 方法的第一个参数代表 SQL 语句中占位符的位置,第二个参数是实际用来代替占位符的。

PreperedStatement(从Statement扩展而来)相对Statement的优点:

  1. 没有SQL注入的问题。
  2. Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。
  3. 数据库和驱动可以对PreperedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)。

这是使用PreparedStatement实现查询语句的写法。

public static void read(String name){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
	//2.建立连接
	conn = JdbcUtils.getConnection();
	//3.创建语句 ==> 占位符"?"
	String sql = "select id,name,money,birthday from user where name=?";
	ps = conn.prepareStatement(sql);
	ps.setString(1, name);
	//4 执行语句
	rs = ps.executeQuery();
	while(rs.next()) {
	    System.out.println(rs.getString("name"));
	}
    } catch (SQLException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
    }finally{
	JdbcUtils.free(rs, ps, conn);
    }
}

这样再测试一下 SQL 注入就不会得到错误的输出了。

六、改进增删改查

对刚才(四)中的增删改查进行改进,把其中的 Statement 换成 PreparedStatement

1. 查询

public static void read(){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try{
        conn = JdbcUtils.getConnections();
        String sql = "select id,name,birthday,money from user";
        ps = conn.prepareStatement(sql);
        rs = ps.executeQuery();
        while(rs.next()){
            System.out.println(rs.getInt("id")+
            		       rs.getString("name")+
            		       rs.getDate("birthday")+
            		       rs.getFloat("money"));
        }
    } catch(SQLException e){
        e.printStackTrace();
    } finally{
        JdbcUtils.free(rs, ps, conn);
    }
}

2. 增加

public static void save(){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try{
        conn = JdbcUtils.getConnections();
        String sql = "insert into user(name,birthday,money) values(?,?,?)";
        ps = conn.prepareStatement(sql);
        ps.setString(1,name);
        ps.setDate(2,new java.sql.date(birthday.getDate()));
        ps.setFloat(3,money);
        int i = ps.executeUpdate();
        System.out.println("i="+i);
    } catch(SQLException e){
        e.printStackTrace();
    } finally{
        JdbcUtils.free(rs,ps,conn);
}

3. 修改

public static void update(){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try{
        conn = JdbcUtils.getConnections();
        String sql = "update user set name=?,birthday=?,money=? where id=?";
        ps = conn.parpareStatement(sql);
        ps.setString(1, name);
        ps.setDate(2,new java.sql.date(birthday.getDate());
        ps.setFloat(3,money);
        ps.setInt(4,id);
        int i = ps.executeUpdate();
        System.out.println("i="+i);
    } catch(SQLException e){
    	e.printStackTrace();
    } finally{
        JdbcUtils.free(rs,ps,conn);
    }

4. 删除

public static void backspace(){
    Connection conn = null;
    PrepareStatement ps = null;
    ResultSet rs = null;
    try{
    	conn = JdbcUtils.getConnections();
    	String sql = "delete from user where id=?";
    	ps = conn.prepareStatement(sql);
    	ps.setInt(1,id);
    	int i = ps.executeUpdate();
    	System.out.println();
    } catch(SQLException e){
    	e.printStackTrace();
    } fianlly{
    	JdbcUtils.free(rs,ps,conn);
    }
}

七、数据访问层DAO

正常写的应用程序里有很多部分,会有向外面展示界面的叫表示层,中间处理业务逻辑的叫业务逻辑层,还有操作数据库的叫数据库访问层,也叫DAO
J2EE三层结构框架
我们来用 JDBC 实现一下 DAO 层,实现登录访问,查询用户名和密码。

User类

首先在工程下建一个 domain 包,然后包中建一个 User 类,使 User 类和User表相互关联起来。

package com.jdbc.domain;

import java.util.Date;

public class User {
    private int id;
    private String name;
    private Date birthday;
    private float money;
    
    public int getId() {
    	return id;
    }
    public void setId(int id){
    	this.id = id;
    }
    public String getName(){
    	return name;
    }
    public void setName(String name){
    	this.name = name;
    }
    public Date getBirthday(){
    	return birthday;
    }
    public void setBirthday(Date birthday){
    	this.birthday = birthday;
    }
    public float getMoney(){
    	return money;
    }
    public void setMoney(float money){
    	this.money = money;
    }
}

UserDao接口

在工程下新建一个 dao 包,里面放接口 UserDao,里面要写对数据库进行那些操作,即声明要对数据库进行操作的函数,不需要具体实现

package com.jdbc.dao;

import com.jdbc.domain.User;

public interface UserDao {
    //添加一个新user
    public void addUser(User user);
    
    //通过id,查找某个user信息
    public User getUser(int userId);
    
    //通过登陆的账户名和密码,找到user(只是举例可以有这种写法,我的user表没有登录用户名和密码这俩字段)
    //public User findUser(String longinName,String password);
    
    //修改某个user信息
    public void update(User user);
    
    //删除某个user
    public void delete(User user);

UserDaoImpl实现类

在 dao 包下新建一个 impl 包,里面写接口 UserDao 的实现类 UserDaoImpl

package com.jdbc.dao.impl;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.jdbc.jdbcUtils;
import com.jdbc.dao.UserDao;
import com.jdbc.domain.User;

public class UserDaoImpl implements UserDao{

    @Override
    //添加一个新user
    public void addUser(User user) {
	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;
	try {
	    conn = jdbcUtils.getConnection();
	    String sql = "insert into user(name,birthday,money) values (?,?,?)";
	    ps = conn.prepareStatement(sql);
	    ps.setString(1, user.getName());
	    ps.setDate(2, new java.sql.Date(user.getBirthday().getTime()));
	    ps.setFloat(3, user.getMoney());
	    ps.executeUpdate();
	} catch (SQLException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}finally {
	    jdbcUtils.free(rs, ps, conn);
	}
    }

    @Override
    //通过id,查找某个user信息
    public User getUser(int userId){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils.getConnection();
            String sql = "select id,name,birthday,money from user where id=?";
            ps = conn.prepareStatement();
            ps.setInt(1,userId);
            rs = ps.executeQuery();
            while(rs.next()){
            	user = mappingUser(rs);
            }
        } catch(SQLException e){
            e.printStackTrace();
        } finally {
            JdbcUtils.free(rs,ps,conn);
        }
        return user;
    }
    
    //通过产生的结果集返回一个user
    private User mappingUser(ResultSet rs) throws SQLException{
    	User user = new User();
    	user.setId(rs.getInt("id"));
    	user.setName(rs.getName("name"));
    	user.setMoney(rs.getFloat("money"));
    	user.setBirthday(rs.getDate("birthday"));
    	return user;
    }
    
    @Override
    //修改某个user信息
    public void update(User user) {
    	Connection conn = null;
    	PreaparedStatement ps = null;
    	ResultSet rs = null;
    	try{
    	    conn = JdbcUtils.getConnection();
    	    String sql = "update user set name=?,birthday=?,money=? where id=?";
    	    ps = conn.prepareStatement(sql);
    	    ps.setString(1,user.getName());
    	    ps.setDate(2,new java.sql.Date(user.getBirthday().getTime()));
    	    ps.setFloat(3, user.getMoney());
    	    ps.setInt(4,user.getId());
    	    ps.executeUpdate();
    	} catch(SQLException e){
    	    e.printStackTrace();
    	} finally{
    	    JdbcUtils.free(rs, ps, conn);
    	}
    }

    @Override
    //删除某个user
    public void delete(User user) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils.getConnection();
            String sql = "delete from user where id=?";
            ps = conn.prepareStatement(sql);
            ps.setId(1,user.getId());
            ps.executeUpdate()
        } catch(SQLException e){
            e.printStackTrace();
        } finally{
            JdbcUtils.free(rs,ps,conn);
        }
    }
    
}

UserDaoTest测试类

在 dao 包下新建测试类 UserDaoTest,用来测试实现类 UserDaoImpl 中的函数

package com.jdbc.dao;

import java.util.Date;

import com.jdbc.dao.impl.UserDaoImpl;
import com.jdbc.domain.User;

public class UserDaoTest {
    public static void main(String[] args) {
        UserDao userDao = new UserDaoImpl();
        User user = new User();
	
	//测试添加一个新user
	user.setName("测试1");
	user.setBirthday(new Date());
	user.setMoney(20000.1f);
	userDao.addUser(user);
	
	//其他可以自行测试
    }
}

八、工厂模式实现DAO

配置文件daoconfig.properties

在 src 下 new 一个文件 File,命名为 daoconfig.properties,这是 DAO 的配置文件。

java 中有一个 Properties 类,这个类是专门用来操作后缀是 .properties 文件的,也就是说我们把一些东西写在后缀是 .properties 文件里,我们就可以通过这个类把文件里的东西读出来或者写进去。

我们把这个 daoconfig.properties 文件写成配置文件,在这个文件下写一个类 userDaoClass,这个类的位置是在哪呢?就是 UserDaoImpl 实现类的位置,右键 UserDaoImpl 实现类 ,选 Copy Qualified Name ,这样就获得了它的地址,然后我们只保留 com 后的内容,把“ / ”改成“ . ”,再删掉“ .java ”。

userDaoClass=com.jdbc.dao.impl.UserDaoImpl

DaoFactory

然后在 dao 包下新建一个工厂类 DaoFactory

package com.jdbc.dao;

import java.io.InputStream;
import java.util.Properties;

//约定大于配置,配置大于代码
public class DaoFactory {
    //单例模式的饿汉模式
    private static UserDao userdao = null;
    private static DaoFactory instance = new DaoFactory();
    
    private DaoFactory() {
    	try {
    	    Properties prop = new Properties();
    	    
    	    //读取配置文件
    	    //在类被加载的时候就读取这个文件
    	    InputStream inStream = DaoFactory.class.getClassLoader().getResourceAsStream("daoconfig.properties");
    	    
    	    prop.load(inStream);
    	    
    	    String userDaoClass = prop.getProperty("userDaoClass");
    	    //System.out.println(userDaoClass);
	    
	    //反射,通过路径加载出这个类
	    Class clazz = Class.forName(userDaoClass);
	    //获取UserDao对象
	    userdao = (UserDao)clazz.newInstance();
	    
    	} catch (Exception e) {
    	    e.printStackTrace();
    	}
    }
    
    public static DaoFactory getInstance() {
    	return instance;
    }
    
    public static UserDao getUserdao() {
    	return userdao;
    }

}

现在可以在测试类 UserDaoTest 中测试一下

UserDao userdao = DaoFactory.getInstance().getUserdao();
User user = new User();
user.setId(3);
//测试删除id=3的user
userdao.delete(user);

最后看一下总体结构
结构

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值