Super detailed explanation of JDBC's DAO layer encapsulation idea

Mysql version: 8.0.26
Visual client: sql yog
Compiler software: IntelliJ IDEA 2019.2.4 x64
Operating environment: win10 Home Chinese version
jdk version: 1.8.0_361



1. What is DAO?

As we all know, Java is an object-oriented language, and data usually exists in the form of objects in Java. In the database, each of its data tables can be regarded as a class in Java, and each row of the table and the corresponding fields can be regarded as individual objects and object attributes. The corresponding field value is the attribute value of the object. Conversely, a Java object can also be regarded as each row record in the data table in the database, and the attribute name and attribute value of the Java object are the field name and corresponding field value of the record.

When a Java program interacts with a database through JDBC, we refer to it 访问数据库的代码封装起来,这些类称为DAOas Data Access Object in English.它相当于是一个数据访问接口,夹在业务逻辑与数据库资源中间,这样使得代码结构更加清晰、易于维护和扩展。

As shown below:
insert image description here


2. Case demonstration

Case: Try to connect to the database 0225db, code in the form of encapsulation ideas of the DAO layer, access the department table and employee table, and realize the corresponding functions of adding, deleting, modifying and checking

2.1 Prepare data

①Create employee table t_employee


Create Table

CREATE TABLE `t_employee` (
  `eid` int NOT NULL AUTO_INCREMENT COMMENT '员工编号',
  `ename` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '员工姓名',
  `salary` double NOT NULL COMMENT '薪资',
  `commission_pct` decimal(3,2) DEFAULT NULL COMMENT '奖金比例',
  `birthday` date NOT NULL COMMENT '出生日期',
  `gender` enum('男','女') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '男' COMMENT '性别',
  `tel` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号码',
  `email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邮箱',
  `address` varchar(150) DEFAULT NULL COMMENT '地址',
  `work_place` set('北京','深圳','上海','武汉') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '北京' COMMENT '工作地点',
  `hiredate` date NOT NULL COMMENT '入职日期',
  `job_id` int DEFAULT NULL COMMENT '职位编号',
  `mid` int DEFAULT NULL COMMENT '领导编号',
  `did` int DEFAULT NULL COMMENT '部门编号',
  PRIMARY KEY (`eid`),
  KEY `job_id` (`job_id`),
  KEY `did` (`did`),
  KEY `mid` (`mid`),
  CONSTRAINT `t_employee_ibfk_1` FOREIGN KEY (`job_id`) REFERENCES `t_job` (`jid`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_ibfk_2` FOREIGN KEY (`did`) REFERENCES `t_department` (`did`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_ibfk_3` FOREIGN KEY (`mid`) REFERENCES `t_employee` (`eid`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_chk_1` CHECK ((`salary` > 0)),
  CONSTRAINT `t_employee_chk_2` CHECK ((`hiredate` > `birthday`))
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

insert image description here

②Create department table t_department

CREATE TABLE `t_department` (
  `did` int NOT NULL AUTO_INCREMENT COMMENT '部门编号',
  `dname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '员工名称',
  `description` varchar(200) DEFAULT NULL COMMENT '员工简介',
  PRIMARY KEY (`did`),
  UNIQUE KEY `dname` (`dname`)
) ENGINE=InnoDB AUTO_INCREMENT=1012 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

insert image description here

2.2 Create bean package

Purpose:

Create two classes to encapsulate each row of records from t_employee and t_department

The code demonstration is as follows:

① Create a department class to encapsulate each row of records taken from t_department, and an object of the department class can be written into the t_department table


public class department {
    
    
    private Integer did;
    private String dname;
    private String description;

    public department() {
    
    
    }

    public department(int did) {
    
    
        this.did = did;
    }

    public department(int did, String dname, String description) {
    
    
        this.did = did;
        this.dname = dname;
        this.description = description;
    }

    public department(String dname, String description) {
    
    
        this.dname = dname;
        this.description = description;
    }

    public int getDid() {
    
    
        return did;
    }

    public void setDid(int did) {
    
    
        this.did = did;
    }

    public String getDname() {
    
    
        return dname;
    }

    public void setDname(String dname) {
    
    
        this.dname = dname;
    }

    public String getDescription() {
    
    
        return description;
    }

    public void setDescription(String description) {
    
    
        this.description = description;
    }

    @Override
    public String toString() {
    
    
        return "department{" +
                "did=" + did +
                ", dname='" + dname + '\'' +
                ", description='" + description + '\'' +
                '}';
    }
}

②Create an employee class to encapsulate each row of records taken from t_employee, and an object of the employee class can be written into the t_employee table

import java.math.BigDecimal;
import java.util.Date;

public class employee {
    
    
    private Integer eid;
    private String ename;
    private Double salary;
    private BigDecimal commissioPct;
    private Date birthday;
    private String gender;
    private String tel;
    private String email;
    private String address;
    private String workPlace="北京";
    private Date hiredate;
    private Integer jobId;
    private Integer mid;
    private Integer did;

    public employee() {
    
    
    }


    public employee(Integer eid) {
    
    
        this.eid = eid;
    }

    public employee(String ename, Double salary, BigDecimal commissioPct, Date birthday, String gender, String tel, String email, String address, String workPlace, Date hiredate, Integer jobId, Integer mid, Integer did) {
    
    
        this.ename = ename;
        this.salary = salary;
        this.commissioPct = commissioPct;
        this.birthday = birthday;
        this.gender = gender;
        this.tel = tel;
        this.email = email;
        this.address = address;
        this.workPlace = workPlace;
        this.hiredate = hiredate;
        this.jobId = jobId;
        this.mid = mid;
        this.did = did;
    }

    public employee(Integer eid, String ename, Double salary, BigDecimal commissioPct, Date birthday, String gender, String tel, String email, String address, String workPlace, Date hiredate, Integer jobId, Integer mid, Integer did) {
    
    
        this.eid = eid;
        this.ename = ename;
        this.salary = salary;
        this.commissioPct = commissioPct;
        this.birthday = birthday;
        this.gender = gender;
        this.tel = tel;
        this.email = email;
        this.address = address;
        this.workPlace = workPlace;
        this.hiredate = hiredate;
        this.jobId = jobId;
        this.mid = mid;
        this.did = did;
    }


    public Integer getEid() {
    
    
        return eid;
    }

    public void setEid(Integer eid) {
    
    
        this.eid = eid;
    }

    public String getEname() {
    
    
        return ename;
    }

    public void setEname(String ename) {
    
    
        this.ename = ename;
    }

    public Double getSalary() {
    
    
        return salary;
    }

    public void setSalary(Double salary) {
    
    
        this.salary = salary;
    }

    public BigDecimal getCommissioPct() {
    
    
        return commissioPct;
    }

    public void setCommissioPct(BigDecimal commissioPct) {
    
    
        this.commissioPct = commissioPct;
    }

    public Date getBirthday() {
    
    
        return birthday;
    }

    public void setBirthday(Date birthday) {
    
    
        this.birthday = birthday;
    }

    public String getGender() {
    
    
        return gender;
    }

    public void setGender(String gender) {
    
    
        this.gender = gender;
    }

    public String getTel() {
    
    
        return tel;
    }

    public void setTel(String tel) {
    
    
        this.tel = tel;
    }

    public String getEmail() {
    
    
        return email;
    }

    public void setEmail(String email) {
    
    
        this.email = email;
    }

    public String getAddress() {
    
    
        return address;
    }

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

    public String getWorkPlace() {
    
    
        return workPlace;
    }

    public void setWorkPlace(String workPlace) {
    
    
        this.workPlace = workPlace;
    }

    public Date getHiredate() {
    
    
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
    
    
        this.hiredate = hiredate;
    }

    public Integer getJobId() {
    
    
        return jobId;
    }

    public void setJobId(Integer jobId) {
    
    
        this.jobId = jobId;
    }

    public Integer getMid() {
    
    
        return mid;
    }

    public void setMid(Integer mid) {
    
    
        this.mid = mid;
    }

    public Integer getDid() {
    
    
        return did;
    }

    public void setDid(Integer did) {
    
    
        this.did = did;
    }

    @Override
    public String toString() {
    
    
        return "employee{" +
                "eid=" + eid +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", commission_pct=" + commissioPct +
                ", birthday=" + birthday +
                ", gender='" + gender + '\'' +
                ", tel='" + tel + '\'' +
                ", email='" + email + '\'' +
                ", address='" + address + '\'' +
                ", work_place='" + workPlace + '\'' +
                ", hiredate=" + hiredate +
                ", job_id=" + jobId +
                ", mid=" + mid +
                ", did=" + did +
                '}';
    }
}

2.3 Create DAO package

2.2.1 Create the BaseDAOImpl class

Purpose:

Basically, each data table should have a corresponding DAO interface and its implementation class. It is found that the codes for all table operations (addition, deletion, modification, and query) are highly repetitive, so the common code can be extracted and given to these DAOs. The implementation class can extract a common parent class called BaseDAOImpl. This class contains general methods for adding, deleting, modifying and checking.

The code demonstration is as follows:

import util.JDBCTools;
import util.JDBCTools2;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public abstract class BaseDAOImpl {
    
    

    //通用的曾,删,改操作
    //sql中包含? 参数,需要设置?的值
    //如果sgL中没有?,那么调用这个方法时,不用传args对应的实参
    // 如果sgL中有5个?,那么调用这个方法时,要给args传入对应的5个实参
    //实现流程 : java程序  ---->  mysql数据库中的数据表
    public int update(String sql,Object ...args) throws SQLException {
    
    
        //连接数据库,类似于网络编程中的socket
        //使用JDBCTools(1.0)获取的连接对象
        Connection conn = JDBCTools.getConnection();

		//使用JDBCTools2(2.0)获取的连接对象
  //      Connection conn = JDBCTools2.getConnection();

        //将sql语句预编译
        PreparedStatement pst = conn.prepareStatement(sql);

        if (args!=null && args.length>0) {
    
    
            for (int i = 0; i < args.length; i++) {
    
    
               pst.setObject(i+1,args[i]);//设置?的值
            }
        }

        int len= pst.executeUpdate();//执行sql语句并返回同步更新的记录条数
        //释放连接
//        JDBCTools.freeConnection(conn);

        return len;
    }



    //查询多个对象的信息的通用方法
    //实现流程:  Mysql数据库中的数据表的记录  --->  java程序中
    protected <T> List<T> getList(Class<T> clazz,String sql,Object ... args) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException {
    
       //泛型方法
        //通过JDBCTools工具类开启数据库连接
        //使用JDBCTools(1.0)获取的连接对象
        Connection conn = JDBCTools.getConnection();

        //使用第二版的JDBCTools2(2.0)的方法以开启数据据库的连接
     //  Connection conn = JDBCTools2.getConnection();


        PreparedStatement pst = conn.prepareStatement(sql);


        //为sql语句中的? 赋值
        if (args!=null && args.length>0) {
    
    
            for (int i = 0; i < args.length; i++) {
    
    
                pst.setObject(i+1,args[i]);//设置?的值
            }
        }

        //从数据库中查到的表t_department的数据返回来是个结果集
        ResultSet resultSet = pst.executeQuery();

        //装从结果集遍历得到的对象
        List<T>  list =new ArrayList<>();


        //得到结果集中的元数据对象【元数据:描述数据的数据】,取数据表中每一个字段名的名称,同样它返回的是元数据对象的结果集,一个表的元数据绝大数是多个,而非一个
        ResultSetMetaData metaData = resultSet.getMetaData();
        //得到元数据对象的结果集的列数,即有多少个元数据
        int columnCount = metaData.getColumnCount();


        //遍历结果集
        while (resultSet.next()){
    
       //每一行是一个对象

             //利用反射来确定每一行对象的类型,但是反射的class对象,我们并不知道,所以希望别人传参告诉我们

            T t = clazz.newInstance();//通过Class对象的newInsatnce()得到类的实例化对象

            for (int i = 1; i <= columnCount ; i++) {
    
    
                //得到每一行的某一列,value是t对象的属性的值
                Object value = resultSet.getObject(i);//获取结果集中第i列的值

                //结果集中第i列的列名称恰好对于T类中的属性
//                String columnName = metaData.getColumnClassName(i);//获取元数据结果集中的第i列的列名称

                String columnName = metaData.getColumnLabel(i);//获取元数据结果集中的第i列的列名称,如果指定了列的别名,就用别名

                //Class对象根据列名称找到对应类类型的类的某个属性
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(t,value);//设置t对象的field属性值

            }

            list.add(t);

        }

        //释放连接
//        JDBCTools.freeConnection(conn);
//        JDBCTools2.freeConnection();/这里不关闭连接,交给事务提交或回滚之后再去关闭连接
        return list;

    }

    //查询一个对象(一条记录)的通用方法
    protected <T> T getBean(Class<T> clazz,String sql,Object ... args) throws SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
    
    

        List<T> list = getList(clazz, sql, args);
        if(list!=null && list.size()>0){
    
    

            return list.get(0);

        }
        return null;
        
    }


}

2.2.2 Establish departmental DAO interface and its implementation class

①Create departmental DAO interface in DAO package

The code demonstration is as follows:

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);

}

②Create the implementation class of the departmental DAO interface in the DAO package

The code demonstration is as follows:

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 Establish employee DAO interface and its implementation class

①Create an employee DAO interface in the DAO package

The code demonstration is as follows:

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);

}

② Create the implementation class of the employee DAO interface in the DAO package

The code demonstration is as follows:

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 Establish JDBCTools tool class

Purpose:

All operations involving the database (addition, deletion, modification, and query) need to obtain the database connection first. The database connection can be obtained through the DriverManager tool class, or obtained from the database connection pool. So 把数据库连接对象的获取和释放encapsulated into this class.

2.4.1 JDBCTools1.0

Notice:

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

The original code of JDBCTools1.0 is as follows:

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();//丢进连接池
            }
        }
}

Modify the previous department table:

insert image description here

The code for testing JDBCTools1.0 is demonstrated as follows:


//模拟触发事务问题
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);//释放连接

    }
}

insert image description here
insert image description here

why? Why does the rollback fail?
Cause Analysis:

In the MySQL database, the database turns on the automatic commit mode by default. To enable transaction rollback, the premise is that the database must turn on the manual commit mode. In the above test code, the connection object obtained at the beginning of test5() is not the same as the connection object obtained by dao.

insert image description here
insert image description here
In Java, each connection is independent, they have their own state and properties . Therefore, if you set manual commit mode on a connection, only operations performed on that connection will be affected by this mode. The commit mode of other connections will not be affected, they still submit according to the default commit mode.
Therefore, in test5(), the connection object conn has turned on the manual submission mode, but the connection object used by the dao that actually performs the update operation has not been turned on, and it is still in the automatic submission mode

2.4.2 JDBCTools2.0

Purpose:

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

What is the ThreadLocal class?

The java.lang.ThreadLocal class is provided in the JDK 1.2 version. In order to solve the concurrency problem of multi-threaded programs, it is usually used to manage shared database connections and Sessions in multi-threads.

ThreadLocal is used to save a thread shared variable, because in Java, each thread object has a ThreadLocalMap<ThreadLocal, Object>, its key is a ThreadLocal, and Object is the shared variable of the thread. And this map is operated through the set and get methods of ThreadLocal. For the same static ThreadLocal, different threads can only get, set, and remove their own variables from it, without affecting the variables of other threads.

  • ThreadLocal对象.get: Get the value of the current thread shared variable in ThreadLocal.

  • ThreadLocal对象.set: Set the value of the current thread shared variable in ThreadLocal.

  • ThreadLocal对象.remove: Remove the value of the current thread shared variable in ThreadLocal.

The JDBCTools2.0 code is as follows:

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();
        }
}

Modify the previous department table:

insert image description here

The code for testing JDBCTools2.0 is demonstrated as follows:

Prerequisite:
You need to use JDBCTools2.0 to obtain the connection object in each method of the BaseDAOImpl class

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();
    }
}

insert image description here

insert image description here

2.5 Code structure analysis

insert image description here

2.6 Demonstration of test results

ps:

The following test functions are shown in detail by taking the department table as an example. The same method is used for the employee table, so the employee table is not shown in detail.

2.6.1 DAO implementation class of test department

①Try to add data to t_department

Before updating data:

insert image description here

The code demonstration is as follows:

	@Test
    //测试添加数据
    public void test01(){
    
    
        Scanner input=new Scanner(System.in);
        System.out.print("请输入部门名称:");
        //注:这里要么全用nextLine(),要么全不用
        String dname=input.nextLine();
        System.out.print("请输入部门简介:");
        String description=input.nextLine();
        department d=new department(dname,description);
        DepartmentDAOlmpl dao=new DepartmentDAOlmpl();
        boolean b = dao.addDepartment(d);
        System.out.println(b?"添加成功":"添加失败");
        input.close();
    }

insert image description here
insert image description here

②Query all data of t_department

	@Test
    //查询t_employee表中所有的信息
    public void test04(){
    
    
        DepartmentDAOlmpl dao=new DepartmentDAOlmpl();
        List<department> allDepartment = dao.getAllDepartment();
        allDepartment.forEach(t -> System.out.println(t));

    }

insert image description here

③Try to update the data and modify the values ​​of the dname and description fields in the row of the user-specified did field in the t_department table

Before update:

insert image description here
The update data code demo is as follows:

	@Test
    //测试方法test3的优化版
    //尝试修改t_department表的用户指定的did字段那行的dname与description字段的值
    public void test03_1(){
    
    
        DepartmentDAOlmpl dao=new DepartmentDAOlmpl();
        Scanner input=new Scanner(System.in);
        System.out.print("请输入要修改的部门编号:");
        int did=input.nextInt();

        input.nextLine();

        department department = dao.getBydid(did);


        System.out.print("请输入部门新名称("+department.getDname()+"):");

        //注:这里要么全用nextLine(),要么全不用
        String dname=input.nextLine();
        //如果什么都输入,就默认使用原来的部门名称
        if (dname.trim().length()==0){
    
    
            dname=department.getDname();
        }
        System.out.print("请输入部门新简介("+department.getDescription()+"):");
        String description=input.nextLine();

        if (description.trim().length()==0){
    
    
            description=department.getDescription();
        }

        input.close();

        department=new department(department.getDid(),dname,description);
        boolean b = dao.updateDepartment(department);
        System.out.println(b?"更新成功":"更新失败");

/*
      参数索引越界异常

      java.lang.RuntimeException: java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2).
       可能原因:可能是update的sql语句的标点写了中文的标点符号
*/



    }

insert image description here
insert image description here

④Try to query the data with department number 4

The code demonstration is as follows:

	 @Test
    //查询t_department表指定did的行的记录
    public void test05(){
    
    
        Scanner input=new Scanner(System.in);
        System.out.print("请输入部门编号:");
        int did=input.nextInt();
        DepartmentDAOlmpl dao=new DepartmentDAOlmpl();
        department department = dao.getBydid(did);
        System.out.println(department);
        input.close();
    }

insert image description here

⑤Try to delete the data whose department number is 1012

Before deleting:
insert image description here

The deletion code demo is as follows:

 	@Test
    //测试删除数据
    public void test02(){
    
    
        Scanner input=new Scanner(System.in);
        System.out.print("请输入部门编号:");
        //注:这里要么全用nextLine(),要么全不用
        int did=input.nextInt();
        department d=new department(did);
        DepartmentDAOlmpl dao=new DepartmentDAOlmpl();
        boolean b = dao.removeDepart(d);
        System.out.println(b?"删除成功":"删除失败");
        input.close();

    }

insert image description here
insert image description here


3. Dbutils optimized code

可引入Dbutils对DAO层的BaseDAOImpl类进行优化,其余代码不变,实现的功能亦是相同

3.1 Introduction to Dbutils

dbutils, full name commons-dbutils, is an open source JDBC tool class library provided by the Apache organization. It is a simple encapsulation of JDBC, and the learning cost is extremely low. Using dbutils can greatly simplify the workload of jdbc coding, and it will not affect program performance.

其中QueryRunner类封装了SQL的执行,是线程安全的。

  1. Adding, deleting, modifying, checking and batch processing can be realized.
  2. Considering that transaction processing needs to share Connection.
  3. The most important thing about this class is that it simplifies SQL queries. It can be used in combination with ResultSetHandler to complete most of the database operations, which can greatly reduce the amount of coding.
  4. There is no need to manually close the connection, the runner will automatically close the connection and release it to the connection pool

3.2 How to use DButils?

Prerequisites:

Import the jar package of Dbutils before using it. For the import steps, please refer to my blog Java SE: JUnit Quick Start Guide . The import steps are exactly the same as it, but the name of the jar package is different.
insert image description here

(1) update

public int update(Connection conn, String sql, Object... params) throws SQLException: Used to perform an update (insert, update, or delete) operation.

(2) insert

public T insert(Connection conn,String sql,ResultSetHandler rsh, Object... params) throws SQLException: Only INSERT statements are supported, where rsh - The handler used to create the result object from the ResultSet of auto-generated keys. Return value: An object generated by the handler. That is, the automatically generated key value

(3) Batch processing

public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句

public T insertBatch(Connection conn, String sql, ResultSetHandler rsh, Object[][] params) throws SQLException: Only INSERT statements are supported

(4) Use the QueryRunner class to implement queries

public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException: Execute a query operation. In this query, each element value in the object array is used as a replacement parameter of the query statement. This method handles the creation and closing of the PreparedStatement and ResultSet itself.

The ResultSetHandler interface is used to process java.sql.ResultSet and convert data to another form as required.

The ResultSetHandler interface provides a single method: Object handle (java.sql.ResultSet rs) The return value of this method will be used as the return value of the query() method of the QueryRunner class.

This interface has the following implementation classes that can be used:

  • BeanHandler: encapsulate the first row of data in the result set into a corresponding JavaBean instance.
  • BeanListHandler: Encapsulate each row of data in the result set into a corresponding JavaBean instance and store it in the List.
  • ScalarHandler: query a single value object
  • MapHandler: Encapsulate the first row of data in the result set into a Map, the key is the column name, and the value is the corresponding value.
  • MapListHandler: Encapsulate each row of data in the result set into a Map, and then store it in the List
  • ColumnListHandler: Store the data of a column in the result set into the List.
  • KeyedHandler(name): Encapsulate each row of data in the result set into a Map, and then store these maps in a map, whose key is the specified key.
  • ArrayHandler: Convert the first row of data in the result set into an object array.
  • ArrayListHandler: Convert each row of data in the result set into an array, and then store it in the List.

3.3 BaseDAOImply optimized version

The code demonstration is as follows:

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import util.JDBCToolsFinal;

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

public class BaseDAOImpl {
    
    

    private QueryRunner queryRunner=new QueryRunner();

    //通用的增,删,改的方法

    /**
     *
     * @param sql : 执行的sql语句
     * @param args :sql中有多少个?,就有多少个args,用来替代?并赋值
     * @return int : 影响的记录条数
     * @throws SQLException : 可能出现的sql异常
     */
    protected int update(String sql,Object ...args) throws SQLException{
    
    
        return queryRunner.update(JDBCToolsFinal.getConnection(),sql, args);

    }

    /**
     * 通用查询多个JavaBean对象的方法
     * @param clazz Class JavaBean.对象的类型的Class对象
     * @param sql  执行的sql语句
     * @param args Object ...为sqL中的?赋值,如果没有?,可以不传args实参
     * @param <T>  java.Been包中类的类型
     * @return List<T>  里面包含查询出来的多个的T对象
     * @throws Exception : 可能出现的sql异常
     */
    protected <T> List<T>  getList(Class clazz,String sql,Object ...args) throws Exception{
    
    
        return queryRunner.query(JDBCToolsFinal.getConnection(),sql,new BeanListHandler<T>(clazz),args);

    }


    /**
     * 通用查询单个JavaBean对象的方法
     * @param clazz Class JavaBean.对象的类型的Class对象
     * @param sql  执行的sql语句
     * @param args Object ...为sqL中的?赋值,如果没有?,可以不传args实参
     * @param <T>  java.Been包中类的类型
     * @return T  一个JavaBean对象
     * @throws Exception : 可能出现的sql异常
     */
    protected <T> T  getBean(Class clazz,String sql,Object ...args) throws Exception{
    
    
        return queryRunner.query(JDBCToolsFinal.getConnection(),sql,new BeanHandler<T>(clazz),args);
        /*
        BeanHandler.类的对象作用是把数据库中的表中的一条记录封装为一个JavaBean.对象,
        这条记录对应的是T类型的对象,如果创建T类型的对象,是通过clazz对象来完成,
        clazz是T类型的Class对象。
        */
    }

    //通用的查询某个值的方法
    protected Object getValue(String sql,Object ... args) throws SQLException {
    
    
        return queryRunner.query(JDBCToolsFinal.getConnection(),sql,new ScalarHandler(),args);
        //ScalarHandler对象的作用是把sgL查询结果中的单个值返回
    }

    //批处理:批量执行一组sql命令
    protected void batch(String sql,Object[][] args) throws SQLException {
    
    
        queryRunner.batch(JDBCToolsFinal.getConnection(),sql,args);
    }



}


Guess you like

Origin blog.csdn.net/siaok/article/details/130076461