JDBC之DAO层封装思想超详解(中3)

简介: JDBC之DAO层封装思想超详解(中3)

2.2.2 建立部门DAO接口及其实现类

①在DAO包中建立部门DAO接口

代码演示如下:

import bean.department;
import java.util.List;
public interface DepartmentDAO {
    //根据部门编号修改一个部门信息
    boolean addDepartment(department d);
    //根据部门编号删除一个部门信息
    boolean removeDepart(department d);
    //根据部门编号修改部门的信息
    boolean updateDepartment(department d);
    //查询所有的部门信息
    List<department> getAllDepartment();
    //根据部门编号查询一个部门的信息
    department getBydid(int did);
}

②在DAO包中建立部门DAO接口的实现类

代码演示如下:

import bean.department;
import java.sql.SQLException;
import java.util.List;
public class DepartmentDAOlmpl extends BaseDAOImpl implements DepartmentDAO {
    @Override
    //添加记录
    public boolean addDepartment(department d) {
        String sql="INSERT INTO t_department VALUES(null,?,?)";
        try {
            int len = update(sql,d.getDname(),d.getDescription());
            return len>0;
        } catch (SQLException e) {
            //目的:不希望其他层出现sql exception,只希望在本层有异常就抛出,这时只需要在DAO层try了
            //该sql exception是编译异常,需要转换为运行时异常
            throw new RuntimeException(e);
        }
    }
    @Override
    //删除记录
    public boolean removeDepart(department d) {
        String sql="delete from t_department where did=?";
        try {
            int len =update(sql,d.getDid());
            return len>0;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    //更新记录
    public boolean updateDepartment(department d) {
        //下列update的sql是通过did作为条件定位更改对于的记录行的字段值
        String sql="UPDATE t_department SET dname=?, description=? WHERE did=?";
        try {
            int len=update(sql,d.getDname(),d.getDescription(),d.getDid());
            return len>0;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    //查询所有记录
    public List<department> getAllDepartment() {
        String sql="select * from t_department";
        try {
            return getList(department.class, sql);
        } catch (Exception e) {
           throw new RuntimeException(e);
        }
    }
    @Override
    //查询单个记录
    public department getBydid(int did) {
        String sql="select * from t_department where did=?";
        try {
            return getBean(department.class,sql,did);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2.2.3 建立员工DAO接口及其实现类

①在DAO包中建立员工DAO接口

代码演示如下:

import bean.employee;
import java.util.List;
public interface EmployeeDAO {
    //实现在t_employee中增加记录并判断实现结果(即是不是真的在表中增加了记录)
    boolean addEmployee(employee e);
    //实现在t_employee中删除记录并判断实现结果
    boolean removeEmployee(employee e);
    //实现在t_employee中修改记录并判断实现结果
    boolean updateEmployee(employee e);
    //实现在t_employee中查询所有记录并判断实现结果
    List<employee> getAllEmployee();
    //实现在t_employee中查询指定的一行记录并判断实现结果
    employee getBydid(int did);
}

②在DAO包中建立员工DAO接口的实现类

代码演示如下:

import bean.employee;
import java.sql.SQLException;
import java.util.List;
public class EmployeeDAOImpl extends BaseDAOImpl implements EmployeeDAO {
    @Override
    //添加记录
    public boolean addEmployee(employee e) {
        String sql="INSERT INTO t_employee(eid,ename,salary,commission_pct,birthday,gender,tel," +
                "email,address,work_place,hiredate,job_id,`mid`,did) VALUES(NULL,   ?,  ? " +
                ",  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?)";
        try {
            int update = update(sql, e.getEname(), e.getSalary(), e.getCommissioPct(), e.getBirthday(), e.getGender(), e.getTel(),
                    e.getEmail(), e.getAddress(), e.getWorkPlace(), e.getHiredate(), e.getJobId(), e.getMid(), e.getDid());
            return update>0;
        } catch (Exception ex) {
            //目的:不希望在其他层出现sql exception,只需要在DAO层try了
            //将编译异常转换为运行时异常
            throw new RuntimeException(ex);
        }
    }
    @Override
    //删除记录
    public boolean removeEmployee(employee e) {
        String sql="DELETE FROM t_employee WHERE eid= ?";
        try {
            int len = update(sql, e.getEid());
            return len>0;
        } catch (SQLException ex) {
            throw new RuntimeException(ex);
        }
    }
    @Override
    //更新记录
    public boolean updateEmployee(employee e) {
        String sql="UPDATE t_employee SET ename=?,salary=?,commission_pct=?,birthday=?,gender=?," +
                "tel=?,email=?,address=?,work_place=?,hiredate=?,job_id=?,`mid`=?,did=?" +
                "WHERE did=?";
        try {
            int update = update(sql, e.getEname(), e.getSalary(), e.getCommissioPct(), e.getBirthday(), e.getGender(), e.getTel(),
                    e.getEmail(), e.getAddress(), e.getWorkPlace(), e.getHiredate(), e.getJobId(), e.getMid(), e.getDid());
            return update>0;
        } catch (Exception ex) {
            //目的:不希望在其他层出现sql exception,只需要在DAO层try了
            //将编译异常转换为运行时异常
            throw new RuntimeException(ex);
        }
    }
    @Override
    //查询多个记录
    public List<employee> getAllEmployee() {
        //   java.lang.RuntimeException: java.lang.NoSuchFieldException: commission_pct
        /*解决方案:
            1.给commission_pct AS commissionPct  取别名
            2.在BaseDAOImpl类的通用的查询方法中,获取字段名的方法要处理
            String columnName = metaData.getColumnName(i);//获取第i列的列名称
            换成
            String columnNgme = metaData.getColumnLabel(i)://获第列的列名称或别名,如果指定了别名就按照别名来处理
         */
        String sql="SELECT `eid`,`ename`,`salary`,`commission_pct` AS commissioPct,`birthday`,`gender`,\n" +
                "`tel`,`email`,`address`,`work_place` AS workPlace,`hiredate`,`job_id` AS jobId,`mid`,`did` from t_employee" ;
        try {
            return getList(employee.class, sql);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    //查询单个记录
    public employee getBydid(int eid) {
        String sql="SELECT `eid`,`ename`,`salary`,`commission_pct` AS commissioPct,`birthday`,`gender`,\n" +
                "`tel`,`email`,`address`,`work_place` AS workPlace,`hiredate`,`job_id` AS jobId,`mid`,`did`\n" +
                "FROM t_employee\n" +
                "WHERE eid= ?;";
        try {
            return getBean(employee.class,sql,eid);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2.4 建立JDBCTools工具类

目的:

凡涉及对数据库的操作(增、删、改、查),都需要先获取数据库连接,数据库连接可以通过DriverManager工具类获取,也可以从数据库连接池中获取。所以 把数据库连接对象的获取和释放封装到此类中。

2.4.1 JDBCTools1.0

注意:

JDBCTools1.0版本不能处理事务问题,有缺陷。

JDBCTools1.0 原版代码如下所示:

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCTools {
        //思考:为什么dataSource后面不写代码,只是声明,
        // 因为它不是简单的new对象,数据库连接池的创建之前要涉及很多代码,只能用静态代码块来实现
        private static DataSource dataSource;
        static {
            Properties properties=new Properties();
            try {
                /*
                properties.load()里是要放输入流对象,那为何不直接放“druid.properties”的路径在其中?
                "properties.load(new FileInputStream(new File("druid.properties")))"
                不能这样写,写它的相对路径,在main方法里会默认在项目根目录下去找,找不到会报异常,test方法下会默认在该模块下去寻找,找不到会报异常
                写相对路径不行,那写该文件的绝对路径?不合适
                因为后期在部署web项目,不会这样写,后期在tomcat服务器上访问web项目时,不是在本地项目中src下找该文件,而是通过访问部署在服务器上的war包【本地web项目经过编译后的压缩包】)
                这时用类加载器去获取druid.properties,就比较合适,不容易出现问题
                 */
                properties.load(JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties"));//getResourceAsStream() 返回读取指定资源的输入流
                //properties.load()读取配置文件中的数据
                dataSource= DruidDataSourceFactory.createDataSource(properties);//创建连接池
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //在连接池中获取连接对象
        public static Connection getConnection() throws SQLException {
             return dataSource.getConnection();
        }
        //释放连接对象并归还至连接池中
        public static void freeConnection(Connection conn) throws SQLException {
            if (conn!=null){
                conn.setAutoCommit(true);//每次丢连接对象到连接池之前,都要把数据库设为自动提交模式
                conn.close();//丢进连接池
            }
        }
}

修改之前的部门表:

测试JDBCTools1.0的代码如下演示:

//模拟触发事务问题
import Dao.DepartmentDAOlmpl;
import bean.department;
import org.junit.Test;
import util.JDBCTools;
import java.sql.Connection;
import java.sql.SQLException;
public class TestJDBCTools {
    @Test
    //JDBCTools类的事务问题
    public void test05() throws SQLException {
        Connection connection = JDBCTools.getConnection(); //获取连接
        connection.setAutoCommit(false);//开启手动提交模式
        DepartmentDAOlmpl dao=new DepartmentDAOlmpl();
        try {
            System.out.println("原始数据:");
            department d1=dao.getBydid(2);//编号为2的部门
            System.out.println(d1);
            d1.setDescription("xx");
            department d2 = dao.getBydid(3);//编号为3的部门
            System.out.println(d2);
            d2.setDescription("yy");
            d2.setDname(null);//故意设置did为3的部门名称为null,制造问题,观察是否发生回滚,回滚失败了,只有一处修改了
            //问题思考:dao拿到的数据库连接对象并没有开启手动提交模式,开头的connection连接对象与前者不一致,非同一连接对象
      /*
            思考:假设jdbc开启多个数据库连接,其中一个连接开启了手动提交模式,该提交模式的变化是针对整个数据库还是针对开启手动提交模式的连接起作用?
            该提交模式的变化只是针对开启手动提交模式的连接起作用。在Java中,每个连接都是独立的,它们具有自己的状态和属性。因此,如果你在一个连接中设置了手动提交模式,
            那么只有在这个连接上执行的操作才会受到这个模式的影响。其他连接的提交模式不会受到影响,它们仍然按照默认的提交模式进行提交。
            如果你想要在多个连接之间共享事务状态,你需要使用分布式事务或其他的事务管理机制。
            */
            dao.updateDepartment(d1);
            dao.updateDepartment(d2);
            System.out.println("修改成功");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("修改失败");
            connection.rollback();
        }
        JDBCTools.freeConnection(connection);//释放连接
    }
}

why?为何会回滚失败?

原因分析:

在MySQL数据库中,数据库默认开启自动提交模式,要想开启事务回滚,前提必须是数据库要开启手动提交模式。在上述测试代码中test5()开头获取的连接对象和dao拿到的连接对象非同一个。

在Java中,每个连接都是独立的,它们具有自己的状态和属性因此,如果你在一个连接中设置了手动提交模式,那么只有在这个连接上执行的操作才会受到这个模式的影响。其他连接的提交模式不会受到影响,它们仍然按照默认的提交模式进行提交。

因此在test5()中连接对象conn开启了手动提交模式,但实际执行更新操作的dao所使用的连接对象却没开启,还是自动提交模式

2.4.2 JDBCTools2.0

目的:

为了解决JDBCTools 1.0遇到的事务处理问题,引入ThreadLocal类来解决上述问题

ThreadLocal类是什么?

JDK 1.2的版本中提供java.lang.ThreadLocal类,为解决多线程程序的并发问题,通常用于在多线程中管理共享数据库连接、Session等。

ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap<ThreadLocal, Object>,其key就是一个ThreadLocal,而Object即为该线程的共享变量。 而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

  • ThreadLocal对象.get: 获取ThreadLocal中当前线程共享变量的值。
  • ThreadLocal对象.set: 设置ThreadLocal中当前线程共享变量的值。
  • ThreadLocal对象.remove: 移除ThreadLocal中当前线程共享变量的值。

JDBCTools2.0 代码如下所示:

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCTools2 {
        //思考:为什么dataSource后面不写代码,只是声明,
        // 因为它不是简单的new对象,数据库连接池的创建之前要涉及很多代码,只能用静态代码块来实现
        private static DataSource dataSource;
        private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();
        static {
            Properties properties=new Properties();
            try {
                properties.load(JDBCTools2.class.getClassLoader().getResourceAsStream("druid.properties"));//getResourceAsStream() 返回读取指定资源的输入流
                //properties.load()读取配置文件中的数据
                dataSource= DruidDataSourceFactory.createDataSource(properties);//创建连接池
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    //获取连接对象
        public static Connection getConnection() throws SQLException {
            Connection connection=threadLocal.get();
            if (connection==null){
                connection = dataSource.getConnection();
                threadLocal.set(connection);//让当前线程存储这个共享的连接,存储到内部的threadlocalmap中,key是threadlocal对象,value是connection对象(数据库的连接)
            }
            return connection;
        }
    //释放连接对象
        public static void freeConnection() throws SQLException {
            Connection connection = threadLocal.get();
            connection.setAutoCommit(true);//释放连接之前重新恢复为自动提交模式
            connection.close();
            threadLocal.remove();
        }
}

修改之前的部门表:

测试JDBCTools2.0的代码如下演示:

前提:
需要在BaseDAOImpl类的各个方法中使用JDBCTools2.0获取连接对象

import Dao.DepartmentDAOlmpl;
import bean.department;
import org.junit.Test;
import util.JDBCTools;
import util.JDBCTools2;
import java.sql.Connection;
import java.sql.SQLException;
public class TestJDBCTools2 {
    @Test
    //解决JDBCTools类的事务问题
    public void test05() throws SQLException {
        Connection connection = JDBCTools2.getConnection(); //获取连接
        connection.setAutoCommit(false);//开启手动提交模式
        try {
            DepartmentDAOlmpl dao=new DepartmentDAOlmpl();
            System.out.println("原始数据:");
            department d1=dao.getBydid(2);//编号为2的部门
            System.out.println(d1);
            d1.setDescription("xx");
            department d2 = dao.getBydid(3);//编号为3的部门
            System.out.println(d2);
            d2.setDescription("yy");
            d2.setDname(null);//故意设置did为3的部门名称为null,制造问题,观察是否发生回滚,回滚成功,值未修改
            dao.updateDepartment(d1);
            dao.updateDepartment(d2);
            System.out.println("修改成功");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("修改失败");
            connection.rollback();
        }
//        JDBCTools.freeConnection(connection);//释放连接
        JDBCTools2.freeConnection();
    }
}

2.5 代码结构分析

相关文章
|
2月前
|
SQL Java 数据库连接
JDBC如何封装成Mybaits持久层框架只需4
本文主要讲解JDBC怎么演变到Mybatis的渐变过程,重点讲解了为什么要将JDBC封装成Mybaits这样一个持久层框架。再而论述Mybatis作为一个数据持久层框架本身有待改进之处。
17 0
|
2月前
|
SQL druid Java
JDBC技术【分页查询、数据库连接池、应用程序分层、封装通用的BaseDao】(四)-全面详解(学习总结---从入门到深化)
JDBC技术【分页查询、数据库连接池、应用程序分层、封装通用的BaseDao】(四)-全面详解(学习总结---从入门到深化)
30 0
|
3月前
|
SQL Java 数据库连接
JDBC技术【分页查询、数据库连接池、应用程序分层、封装通用的BaseDao】(四)-全面详解(学习总结---从入门到深化)(下)
JDBC技术【分页查询、数据库连接池、应用程序分层、封装通用的BaseDao】(四)-全面详解(学习总结---从入门到深化)
359 1
|
3月前
|
SQL Java 数据库连接
JDBC技术【分页查询、数据库连接池、应用程序分层、封装通用的BaseDao】(四)-全面详解(学习总结---从入门到深化)(中)
JDBC技术【分页查询、数据库连接池、应用程序分层、封装通用的BaseDao】(四)-全面详解(学习总结---从入门到深化)
28 0
|
3月前
|
SQL 存储 Java
JDBC技术【封装JDBC工具类、Statement的使用、PreparedStatement的使用(重点)、ResultSet的使用】(二)-全面详解(学习总结---从入门到深化)
JDBC技术【封装JDBC工具类、Statement的使用、PreparedStatement的使用(重点)、ResultSet的使用】(二)-全面详解(学习总结---从入门到深化)
37 0
|
8月前
|
Java 数据库连接
Java 中封装JDBC连接到JDBCUtils工具类的详解
Java 中封装JDBC连接到JDBCUtils工具类的详解
41 0
|
7月前
|
SQL Java 关系型数据库
[Mysql]JDBC篇, DAO, DBUtils, 批处理, 数据库池
面向接口编程是一种抽象的, 高效的编程思想 (就像Java里面的多态的思想. 解耦合) 解耦合 : 降低程序的耦合度(乐高坏了只需要换一个零件, 不香?), 提高程序的扩展力(比如多态) SUN公司写了一个接口 各大厂商对这个接口进行实现 这个接口实现了"多态"的编程思想 面向接口编程的程序员只需要调用JDBC即可 底层的实现是厂商写的代码, 程序员也不用去关心 厂商写的实现接口的.class文件, 也是驱动(Driver) 数据库驱动都是以.jar包的形式存在, .jar包当中有很多.class文件, 它们实现了JDBC接口
|
8月前
|
SQL 安全 Java
JDBC之DAO层封装思想超详解(下)
JDBC之DAO层封装思想超详解(下)
|
8月前
|
SQL 数据可视化 Java
JDBC之DAO层封装思想超详解(上)
JDBC之DAO层封装思想超详解(上)
|
3月前
|
SQL Java 关系型数据库
MySQL之JDBC(二)
MySQL之JDBC(二)
34 0