Java 中级02 jdbc小结

如何操作数据库

使用客户端工具访问数据库,需要手工建立连接,输入用户名和密码登录,编写 SQL 语句,点击
执行,查看操作结果(客户端软件工具里是结果表,java代码返回的是结果集或受影响行数)
在这里插入图片描述

JDBC(Java Database Connectivity)

1.1 什么是jdbc
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。
A.是java连接数据库的标准(规范),规范了访问数据库的接口
B.提供增删改查操作

在这里插入图片描述
1.2 MySQL数据库驱动(jar包)
mysql-connector-java-5.1.X 适用于 5.X 版本
mysql-connector-java-8.0.X 适用于 8.X版本
1.3 JDBC API
JDBC 是由多个接口和类进行功能实现。
在这里插入图片描述
1.4环境搭建(导包)
在项目下新建 lib 文件夹,用于存放 jar 文件。
将 mysql 驱动mysql-connector-java-5.1.X复制到项目的 lib 文件夹中。
选中 lib 文件夹右键 Add as Libraay,点击 OK。

二、JDBC开发步骤

例子

package com.qfedu.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class InsertDemo {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        //0. 加载数据库驱动(如果是旧版 MySQL 驱动,需要手动加载)
        //MySQL8.0+ 按如下方式
        Class.forName("com.mysql.cj.jdbc.Driver");
        //MySQL8.0 之前即MySQL5.0+,按如下方式
//        Class.forName("com.mysql.jdbc.Driver");
        //1. 首先和 MySQL 之间建立连接
        //第一个参数是连接地址
        //第二个参数是数据库用户名
        //第三个参数是数据库密码
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test01?serverTimezone=Asia/Shanghai", "root", "1234");
        //2. 准备 SQL,首先创建一个 Statement,将来由 Statement 去执行 SQL
        Statement statement = connection.createStatement();
        //执行 SQL,返回值是数据库受影响的行数
        int i = statement.executeUpdate("insert into user (username,address) values ('zhangsan','广州');");
        System.out.println("i = " + i);
        //3. 释放资源
        statement.close();
        connection.close();
    }
}

在这里插入图片描述

1.加载驱动
MySQL5.0+版 Class.forName(“com.mysql.jdbc.Driver”);
MySQL8.0+版 多个cj,Class.forName(“com.mysql.cj.jdbc.Driver”);
2.连接数据库
通过 DriverManager.getConnection(url,user,password) 获取数据库连接对象
URL:jdbc:mysql://localhost:3306/database
username:root
password:1234
URL(Uniform Resource Locator) 统一资源定位符:由协议、IP、端口、SID(程序实例名称)组

3.获取发送 SQL 的对象
通过 Connection 对象获得 Statement 对象,用于对数据库进行通用访问。

Statement statement = conn.createStatement();

4.执行SQL语句
执行 SQL 语句并接收执行结果。

String sql ="INSERT INTO t_jobs(JOB_ID,JOB_TITLE,MIN_SALARY,MAX_SALARY)
VALUES('JAVA_Le','JAVA_Lecturer',4000,10000);";
int result = statement.executeUpdate(sql);//执行SQL语句并接收结果

注意:在编写 DML 语句时,一定要注意字符串参数的符号是单引号 ‘值’
DML 语句:增删改时,返回受影响行数(int 类型)
DQL 语句:查询时,返回结果数据(ResultSet 结果集)
5.处理结果

if(result == 1){
System.out.println("Success");
}

受影响行数:逻辑判断、方法返回。
查询结果集:迭代、依次获取。
6.释放资源
遵循先开后关原则,释放所使用到的资源对象。

statement.close();
conn.close();

三、ResultSet (结果集)

在执行查询 SQL 后,存放查询到的结果集数据。
3.1 接受结果集
ResultSet rs = statement.executeQuery(sql);

ResultSet rs= statement.executeQuery("SELECT * FROM user;");

3.2 遍历结果集
ResultSet 以表(table)结构进行临时结果的存储,需要通过 JDBC API 将其中数据进行依次获
取。
数据行指针:初始位置在第一行数据前,每调用一次 boolean next()方法ResultSet 的指针向
下移动一行,结果为 true,表示当前行有数据。
rs.getXxx(整数);代表根据列的编号顺序获得,从 1 开始。
rs.getXxx(“列名”);代表根据列名获得。

package com.qfedu.demo;

import java.sql.*;
import java.util.Scanner;

public class SelectDemo {
    /**
     * 根据用户名/密码去查询一个用户
     *
     * @param args
     */
    public static void main(String[] args) throws SQLException {
        //1. 建立数据库连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8", "root", "1234");
        //2. 准备 SQL
        Statement statement = connection.createStatement();
        Scanner scanner = new Scanner(System.in);

        //用户在控制台输入用户密码
        System.out.println("please enter username:");
        String username = scanner.next();
        System.out.println("please enter password:");
        String password = scanner.next();
        //3. 执行查询操作
        //这里返回一个 ResultSet 结果集,可以将之理解为一个表格
        // rs 相当于 sql 执行完成之后的表格
        String sql = "select * from user where username='" + username + "' and password='" + password + "';";
        System.out.println("sql = " + sql);
        ResultSet rs = statement.executeQuery(sql);
        //表示将游标向下移动一行,返回值为 true 表示移动之后,有数据,否则表示移动之后无数据,即表格已经遍历完成
        //第一次挪动之后,游标指向 id 为 1 的记录
        boolean next = rs.next();
        //如果返回 true,表示 rs 中是有数据的
        if (next) {
            //getInt 表示读取一个 int 类型的数据,参数 1 表示第一列
            int id = rs.getInt(1);
            //getString 表示读取一个字符串,参数表示列名,一般建议使用列名
            String uname = rs.getString("username");
            String address = rs.getString("address");
            String passwd = rs.getString("password");
            System.out.println("id = " + id);
            System.out.println("username = " + uname);
            System.out.println("address = " + address);
            System.out.println("password = " + passwd);
            System.out.println("login success");
        } else {
            System.out.println("login error");
        }
        //3. 释放资源
        rs.close();
        statement.close();
        connection.close();
    }
}

四、常见错误

java.lang.ClassNotFoundException:找不到类(类名书写错误、没有导入驱动jar包)
java.sql.SQLException:与sql语句相关的错误 (约束错误、表名列名书写错误) 建议:在
客户端工具中测试SQL语句之后再粘贴在代码中

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 原
因:列值Sting类型没有加单引号
Duplicate entry ‘1’ for key ‘PRIMARY’ 原因,主键值已存在或混乱,更改主键值或清空表
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column
‘password’ in
原因:可能输入的值的类型不对,确定是否插入的元素时对应的值的类型正确

五、SQL 注入问题

5.1 什么是SQL 注入
用户输入的数据中有 SQL 关键字或语法并且参与了 SQL 语句的编译,导致 SQL 语句编译后的条件含义为 true,一直得到正确的结果。这种现象称为 SQL 注入。
例如:拼接的sql语句输入的username和password 带有单引号或者# 号,Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(sql); 其是将sql字符串原封不动的发送到MySQL软件并执行,# 在软件中表注释,多的单引号会造成值识别错误
5.2 如何避免SQL注入
由于编写的 SQL 语句是在用户输入数据,整合后再进行编译。所以为了避免 SQL 注入的问题,我们要使 SQL 语句在用户输入数据前就已进行编译成完整的 SQL 语句,再进行填充数据。
即使用PreparedStatement 预编译

六、PreparedStatement 预编译

PreparedStatement 继承了 Statement 接口,执行 SQL 语句的方法无异。
作用:
预编译SQL 语句,效率高。
安全,避免SQL注入 。
可以动态的填充数据,执行多个同构的 SQL 语句。
语法案例:
JDBC中的所有参数都由 ?符号占位,这被称为参数标记。在执行SQL语句之前,必须为每
个参数提供值。
pstmt.setXxx(下标,值) 参数下标从 1 开始,为指定参数下标绑定值,Xxx对应值的类型

//1.预编译 SQL 语句
PreparedStatement pstmt = conn.prepareStatement("select * from user where
username=? and password=?");
//2.为参数下标赋值
pstmt.setString(1,username);
pstmt.setString(2,password);

package com.qfedu.demo;

import java.sql.*;
import java.util.Scanner;

public class PsDemo {
    public static void main(String[] args) throws SQLException {
        //1. 获取数据库连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai", "root", "1234");
        //使用 PreparedStatement,会自动的进行 SQL 的预编译,同时也能防止 SQL 注入的问题
        //这里先使用 ? 做占位符,将来在这个地方填充参数
        PreparedStatement ps = connection.prepareStatement("select * from user where username=? and password=?;");
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = scanner.next();
        System.out.println("请输入用户密码:");
        String password = scanner.next();
        //将用户输入的参数,设置给 ps
        //1 表示第一个参数,即第一个 ?
        ps.setString(1, username);
        ps.setString(2, password);
        //接下来执行查询
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败");
        }
        rs.close();
        ps.close();
        connection.close();
    }
}

七、封装增删改查方法

创建model包下的User类

package model;

public class User {
    private Integer id;
    private String username;
    private String address;
    private String password;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

创建dao包下的UserDao

package dao;

import model.DBUtils;
import model.User;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

//封装对user 的增删改查
public class UserDao {
    //添加用户
    public Integer addUser(User user) {
        Connection con = null;
        PreparedStatement ps = null;
        try {
            //获取数据库来连接
            con = DBUtils.getConnection();
            //添加用户的sql
            ps = con.prepareStatement("insert into user(username,address,password)values (?,?,?);");
            //设置参数
            ps.setString(1, user.getUsername());
            ps.setString(2, user.getAddress());
            ps.setString(3, user.getPassword());
            //执行sql
            int i = ps.executeUpdate();
            return i;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtils.close(ps);
            DBUtils.close(con);
        }
        return 0;
    }

    //删除用户
    public Integer deleteUserById(Integer id) {
        Connection con = null;
        PreparedStatement ps = null;
        try {
            con = DBUtils.getConnection();
            ps = con.prepareStatement("delete from user where id=?;");
            ps.setInt(1, id);
            int i = ps.executeUpdate();
            return i;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtils.close(ps);
            DBUtils.close(con);
        }
        return 0;
    }

    //根据id 更新用户名
    public Integer updateUsernameById(User user) {
        Connection con = null;
        PreparedStatement ps = null;
        try {
            con = DBUtils.getConnection();
            ps = con.prepareStatement("update user set username =? where id=?;");
            ps.setString(1, user.getUsername());
            ps.setInt(2, user.getId());
            int i = ps.executeUpdate();
            return i;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(ps);
            DBUtils.close(con);
        }
        return 0;
    }

    //查询所有 快捷键shift +alt+ 上下键 上下移动代码
    public List<User> getAllUsers() {
        List<User> users = new ArrayList<>();
        Connection con = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            con = DBUtils.getConnection();
            ps = con.prepareStatement("select * from user;");
            rs = ps.executeQuery();
            while (rs.next()) {
                User u = new User();
                int id = rs.getInt("id");
                String username = rs.getString("username");
                String address = rs.getString("address");
                String password = rs.getString("password");
                u.setId(id);
                u.setUsername(username);
                u.setPassword(password);
                u.setAddress(address);
                users.add(u);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtils.close(rs);
            DBUtils.close(ps);
            DBUtils.close(con);
        }
        return users;
    }
}

八、主键回填

代码不全 主要是 获取主键值给外键设值
先预编译sql语句,加上Statement.RETURN_GENERATED_KEYS参数,

PreparedStatement ps=con.prepareStatement("insert into author(name,age) values(?,?);",Statement.RETURN_GENERATED_KEYS);

补充完sql语句参数,然后执行语句
int i =ps.executeUpdate();
得到执行语句后的结果,取出id,然后将id的值插入其他表

           //获取刚刚插入记录的id
            //这个rs中,只有一行一列
            rs=ps.getGeneratedKeys();
            if(rs.next()){
                //这个就是刚刚添加的作者id
                int aid=rs.getInt(1);
                //添加图书的信息
                ps=con.prepareStatement("insert into book(name,aid) values (?,?);");
                ps.setString(1,"三国演义");
                ps.setInt(2,aid);
                //添加图书
                ps.executeUpdate();
            }

部分代码

package model;

import java.sql.*;
public class BookDemo {
    public static void main(String[] args) {
        Connection con =null;
        ResultSet rs = null;
        PreparedStatement ps = null;
        try {
            con = DBUtils.getConnection();
            //添加作者
            //Statement.RETURN_GENERATED_KEYS 表示返回添加完成后生成的 id
            ps= con.prepareStatement("insert into author(name,age) values(?,?);", Statement.RETURN_GENERATED_KEYS);
            ps.setString(1,"罗贯中");
            ps.setInt(2,80);
            //添加作者信息
            //注意下面两行顺序不能写反了,必须要先添加一条记录,然后才能获取到 id
            int i =ps.executeUpdate();
            //获取刚刚插入记录的id
            //这个rs中,只有一行一列
            rs=ps.getGeneratedKeys();
            if(rs.next()){
                //这个就是刚刚添加的作者id
                int aid=rs.getInt(1);
                //添加图书的信息
                ps=con.prepareStatement("insert into book(name,aid) values (?,?);");
                ps.setString(1,"三国演义");
                ps.setInt(2,aid);
                //添加图书
                ps.executeUpdate();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtils.close(rs);
            DBUtils.close(ps);
            DBUtils.close(con);
        }

    }
}

九、DBUtils(封装工具类)

在实际JDBC的使用中,存在着大量的重复代码:例如连接数据库、关闭数据库等这些操作!
我们需要把传统的JDBC代码进行重构,抽取出通用的JDBC工具类!以后连接任何数据库、释
放资源都可以使用这个工具类。
在这里插入图片描述
代码
一、工具类封装实现(前面代码需要的类)

package com.qfedu.demo;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;

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

/**
 * 池化技术
 * <p>
 * 用一个连接池,里边可以存放很多备用的 Connection,当需要用的时候,就拿一个出来用,用完了返回去,将来还能复用
 * <p>
 * <p>
 * <p>
 * DBCP
 * C3P0
 * <p>
 * Druid
 */
public class DBUtils {

    private static DruidDataSource ds = null;

    static {
        //创建一个数据库连接池
        ds = new DruidDataSource();
        //设置数据库连接
        ds.setUrl("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai");
        //设置数据库连接用户名
        ds.setUsername("root");
        //设置数据库密码
        ds.setPassword("1234");
        //初始化连接数
        //当 datasource 创建成功后,默认里边有 100 个可用的 connection
        ds.setInitialSize(100);
        //最小空闲数,如果当前没有人使用 connection,连接池中也会随时保证有 10 个 connection 待命
        ds.setMinIdle(10);
        //最大等待时间,当所有的 connection 都被使用时,新的操作只能等待,等待时间超过 1 分钟,就会抛出异常
        ds.setMaxWait(60000);
        //连接池最大连接数
        ds.setMaxActive(200);
    }

    public static Connection getConnection() {
        //从数据库连接池中获取一个 Connection
        //DruidPooledConnection 实现了 Connection 接口
        DruidPooledConnection connection = null;
        try {
            connection = ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }
//    public static Connection getConnection() {
//        try {
//            return DriverManager.getConnection("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai", "root", "1234");
//        } catch (SQLException e) {
//            e.printStackTrace();
//        }
//        return null;
//    }

    public static void close(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void close(PreparedStatement ps) {
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void close(Connection con) {
        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

二、跨平台工具类实现加Druid连接池 (即通过配置文件 properties)
在src 目录下新建db.properties文件
properties文件底层是hashtable 和hashmap类似

#配置驱动名称 也可以不配置会自动读取
# 如果配置的话,mysql8.0+,com.mysql.cj.jdbc.Driver
# mysql8.0 之前 com.mysql.jdbc.Driver
driverClassName =com.mysql.jdbc.Driver
#配置url地址
url=jdbc:mysql:///test01?severTimezone=Asia/Shanghai
#配置mysql的用户名
username=root
#配置mysql的密码
password =root
#连接池中初始化的连接数
initialSize=100
#最大连接数
maxActive=200
#最小空闲数
minIdle=10
#最大等待时间
maxWait=60000

使用了alibaba 的DruidDataSourceFactory 连接池类,用了ThreadLocal

ThreadLocal

ThreadLocal 特点:在哪个线程中存的数据,就在哪个线程中读取,换一个线程则读取不到
ThreadLocal 中只能保存一个对象,所以他的方法比较简单,只有三个:
set:存一个对象到 ThreadLocal中 get:读取一个对象 remove:移除对象
封装工具类流程
1.加载db.properties 配置文件,将数据读取到properties对象中
读取地址 有/表示去classpath的根目录下查找,classpath 就是 src,没有/就是在当前类所处的包的目录下查找
properties文件底层是hashtable 和hashmap类似,即和map的方法有些类似
load方法类似手动向properties 中put了7条数据,put完后,可以有get()读取 例:properties.get(“password”)
根据properties 配置文件,创建一个datasource数据源
2.首先从ThreadLocal 中获取Connection,保证同一个线程,拿到的始终是同一个Connection
a.拿到了Connection 说明之前已经有方法来获取过Connection了,即不为null,直接返回 connection 即可
b.如果拿到null 说明在当前线程中,是第一次来拿Connection ,就从连接池中拿一个 Connection,存入 ThreadLocal,并返回;
这样就能确保同一个线程中,拿到的始终是同一个 Connection
3.当将连接放回连接池的时候,需要从 ThreadLocal 中移除该连接
否则,由于 threadlocal 只有当前线程能够操作它,当当前请求结束的时候,当前线程的使命就完成了,下个请求来又是一个新线程,此时就没人能够将这个 threadlocal 中的数据移除了

package utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

public class DBUtils {
    private static DataSource ds =null;
    private static final Properties PROPERTIES = new Properties();
    /**ThreadLocal 特点:在哪个线程中存的数据,就在哪个线程中读取,换一个线程则读取不到
     * ThreadLocal 中只能保存一个对象,所以他的方法比较简单,只有三个:
     * set:存一个对象到 ThreadLocal中 get:读取一个对象 remove:移除对象
     */
    private static final ThreadLocal<Connection> THREAD_LOCAL=new ThreadLocal<>();
    static {
        try {
            //加载db.properties 配置文件,将数据读取到properties对象中
            //读取地址 有/表示去classpath的根目录下查找,classpath 就是 src,没有/就是在当前类所处的包的目录下查找
            //properties文件底层是hashtable 和hashmap类似
            // load方法类似手动向properties 中put了7条数据,put完后,可以有get()读取 例:properties.get("password")
            PROPERTIES.load(DBUtils.class.getResourceAsStream("/db.properties"));
            //根据properties 配置文件,创建一个datasource数据源
            ds= DruidDataSourceFactory.createDataSource(PROPERTIES);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 同一个线程。拿到的始终是同一个Connection
     */
    public static Connection getConnection(){
        /*首先从ThreadLocal 中获取Connection
        1.拿到了Connection 说明之前已经有方法来获取过Connection了
        2.拿到null 说明在当前线程中,是第一次来拿Connection
        * */
        Connection connection = THREAD_LOCAL.get();
        if(connection==null){
            //拿到了 null:说明在当前线程中,是第一次来拿  Connection
            try {
                //从连接池中拿一个 Connection,存入 ThreadLocal,并返回
                Connection con = ds.getConnection();
                THREAD_LOCAL.set(con);
                return con;
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

        }
        //不为null,直接返回 connection 即可,这样就能确保同一个线程中,拿到的始终是同一个 Connection
        return connection;
    }

   /* public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }*/
    /*public static Connection getConnection(){
        try {
            Class.forName("com.mysql.jdbc.Driver");
            return DriverManager.getConnection("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai","root","root");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }*/
    public static void close (ResultSet rs){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    public static void close (Connection con){
        if(con!=null){
            try {
                con.close();
                /**
                 * 当将连接放回连接池的时候,需要从 ThreadLocal 中移除该连接
                 * 否则,由于 threadlocal 只有当前线程能够操作它,当当前请求结束的时候,当前线程的使命就完成了,下个请求来又是一个新线程,此时就没人能够将这个 threadlocal 中的数据移除了
                 */
                THREAD_LOCAL.remove();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    public static void close (PreparedStatement rs){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

Druid连接池 : 在程序初始化时,预先创建指定数量的数据库连接对象存储在池中。当需要连接数据库时,从连接
池中取出现有连接;使用完毕后,也不会进行关闭,而是放回池中,实现复用,节省资源。就相当于一个线程池
要使用DButils需要导包
druid-1.2.8.jar
mysql-connector-java-5.1.27.jar

十、事务

在service层使用,try-c-f其使用流程
1.获取一个数据库连接
2.开启事务
conn.setAutoCommit(false);//true 等价于 1,false 等价于 0
3.调用并执行dao类的方法
4.提交业务
conn.commit();//手动提交事务
5.执行dao类的方法出错,回滚事务
conn.rollback();//手动回滚事务
6.关闭连接
DBUtils.close(con);
例子
dao包下的类

package dao;

import utils.DBUtils;

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

public class AccountDao {
    public Integer addMoney(String username, Double money) throws SQLException {
        Connection con =null;
        PreparedStatement ps = null;
        con =DBUtils.getConnection();
        //给指定的账户加钱
        //注意,dao 层的异常不要自己捕获,有异常直接抛出给 service 层,这样方便 service 层去处理事务。
        ps = con.prepareStatement("update account set money=money+? where username=?");
        ps.setDouble(1, money);
        ps.setString(2, username);
        //执行更新操作
        int i = ps.executeUpdate();
        DBUtils.close(ps);
        return i;
    }
    //从指定的账户减钱
    public Integer minMoney(String username, Double money) throws SQLException {
        Connection con =null;
        PreparedStatement ps = null;
        con =DBUtils.getConnection();
        ps = con.prepareStatement("update account set money=money-? where username=?");
        ps.setDouble(1, money);
        ps.setString(2, username);
        int i = ps.executeUpdate();
        DBUtils.close(ps);
        return i;
    }

}

model包下的类

package model;

import service.AccountService;

public class AccountDemo {
    public static void main(String[] args) {
        AccountService accountService=new AccountService();
        accountService.transferMoney("张三","李四",50.0);
    }
}

service下的类

package service;

import dao.AccountDao;
import utils.DBUtils;

import java.sql.Connection;
import java.sql.SQLException;

public class AccountService {

        AccountDao accountDao=new AccountDao();
        //转账的业务方法
        //一个转账操作,从张三账户转50到李四账户
        //我们希望minMoney 和addMoney 具备原子性,即两个同时成功或者失败
        //JDBC中 默认情况下,因为是有事物的,事物会自动提交或者回滚,所谓的开启事务,其实就是关闭JDBC默认的事务
        public void transferMoney(String from,String to,Double money){
            Connection con = DBUtils.getConnection();
            try {
                //注意,由于事务是在Connection 上开启的,所以需要确保当前事务中的所有JDBC 连接是同一个Connection,只有是同一个Connection,才能确保所有的数据库操作同时提交或者同时回滚
                //这句就是开启事务
                con.setAutoCommit(false);
                //调用并执行dao类的方法
                accountDao.minMoney(from,money);
                int i=1/0;
                accountDao.addMoney(to,money);
                //提交事务
                con.commit();

            } catch (SQLException e) {
                e.printStackTrace();
                //如果到这里,说明上面的执行出现异常了 =,那么此时应该回滚
                try {
                    con.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            } finally {
                DBUtils.close(con);
            }
        }

}

utils包下的类使用封装DbUtils 第二种方式,properties是在src目录下,DBUtils是在utils包下,导相关的包加依赖在lib目录下
在这里插入图片描述

DAO层 (Data Access Object 数据访问对象 )

DAO层 实现了业务逻辑与数据库访问相分离,dao层就是具体写sql语句的地方,一般一个sql语句对应一个方法

service业务逻辑层

业务 代表用户完成的一个业务功能,可以由一个或多个DAO 的调用组成。就是写功能,调用需要的DAO类的方法,执行DAO类里方法的sql语句并对返回的结果进行处理

三层架构

1.什么是三层
表示层:命名:XXXView 职责:收集用户的数据和需求、展示数据。
业务逻辑层:命名:XXXServiceImpl 职责:数据加工处理、调用DAO完成业务实现、控制事务。
数据访问层:命名:XXXDaoImpl 职责:向业务层提供数据,将业务层加工后的数据同步到数据库。
在这里插入图片描述

2.三层架构项目搭建(按开发步骤)
utils 存放工具类(DBUtils)
entity 存放实体类(Person)
dao 存放 DAO 接口(PersonDao)
impl 存放 DAO 接口实现类(PersonDaoImpl)
service 存放 service 接口(PersonService)
impl 存放 service 接口实现类(PersonServiceImpl)
view 存放程序启动类(main)
程序设计时,考虑易修改、易扩展,为Service层和DAO层设计接口,便于未来更换实现类

简化查询操作

创建一个 queryrunner 对象,增删改查操作 都通过这个对象完成
构造方法只有一个数据源参数,这个参数可传可不传
传:以后的增删改查就不需要传递数据源或者 Connection(一般都传,方法里就不需要传)

private QueryRunner queryRunner =new QueryRunner(DBUtils.getDs());

不传: 以后的增删改查 就需要传递Connection
如果不传,方法也没传就会出现数据库连接错误SQLException: QueryRunner requires a DataSource to be invoked in this way, or a Connection should be passed in
如果查询的结果是一行一列,那么可以使用ScalarHandler 自动将这一行一列读取出来,不是一行一列的话用 new ResultSetHandler<List< User>>() {} 自己手动处理结果集
select count(*) from user count()函数 查找表一共有多少行
如果数据库中的列名 和Java对象中的get/set 方法推断出来的属性名一一对应的话(与写属性的顺序无关,只要数据表列名 和属性名相等就行),那么有一个简写的类可以直接使用,new BeanHandler<>(User.class)或则和 BeanListHandler <>(model包下的实体类.class)分别表示返回的是一个User对象 ,list集合
增删改,用 queryRunner.update方法,参数是sql语句,补充sql的参数
查,用queryRunner.query 方法,参数是sql语句,处理结果集,补充sql的参数
当需要事务的时候,在首位多加一个连接池参数

看代码

package dao;

import model.User;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import utils.DBUtils;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class UserDao {
    /**
     * 创建一个 queryrunner 对象,增删改查操作 都通过这个对象完成
     * 构造方法只有一个数据源参数,这个参数可传可不传
     * 传:以后的增删改查就不需要传递数据源或者 Connection
     *不传: 以后的增删改查 就需要传递Connection
     */
    private QueryRunner queryRunner =new QueryRunner(DBUtils.getDs());
    //特殊情况简化 queryRunner.query 里的参数
    public Long getCount() throws SQLException {
        /**
         * 如果查询的结果是一行一列,那么可以使用ScalarHandler 自动将这一行一列读取出来
         * select count(*) from user count()函数 查找表一共有多少行
         */
        Long count = queryRunner.query("select count(*) from user", new ScalarHandler<Long>());
        return count;

    }

    public List<User> getAllUsers2() throws SQLException {
        return queryRunner.query("select * from user",new BeanListHandler<>(User.class));
        //BeanListHandler 表示返回的是一个list集合
    }

    /**
     * 如果数据库中的列名 和Java对象中的get/set 方法推断出来的属性名一一对应的话,那么有一个简写的类可以直接使用
     * @return
     * @throws SQLException
     */
    public User getUserById2(Integer id) throws SQLException {
        /**
         * 注意这里的第二个参数,是由系统提供的
         * 如果数据库查询结果的列名和Java对象中的get/set 方法推断出来的属性名是一一对应的,那么可以自动映射
         * 与写属性的顺序无关,只要列明 和属性名相等就行
         */
        return queryRunner.query("select * from user where id=?",new BeanHandler<>(User.class),id);
    }


    public List<User> getAllUsers() throws SQLException {
        List<User> list =queryRunner.query("select * from user", new ResultSetHandler<List<User>>() {
            @Override
            public List<User> handle(ResultSet resultSet) throws SQLException {
                List<User> users =new ArrayList<>();
                while (resultSet.next()){
                    User u =new User();
                    int id = resultSet.getInt("id");
                    String username = resultSet.getString("username");
                    String password = resultSet.getString("password");
                    String address = resultSet.getString("address");
                    u.setId(id);
                    u.setUsername(username);
                    u.setPassword(password);
                    u.setAddress(address);
                    users.add(u);
                }
                return users;
            }
        });
        return list;
    }

    /**
     * 根据id 查询一个用户对象
     */
    public User getUserById(Integer id) throws SQLException {
        /**
         * 查询用户
         * 查询用户的sql
         * resultsethandler 结果处理器
         */
        User user = queryRunner.query("select * from user where id=?;", new ResultSetHandler<User>() {
            /**
             * 自己在这里手动处理查询结果集的映射
             * @param resultSet 这个就是查询结果集
             * @return
             * @throws SQLException
             */
            @Override
            public User handle(ResultSet resultSet) throws SQLException {
                User u = new User();
                //由于是根据id查询的,所以结果集 只有一行,用if即可
                if (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String username = resultSet.getString("username");
                    String address = resultSet.getString("address");
                    String password = resultSet.getString("password");
                    u.setId(id);
                    u.setUsername(username);
                    u.setPassword(password);
                    u.setAddress(address);
                }
                return u;
            }
        }, id);
        return user;

    }
    /**
     * 根据id 删除一条记录
     */
    public Integer deleteUserById(Integer id) throws SQLException {
        return queryRunner.update("delete from user where id=?;",id);
    }
    /**
     * 根据 id 修改用户名
     * @param username
     * @param id
     * @return
     */
    public Integer updateUsernameById(String username, Integer id) throws SQLException {
        return queryRunner.update("update user set username=? where id=?", username, id);
    }
    /**
     * 添加用户
     */
    public Integer addUser(User user) throws SQLException {
        /*
        * update 方法可以做增删改
        * 1.执行的SQL,和jdbc 中一样,用?做占位符
        * 2. 可变长度的参数,和sql 中的占位符一一对应
        * f返回值表示受影响的行数*/
        int update = queryRunner.update("insert into user(username,address,password) values(?,?,?)",user.getUsername(),user.getAddress(),user.getPassword());
        return update;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值