JDBC(2)

2022-09-19

预编译

问题: 1.sql命令书写麻烦

2.为了确保每条sql语句携带不同数据,采用字符串拼接方式,比如:

insert into t_department(department_id,department_name,loc) values("+i+",'dept+"+i+"','sichuan')

3.浪费时间:preparedStatement每次只能推送一条命令,为了推送多条命令,它需要往返多次

因此我们需要预编译。

预编译实现批处理

预编译sql相当于一个模具,在后续开发的时候,将数据填充到占位符就可以得到全新的sql命令

流程:

public class demo04 {
    //预编译实现批处理
    public static void main(String[] args) throws Exception{
        //?是占位符,一个问号代替一个值
        String sql = "insert into t_departments(DEPARTMENT_ID,DEPARTMENT_NAME,MANAGER_ID,LOCATION_ID) " +
                "values(?,?,?,?)";

        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb","root","123456");

        //添加交通工具时需要将预编译sql命令注册到preparedStatement中
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //向mysql服务器推送10条数据
        for (int i = 0; i < 10; i++) {
            //通过向预编译sql命令中填充数据生成全新sql
            preparedStatement.setString(1,i+"770");//第一个参数代表位置,第二个参数代表值
            //比如第一个命令就变成了values(1,?,?,?)
            preparedStatement.setString(2,"ikun");//部门名
            preparedStatement.setInt(3,i*10);
            preparedStatement.setInt(4,i+1);
            //在新的sql语句生成后,将新的sql语句作为子弹添加到ps的弹夹
            preparedStatement.addBatch();
        }
        //弹夹装满后,将所有sql语句一次性推送
        preparedStatement.executeBatch();
        if (preparedStatement!=null)preparedStatement.close();
        if (connection!=null)connection.close();
    }
}

发现报错: Duplicate entry ‘10’ for key ‘PRIMARY’

数据表中设置了主键(Primary Key),而主键对应的值是不允许重复的。错误提示为:你插入的记录与数据表中原有记录的主键重复了(Duplicate)。

所以我修改了department_id(主键)值,成功解决

事务控制

规则:在一个需求中,只要有任意一个sql命令无法执行的,此时应该将需求中所有执行命令都判定为执行无效

rollback和commit

/*
需求:删除部门30及及其部门下所有职员信息
 */
public class demo05 {
    public static void main(String[] args) throws Exception{
        String sql1 = "delete from emp where deptno = 30";
        String sql2 = "delete from dept where deptno = 30";

        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/t");

        //通过连接通道向mysql服务器发送命令"start transaction"
        connection.setAutoCommit(false);//如果是true,则是由mysql服务器管理sql命令
        //false则告诉mysql服务器现在由我们所拥有的事务对象表文件进行管理

        PreparedStatement preparedStatement = connection.prepareStatement("");

        //推送sql命令
        //excute方法执行可能会抛出sql异常,当有一条语句异常,进入catch执行rollback
        try {
            preparedStatement.executeUpdate(sql1);
            preparedStatement.executeUpdate(sql2);
            //如果走到这行说明所有sql命令都能正常执行,此时通知mysql服务器将本次表文件的备份删除,本次操作成功
            connection.commit();
        } catch (SQLException e) {
            //由connection通知服务将本次操作中所有表文件备份覆盖到表文件,以取消删除操作
            connection.rollback();
        }finally {//无论是取消操作还是执行成功都要关闭资源
            if (preparedStatement!=null)preparedStatement.close();
            if (connection!=null)connection.close();
        }

JDBCUtil封装

将jdbc规范下相关对象创建与销毁功能封装到方法

可以避免重复代码(利用静态代码块让注册只执行一次):

    //注册只执行一次
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Driver注册了");
    }
    //封装connection对象创建细节
    public Connection createCon() {
        Connection con=null;
        try {
            con = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb","root","123456");
        } catch (SQLException e) {
            System.out.println("connection创建失败");
            throw new RuntimeException(e);
        }
        return  con;
    }

    //封装PreparedStatement创建细节,注意要给一个sql参数
    public PreparedStatement createPs(String sql){
        PreparedStatement ps = null;
        //方法可以相互调用,直接调用createCon即可
        Connection con = createCon();//这个con是局部变量,方法调用后就消失了,所以这个方法只会创建preparedstatement对象
        try {
            con.prepareStatement(sql);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return ps;
    }

然后在封装销毁细节的时候发现,con和preparedstatement都是局部变量,在方法结束后销毁了,我们在这个类中并不能调用这两个变量,所以我们要将它们提升为这个类的全局变量:

 private Connection con=null;//提升为类文件属性,可以在类中所有方法调用
 private PreparedStatement ps = null;

这样再写close方法:

    //封装对象销毁细节
    public void close(){
        try {
            if (con!=null)con.close();
            if (ps!=null)ps.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

最后针对resultSet重载close方法

    //封装resultSet销毁细节,由于结果集在过程中不一定会产生,所以不单独写,而是写一个带参数的close(重载)
    public void close(ResultSet rs){
        try {
            if (rs!=null)rs.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

以后就可以直接new工具类对象调用createps方法传入sql即可创建preparedstatement对象。然后进行操作。

DAO封装

DAO = database access object ,数据库访问对象

作用:数据库访问对象在开发时提供针对某张表的操作细节(增删改查)

优点:

  • 在管理系统开发时,避免反复的sql命令的书写
  • 在管理系统开发时,避免反复的jdbc开发步骤书写

DAO类:能够提供数据库访问对象的类

DAO开发规则

1.一个DAO类封装的是一张表的操作细节

2.命名规则:表名+DAO,比如封装user表的操作细节–》userDao

3.DAO类所在包的命名规则:公司网站的域名.Dao com.bo.dao

下面是Dao封装具体例子(用到了util工具类):

//dao封装
public class DepartmentDao {
    //添加数据行
    public int add(String DEPARTMENT_ID,String DEPARTMENT_NAME,String MANAGER_ID,String LOCATION_ID){
        String sql = "insert into t_departments(DEPARTMENT_ID,DEPARTMENT_NAME,MANAGER_ID,LOCATION_ID)" +
                " values(?,?,?,?)";
        int result =0;
        JdbcUtil jdbcUtil = new JdbcUtil();
        PreparedStatement ps = jdbcUtil.createPs(sql);
        try {
            ps.setString(1,DEPARTMENT_ID);
            ps.setString(2,DEPARTMENT_NAME);
            ps.setString(3,MANAGER_ID);
            ps.setString(4,LOCATION_ID);
             result = ps.executeUpdate(sql);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {//无论是否异常都要关闭资源
            jdbcUtil.close();
        }
        return result;
    }

    //删除数据行
    public int delete(String DEPARTMENT_ID){
        JdbcUtil jdbcUtil = new JdbcUtil();
        int result = 0;
        String sql= "delete form t_departments where DEPARTMENT_ID=?";
        PreparedStatement ps = jdbcUtil.createPs(sql);

        try {
            ps.setString(1,DEPARTMENT_ID);
            result=ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            jdbcUtil.close();
        }
        return result;
    }

    //更新数据行
    public int update(String DEPARTMENT_ID,String MANAGER_ID,String LOCATION_ID){
        JdbcUtil jdbcUtil = new JdbcUtil();
        int result = 0;
        String sql = "update t_departments set MANAGER_ID=?,LOCATION_ID=? where DEPARTMENT_ID=?";
        PreparedStatement ps = jdbcUtil.createPs(sql);
        try {
            ps.setString(1,MANAGER_ID);
            ps.setString(2,LOCATION_ID);
            ps.setString(3,DEPARTMENT_ID);
            result = ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return result;
    }

我们在后续开发中只需要根据客户端需求调用特定的方法就可以完成操作,不需要多次写sql命令(因为dao封装)和驱动注册(因为util工具类)