JDBC的介绍续

四 JDBC的事务支持

4.1 银行转账案例演示

4.4.1 案例分析:

1.需求:一个账号fromAccount向另一个账号toAccount转入money元钱
2.分析:
    - 检查两个账号是否存在,不存在的话,结束转账行为
    - 检查转出账号的里金额是否充足,不充足,结束转账行为,充足的话,进行扣款money元
    - 转入账号进行增加money元

4.4.2 代码实现:

 

package com.jdbc.day01._05Transfer;
/*
    银行转账业务的演示:
        两个账号,一个金额。
 */

import com.jdbc.day01.util.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BankTransferDemo {
    public static void main(String[] args) throws Exception {
        boolean flag = transfer("6225113088436225","6225113088436226",1000);
    }
    /**
     *
     * @param fromAccount 转出
     * @param toAccount 转入
     * @param money 转账金额
     * @return 成功true ,失败false
     */
    public static boolean transfer(String fromAccount,String toAccount,double money){
        Connection conn=null;
        try{
            //第一步:先校验参数是否合理
            if (money<0){
                System.out.println("转账金额不能为负数");
                return false;
            }
            if (fromAccount==null || toAccount==null
                    || fromAccount.trim().length()==0
                    || toAccount.trim().length()==0){
                System.out.println("转账账号不能为空");
                return false;
            }
            //验证两个账号是否真实有效,去数据库中验证
            conn = DBUtil.getConnection();
            String sql = "select * from bank_account where account_id = ?";

            //获取预编译对象 先验证转出账号
            PreparedStatement state = conn.prepareStatement(sql);
            state.setString(1,fromAccount);
            ResultSet resultSet = state.executeQuery();
            if (!resultSet.next()){
                // 说明转出账号不存在
                System.out.println("转出账号不存在");
                return false;
            }
            PreparedStatement state2 = conn.prepareStatement(sql);
            state2.setString(1,toAccount);
            ResultSet resultSet2 = state2.executeQuery();
            if (!resultSet2.next()){
                //说明转入账号不存在
                System.out.println("转入账号不存在");
                return false;
            }
            //获取转出账号的余额
            if (resultSet.getDouble("account_balance")<money){
                //说明转出账号余额不足
                System.out.println("转出账号余额不足");
                return false;
            }

            //余额充足,可以转出
            String sql2 = "update bank_account set account_balance=? where account_id=?";
            PreparedStatement state3 = conn.prepareStatement(sql2);
            state3.setDouble(1,resultSet.getDouble("account_balance")-money);
            state3.setString(2,fromAccount);
            state3.executeUpdate();

            /*写一个异常,来模拟程序正好执行到这里,银行断电。*/
            try{
                String str = null;
                System.out.println(str.length());
            }catch (Exception e){
                //如果出现异常
                conn.rollback();
            }
        /*造成的结果:出账没问题,已经扣除相应金额,但是入账出了问题,没有执行到入职步骤,因此入账失败
         */
            PreparedStatement state4 = conn.prepareStatement(sql2);
            state4.setDouble(1,resultSet2.getDouble("account_balance")+money);
            state4.setString(2,toAccount);
            state4.executeUpdate();

            return true;
        }catch (Exception e){
            e.printStackTrace();
            try {
                conn.rollback();
            } catch (SQLException ex) {
                e.printStackTrace();
            }
        }finally {
            DBUtil.closeConnection(conn);
        }

        DBUtil.closeConnection(conn);
        return false;
    }
}

4.2 转账异常演示及事务的引入

        造成的结果:出账没问题,已经扣除相应金额,但是入账出了问题,没有执行到入职步骤,因此入账失败。在数据库层面就是,入账金额没变,但是出账金额少了。这样子是不合理的。程序员不应该让这种事情发生。 因此引入了一个关于数据库的概念: 事务(TCL)。

4.3 JDBC的事务支持

4.3.1 事务的概念:

        当一个业务需求涉及到N个DML操作时,这个业务(或者时N个DML操作)当成一个整体来处理。在处理的过程中,如果有失败或异常,我们要回到业务开始时如果成功处理,我们再将数据持久化到磁盘中。这样一个过程我们称之为一个事务。具有原子性。不可切割。

TCL(事务控制语言):提供了三个关键字,来保证这些特性:
            commit:  提交,进行持久化保存
            rollback: 回滚到事务开始的时候。
            savepoint:  设置保存点,事务在发生过程中临时保存,相当于游戏的存档功能。

什么时候会涉及到事务的概念?
            只有当使用DML语言(insert into、update、delete)时,才会触发事务。
            - 默认情况下,mysql的一个DML语句,就是一个完整的事务,会自动触发commit操作。
            - 如果你的事务是涉及到多个DML操作时,应该取消mysql的默认机制,将你的这多个DML操作,当成一个事务来处理。 

4.3.2 事务的特性:

特点:ACID
        1.原子性: 原子具有不可再切割性,即最小的。(之前物理化学中认为原子是最小的)
        2.一致性: 做这件事之前和之前的数据之和是一样的。
        3.隔离性: 这个事务被一个人做的时候,另外的人需要等待。(类似于线程的同步)
        4.持久性: 这个事务如果完成了,就必须要持久化到磁盘上。

4.3.3 MySQL事务

- 默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。
- 如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务。
    开启事务start transaction
    结束事务commit或rollback

回滚情况

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
SELECT * FROM account;
UPDATE account SET balance=balance+10000 WHERE id=2;
ROLLBACK;

提交情况

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
SELECT * FROM account;
UPDATE account SET balance=balance+10000 WHERE id=2;
COMMIT;

4.3.4 JDBC的事务支持

Connection.setAutoCommit(boolean flag):此方法可以取消事务的自动提交功能,值为false。  
Connection.commit():进行事务提交   。  
Connection.rollback():进行事务回滚。  

4.3.5 多事务的情况:

脏读:事务A读取了事务B刚刚更新的数据,但是事务B回滚了,这样就导致事务A读取的为脏数据,我们称之为脏读。
如公司某财务人员更新公司入账报表时,在DML语句中的数字后少添加了一个0,但是未提交,然后吃饭,吃饭回来,发现错误然后更正后做了提交。而在吃饭期间,老板要求秘书查看一下报表,秘书看到的是少个0的数据。这就是脏读。

不可重复读:事务A读取同一条记录两次,但是在两次之间事务B对该条记录进行了修改并提交,导致事务A两次读取的数据不一致。
它和脏读的区别是,脏读是事务A读取了另一个事务B未提交的脏数据,而不可重复读则是事务A读取了事务B提交的数据,多数情况下,不可重复读并不是问题,因为我们多次查询某个数据时,当然要以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,比如,老板让B和C分别核对事务A操作的数据,结果可能不同,老板是怀疑B呢,还是C呢? 

幻读:事务A在修改全表的数据,比如将字段age全部修改为0岁,在未提交时,事务B向表中插入或删除数据,如插入一条age为25岁的数据。这样导致事务A读取的数据与需要修改的数据不一致,就和幻觉一样。
幻读不可重复读

相同点:都是针对于另外一个已经提交的事务而言。

不同点:不可重复读是针对于同一条记录来说的(delete或update 同一条记录),而幻读是针对于一批数据来说的(insert) 

4.3.6 隔离机制

1、未提交读(read uncommitted): 就是不做隔离控制,可以读到“脏数据”,可能发生不可重复读,也可能出现幻读。 

2、提交读(read committed): 提交读就是不允许读取事务没有提交的数据
显然这种级别可以避免了脏读问题。但是可能发生不可重复读,幻读这个隔离级别是大多数数据库(除了mysql)的默认隔离级别。

3、可重复读 (repeatableread): 为了避免提交读级别不可重复读的问题,在事务中对符合条件的记录上"排他锁",这样其他事务不能对该事务操作的数据进行修改,可避免不可重复读的问题产生。由于只对操作数据进行上锁的操作,所以当其他事务插入或删除数据时,会出现幻读的问题此种隔离级别为MysqI默认的隔离级别。

4、序列化(Serializable),在事务中对表上锁,这样在事务结束前,其他事务都不能够对表数据进行操作(包括新增,删除和修改)
这样避免了脏读,不可重复读和幻读是最安全的隔离级别。但是由于该操作是堵塞的,因此会严重影响性能.

 修改当前会话的隔离机制:

set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level read uncommitted;


查询mysql的当前会话的隔离机制:
select @@tx_isolation;

4.4 修改转账代码:改为手动提交

import com.jdbc.day01.util.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BankTransferDemo {
    public static void main(String[] args) throws Exception {
        boolean flag = transfer("6225113088436225","6225113088436226",1000);
    }
    /**
     *
     * @param fromAccount 转出
     * @param toAccount 转入
     * @param money 转账金额
     * @return 成功true ,失败false
     */
    public static boolean transfer(String fromAccount,String toAccount,double money){
        Connection conn=null;
        try{
            //第一步:先校验参数是否合理
            if (money<0){
                System.out.println("转账金额不能为负数");
                return false;
            }
            if (fromAccount==null || toAccount==null
                    || fromAccount.trim().length()==0
                    || toAccount.trim().length()==0){
                System.out.println("转账账号不能为空");
                return false;
            }
            //验证两个账号是否真实有效,去数据库中验证
            conn = DBUtil.getConnection();
            String sql = "select * from bank_account where account_id = ?";

            //获取预编译对象 先验证转出账号
            PreparedStatement state = conn.prepareStatement(sql);
            state.setString(1,fromAccount);
            ResultSet resultSet = state.executeQuery();
            if (!resultSet.next()){
                // 说明转出账号不存在
                System.out.println("转出账号不存在");
                return false;
            }
            PreparedStatement state2 = conn.prepareStatement(sql);
            state2.setString(1,toAccount);
            ResultSet resultSet2 = state2.executeQuery();
            if (!resultSet2.next()){
                //说明转入账号不存在
                System.out.println("转入账号不存在");
                return false;
            }
            //获取转出账号的余额
            if (resultSet.getDouble("account_balance")<money){
                //说明转出账号余额不足
                System.out.println("转出账号余额不足");
                return false;
            }
            //因为转账涉及到两个Update语句,因此应该将这两个update语句当成一个事务来处理。
            // 所以,要在第一个update开始之前,取消自动提交操作
            conn.setAutoCommit(false); //false表示取消


            //余额充足,可以转出
            String sql2 = "update bank_account set account_balance=? where account_id=?";
            PreparedStatement state3 = conn.prepareStatement(sql2);
            state3.setDouble(1,resultSet.getDouble("account_balance")-money);
            state3.setString(2,fromAccount);
            state3.executeUpdate();
        
        /*
        事务的简单理解:就是做一件事,这件事是一个整休,要是做,就做完。要么就认为这件事没有开始,即使做到了一半,也要想办法回到做这件事之前。
         */
            PreparedStatement state4 = conn.prepareStatement(sql2);
            state4.setDouble(1,resultSet2.getDouble("account_balance")+money);
            state4.setString(2,toAccount);
            state4.executeUpdate();
            // 能只想到此处,说明转账业务能成功完成,那么就应该提交事务
            conn.commit();
            return true;
        }catch (Exception e){
            e.printStackTrace();
            try {
                conn.rollback();
            } catch (SQLException ex) {
                e.printStackTrace();
            }
        }finally {
            DBUtil.closeConnection(conn);
        }

        DBUtil.closeConnection(conn);
        return false;
    }
}

五 数据库连接池技术

5.1 连接池技术简介:

        在与数据库连接过程中,会非常消耗内存,性能大打折扣。如果每次请求都去重新连接数据库。那么,宕机的几率很高。

因此,我们可以使用连接池技术

连接池的工作原理:

        连接池对象在初始化阶段 一次性创建N个连接对象,这些连接对象存储在连接池对象中。当有请求过来时,先从连接池中寻找空闲连接对象并使用,当使用完后,将连接对象归还给连接池,而不是真正意义上断开连接。这样也可以满足成千上万个请求,同时并提高了数据库的性能。

常用的连接池技术

- dbcp      :是apache组织旗下的一个数据库连接池技术产品
- c3p0      :是一个开源的连接池技术
- druid     :是阿里的数据库连接池技术

5.2 dbcp

5.2.1 资源jar包:

commons-dbcp2-2.6.0.jar
commons-pool2-2.4.3.jar
commons-logging.jar

5.2.2 配置文件dbcp.properties

此配置文件请放在src目录下

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb?serverTimezone=Asia/Shanghai&useTimezone=true
username=root
password=123456
initialSize=5
maxTotal=50
maxIdle=10
minIdle=3
maxWaitMillis=60000

5.2.3 DBUtildbcp类型的编写

import org.apache.commons.dbcp2.BasicDataSource;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class DBCPUtil {

    private static String driver ;
    private static String url ;
    private static String username ;
    private static String password ;
    private static int maxTotal ;
    private static int maxIdle ;
    private static int minIdle ;
    private static long maxWaitMillis ;
    private static int initialSize ;

    private static BasicDataSource bds;

    static{
        try{
            //先读取配置文件
            InputStream input = DBCPUtil.class.getClassLoader().getResourceAsStream("dbcp.properties");
            Properties pro = new Properties();
            pro.load(input);
            driver = pro.getProperty("driver");
            url = pro.getProperty("url");
            username = pro.getProperty("username");
            password = pro.getProperty("password");
            maxTotal = Integer.parseInt(pro.getProperty("maxTotal"));
            maxIdle = Integer.parseInt(pro.getProperty("maxIdle"));
            minIdle = Integer.parseInt(pro.getProperty("minIdle"));
            maxWaitMillis = Long.parseLong(pro.getProperty("maxWaitMillis"));
            initialSize = Integer.parseInt(pro.getProperty("initialSize"));

            //给连接池变量初始化
            bds = new BasicDataSource();
            //将各种配置传给连接池
            bds.setDriverClassName(driver);
            bds.setUrl(url);
            bds.setUsername(username);
            bds.setPassword(password);
            bds.setMaxTotal(maxTotal);
            bds.setMaxIdle(maxIdle);
            bds.setMinIdle(minIdle);
            bds.setMaxWaitMillis(maxWaitMillis);
            bds.setInitialSize(initialSize);


        }catch (Exception e){
            e.printStackTrace();
        }
    }


    public static Connection getConnection(){
        //从连接池中获取连接对象
        Connection conn = null;
        try {
            conn = bds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
    public static void closeConnection(Connection conn){
        if(conn!= null){
            try{
                //此时因为conn这个对象是从连接池中获取的。
                // 所以,此时的close方法,并没有真正关闭连接,而是归还给连接池。
                conn.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

5.3 c3p0

5.3.1 资源jar包

c3p0-0.9.5-pre8.jar
mchange-commons-java-0.2.7.jar

5.3.2 配置文件c3p0-config.xml

配置文件请放在src目录下

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!-- 默认配置,如果没有指定则使用这个配置 -->
    <default-config>
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb?serverTimezone=Asia/Shanghai&amp;useTimezone=true&amp;useSSL=false&amp;allowPublicKeyRetrieval=true</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <!-- 当连接池中的连接用完时,C3P0一次性创建新连接的数目 -->
        <property name="acquireIncrement">10</property>
        <!-- 连接池中保留的最大连接数 -->
        <property name="maxPoolSize">50</property>
        <!-- 连接池中保留的最小连接数 -->
        <property name="minPoolSize">2</property>
        <!-- 初始化时创建的连接数,应在minPoolSize与maxPoolSize之间取值。默认为3; -->
        <property name="initialPoolSize">5</property>
        <!-- 最大空闲时间,超过空闲时间N秒的连接将被丢弃。为0或负数则永不丢弃。默认为0; -->
        <property name="maxIdleTime">600</property>
    </default-config>
</c3p0-config>

5.3.3 DBUtilc3p0类型的编写

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;

public class C3P0Utile {
    //提供一个C3o的连接池属性
    private static ComboPooledDataSource ds;
    static{
        //构造器会主动读取src的名字为c3p0-config配置文件
        ds = new ComboPooledDataSource("c3p0-config.xml");
    }
    public static Connection getConnection(){
        Connection conn=null;
        try {
            conn= ds.getConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }

    public static void closeConnection(Connection conn){
        if(conn!=null){
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Connection conn = C3P0Utile.getConnection();
        System.out.println(conn);
        C3P0Utile.closeConnection(conn);
    }
}

5.4 druid

5.4.1 资源jar包

druid-1.1.18.jar

5.4.2 配置文件druid.properties

放在src目录下。注意,前面的key值是固定写法

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb?serverTimezone=Asia/Shanghai&useTimezone=true
username=root
password=123456
maxActive=20
minIdle=3
initialSize=5
maxWait=60000

5.4.3 DBUtildruid类型的编写

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

public class DruidUtil {
    private  static DataSource ds;

    static{
        try{
            InputStream io = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties pro = new Properties();
            pro.load(io);

            //提供了一个工厂类,里面提供了一个createDataSource(Properties pro)方法
            //会自动解析prop里的各种键值对,进行赋值
            ds= DruidDataSourceFactory.createDataSource(pro);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static Connection getConnection(){
        Connection conn=null;
        try{
            conn=ds.getConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
        return conn;
    }
    public static void closeConnection(Connection conn){
        if(conn!=null){
            try{
                conn.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Connection conn = DruidUtil.getConnection();
        System.out.println(conn);
        DruidUtil.closeConnection(conn);
    }
}

六 DAO设计模式

6.1 DAO简介

- DAO数据访问对象(Data Access Object)的简写。
- 建立在数据库与业务层之间,封装所有对数据库的访问操作,我们也可称之为持久层
- 目的: 将数据访问逻辑和业务逻辑分开

 如下图所示

一个DAO设计模式包含以下内容

1. 定义实体类:  通过对象关系映射(ORM)将数据库的表结构映射成java类型;表中的每一条记录映射成类的实例。用于数据的传递。

2. 定义一个接口:在此接口中,定义应用程序对此表的所有访问操作,如增,删,改、查,等方法。

3. 定义接口的实现类:实现接口中的所有抽象方法。

4. 定义一个DAO工厂类型:用于返回接口实例 这样,开发人员只需要使用DAO接口即可,具体逻辑就变得透明了,无需了解内部细节。

扩展:项目的包名命名规则

 规范: com.域名.项目名称.模块名称

com.ssy.jdbc03.util
com.ssy.jdbc03.entity
com.ssy.jdbc03.test
com.ssy.jdbc03.dao
com.ssy.jdbc03.dao.impl
com.ssy.jdbc03.service

6.2 DAO的案例示范

6.2.1 创建项目,导入相关资源

6.2.2 编写工具类DBUtil

记得导入druid.properties文件

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

public class DruidUtil {
    private  static DataSource ds;

    static{
        try{
            InputStream io = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties pro = new Properties();
            pro.load(io);

            //提供了一个工厂类,里面提供了一个createDataSource(Properties pro)方法
            //会自动解析prop里的各种键值对,进行赋值
            ds= DruidDataSourceFactory.createDataSource(pro);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static Connection getConnection(){
        Connection conn=null;
        try{
            conn=ds.getConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
        return conn;
    }
    public static void closeConnection(Connection conn){
        if(conn!=null){
            try{
                conn.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Connection conn = DruidUtil.getConnection();
        System.out.println(conn);
        DruidUtil.closeConnection(conn);
    }
}

6.2.3 编写实体类

import java.sql.Date;
import java.util.Objects;

/**
 *  根据ORM对象关系映射,为数据库中的emp表设计一个实体类 Employee
 *  1. 表的字段 --->类的属性
 *  2. 表的每一行记录 ---> 类的具体实例
 *
 * 建议: 数据库的数值类型,在java中映射成对应的包装类型
 */

public class Employee {
    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;

    public Employee() {};
    public Employee(Integer empno, String ename, String job, Integer mgr, Date hiredate, Double sal, Double comm, Integer deptno) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.mgr = mgr;
        this.hiredate = hiredate;
        this.sal = sal;
        this.comm = comm;
        this.deptno = deptno;
    }

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

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

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

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

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        Employee employee = (Employee) object;
        return Objects.equals(empno, employee.empno) && Objects.equals(ename, employee.ename) && Objects.equals(job, employee.job) && Objects.equals(mgr, employee.mgr) && Objects.equals(hiredate, employee.hiredate) && Objects.equals(sal, employee.sal) && Objects.equals(comm, employee.comm) && Objects.equals(deptno, employee.deptno);
    }

    @Override
    public int hashCode() {
        return Objects.hash(empno, ename, job, mgr, hiredate, sal, comm, deptno);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", sal=" + sal +
                ", comm=" + comm +
                ", deptno=" + deptno +
                '}';
    }
}

6.2.4 定义接口

import com.youcai.emp.vo.Employee;

import java.util.List;

/**
 *  根据实体类Employee和数据库中的emp表,来设计DAO层的接口类型
 *  该接口中实际上就是封装了一些与数据库进行交互的方法。
 *  增,删,改,查
 */

public interface EmployeeDao {
    /**
     * 从java的面相对象思想考虑,前段提供了一个员工的所有的零散信息
     * 传入服务端后,应该封装到实体类的具体实例里。然后在DAO蹭,我们
     * 将具体实例保存到数据库中,所以,方法带实体类参数
     * @param employee
     */
    void addEmployee(Employee employee);

    /**
     * 删除某一个员工,一定是前段传入了一个代表该员工的唯一标识。即主键字段
     *
     * 因此该方法也应该带参数
     * @param empno
     */
    void deleteEmployee(Integer empno);

    /**
     * 修改一个员工的信息,在前段的员工信息的文本框中,不一定是修改了什么信息
     * 因此,后端就应该考虑全面。认为全都可能被修改了,所以重新封装成对象。
     * 传入方法
     *
     * 注意: 形参已经是修改后的数据了。
     * @param employee
     */
    void updateEmployee(Employee employee);

    /**
     * 通过唯一标识,查询一个员工的所有信息,结果封装成实体类对象
     * @param empno
     * @return
     */
    Employee findEmployeeById(Integer empno);

    /**
     * 查询表中的所有员工信息,封装成集合,不需要形参,因为sql语句不需要: select * from 表名;
     * 每一条记录都应该封装成实体类对象。
     * 多个对象,应该存储到集合容器中,所以返回值应该是一个集合
     *
     * @return
     */
    List<Employee> findAll();

    /**
     * 分页查询
     * select ... from emp order by ... limit (page-1)*pageSize,pageSize
     */
    List<Employee> findByPage(Integer page,Integer pageSize);


}

6.2.5 编写实现类

import com.youcai.emp.dao.EmployeeDao;
import com.youcai.emp.util.DruidUtil;
import com.youcai.emp.vo.Employee;

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

/**
 * 定义EmployeeDao接口的实现类型
 */

public class EmployDaoImpl implements EmployeeDao {

    @Override
    public void addEmployee(Employee employee) {
        Connection conn = null;
        try{
            conn= DruidUtil.getConnection();
            String sql = "insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values(?,?,?,?,?,?,?,?)";
            PreparedStatement prep = conn.prepareStatement(sql);
            prep.setInt(1,employee.getEmpno());
            prep.setString(2,employee.getEname());
            prep.setString(3,employee.getJob());
            prep.setInt(4,employee.getMgr());
            prep.setDate(5, employee.getHiredate());
            prep.setDouble(6,employee.getSal());
            prep.setDouble(7,employee.getComm());
            prep.setInt(8,employee.getDeptno());
            prep.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }




    }

    @Override
    public void deleteEmployee(Integer empno) {
        Connection conn =null;
        try{
            conn=DruidUtil.getConnection();
            String sql = "delete from emp where empno=?";
            PreparedStatement prep = conn.prepareStatement(sql);
            prep.setInt(1,empno);
            prep.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }

    }

    @Override
    public void updateEmployee(Employee employee) {
        Connection conn =null;
        try{
            conn=DruidUtil.getConnection();
            //获取预编译语句对象
            String sql = "update emp set ename=?,job=?,mgr=?,hiredate=?,sal=?,comm=?,deptno=? where empno=?";
            PreparedStatement prep = conn.prepareStatement(sql);
            prep.setString(1,employee.getEname());
            prep.setString(2,employee.getJob());
            prep.setInt(3,employee.getMgr());
            prep.setDate(4,new java.sql.Date(employee.getHiredate().getTime()));
            prep.setDouble(5,employee.getSal());
            prep.setDouble(6,employee.getComm());
            prep.setInt(7,employee.getDeptno());
            prep.setInt(8,employee.getEmpno());
            prep.executeUpdate();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }

    }

    @Override
    public Employee findEmployeeById(Integer empno) {
        Connection conn = null;
        Employee employee = null;
        try{
            conn=DruidUtil.getConnection();
            String sql = "select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where empno=?";
            PreparedStatement prep = conn.prepareStatement(sql);
            prep.setInt(1,empno);
            ResultSet res = prep.executeQuery();
            if(res.next()){
                 employee = new Employee(res.getInt(1),res.getString(2),
                        res.getString("job"),res.getInt("mgr"),res.getDate("hiredate"),
                        res.getDouble("sal"),res.getDouble("comm"),res.getInt("deptno"));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }
        return employee;
    }

    @Override
    public List<Employee> findAll() {
        List<Employee> employees = new ArrayList<>();
        Connection conn = null;
        try{
            conn=DruidUtil.getConnection();
            String sql = "select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp";
            PreparedStatement prep = conn.prepareStatement(sql);
            ResultSet res = prep.executeQuery();
            while(res.next()){
                 employees.add(new Employee(res.getInt("empno"),res.getString("ename"),
                        res.getString("job"),res.getInt("mgr"),res.getDate("hiredate"),
                        res.getDouble("sal"),res.getDouble("comm"),res.getInt("deptno")));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }
        return employees;
    }

    @Override
    public List<Employee> findByPage(Integer page, Integer pageSize) {
        Connection conn = null;
        List<Employee> employees = new ArrayList<>();
        try{
            conn=DruidUtil.getConnection();
            String sql = "select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp limit ?,?";
            PreparedStatement prep = conn.prepareStatement(sql);
            prep.setInt(1,(page-1)*pageSize);
            prep.setInt(2,pageSize);
            ResultSet res = prep.executeQuery();
            while(res.next()){
                employees.add(new Employee(res.getInt(1),res.getString(2),
                        res.getString("job"),res.getInt("mgr"),res.getDate("hiredate"),
                        res.getDouble("sal"),res.getDouble("comm"),res.getInt("deptno")));
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }


        return employees;
    }
}

6.2.6 编写DAO工厂类

import com.youcai.emp.dao.DeptDao;
import com.youcai.emp.dao.EmployeeDao;
import com.youcai.emp.dao.impl.DeptDaoImpl;
import com.youcai.emp.dao.impl.EmployDaoImpl;

/**
 * 定义一个持久层的工厂类型,
 * 在该类型中提供一些静态工具方法,用于获取每个实体类对应的DAO接口实例
 *
 */

public class DaoFactory {
    private static EmployeeDao employeeDao;
    private static DeptDao deptDao;

    //私有化构造器,防止在外部直接new对象
    private DaoFactory(){
    }

    //提供一个共有的静态方法,来返回接口的实例对象
    public static EmployeeDao getEmployeeDaoInstance(){
        if (employeeDao == null){
            employeeDao = new EmployDaoImpl();
        }
        return employeeDao;
    }

    public static DeptDao getDeptDaoInstance(){
        if (deptDao == null){
            deptDao = new DeptDaoImpl();
        }
        return deptDao;
    }

}

6.2.7 编写测试类

import com.youcai.emp.dao.EmployeeDao;
import com.youcai.emp.dao.impl.EmployDaoImpl;
import com.youcai.emp.util.DaoFactory;
import com.youcai.emp.vo.Employee;
import org.junit.Test;

import java.sql.Date;
import java.util.List;

public class employeeDaoTest {

    @Test
    public void testAddEmployee(){
        Employee e1 = new Employee(1111,"qpz","hero",3619, Date.valueOf("2024-08-01"), 15000.0,1002.0,20);

        EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();
        dao.addEmployee(e1);
    }

    @Test
    public void testDeleteEmployee(){
        EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();
        dao.deleteEmployee(10000);
    }


    /**
     * 测试修改员工
     */
    @Test
    public void testUpdateEmployee(){
        // 创建一个员工对象,来模拟已经修改完后并封装的操作
        Employee e1 = new Employee(10000,"superman","hero",7499, Date.valueOf("2024-08-01"), 10000.0,100.0,10);
        //调用修改方法,直接提交到数据库
        //通过工厂类型里的工具方法,获取EmployeeDao实例
        EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();
        dao.updateEmployee(e1);
    }

    @Test
    public void testFindEmployeeById(){
        EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();
        Employee e1 = dao.findEmployeeById(7934);
        System.out.println(e1);
    }

    @Test
    public void testFindAll(){
        EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();
        List<Employee> fall = dao.findAll();
        for (Employee e:fall){
            System.out.println(e);
        }
    }

    @Test
    public void testFindByPage(){
        EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();
        List<Employee> page = dao.findByPage(1, 5);
        page.forEach(System.out::println);
    }
    /**
     * junit是用来进行测试的。其中有很多注解。
     * @Test: 用于测试方法,相当于man方法,可以直接运行
     * @Before: 位于方法上,有该注解的方法,会优先于有@Test注解的方法执行
     * @After: 位于方法上,有该注解的方法,会在有@Test注解的方法执行完之后执行
     */

    @Test
    public void test1(){
        System.out.println(1+2);
    }

    @Test
    public void test2(){
        System.out.println(Math.random());
    }


}

注解测试所需的包

hamcrest-core-1.3.jar
junit-4.12.jar

七 dbutils第三方工具类的使用

7.1 简介

- 此工具封装了DAO层(持久层)的逻辑。减少了开发周期。
- jar包:commons-dbutils-1.7.jar
- 常用API: 
1. QueryRunner类型:可以直接使用连接池技术来操作数据库,进行增删改查
   构造器:QueryRunner(DataSource ds)
             返回一个指定数据库连接池得QueryRunner对象
   非静态方法:query(String sql, ResultSetHandler<T> rsh)
            通过sql,及其ReusltSetHandler的子类型来获取数据并封装成相应对象
2. ResultSetHandler:关于结果集的一个接口。
    其实现类如下:
    BeanHandler:将查询到的数据的第一条封装成实体类对象
    BeanListHandler:将查询到的数据的第一条封装成实体类对象的集合 

7.2 代码测试:

public class Testdbutils {
    @Test
    public void testFindOne() throws SQLException {
        QueryRunner qr = new QueryRunner(DBUtil.getPool());
        Emp emp = qr.query("select * from emp",new BeanHandler<Emp>(Emp.class));
        System.out.println(emp);
    }
    @Test
    public void testFindOneParam() throws SQLException {
        QueryRunner qr = new QueryRunner(DBUtil.getPool());
        Emp emp = qr.query("select * from emp where empno =?",
                           new BeanHandler<Emp>(Emp.class),9007);
        System.out.println(emp);
    }
    @Test
    public void testFindAll() throws SQLException {
        QueryRunner qr = new QueryRunner(DBUtil.getPool());
        List<Emp> emp = qr.query("select * from emp",
                                 new BeanListHandler<Emp>(Emp.class));
        System.out.println(emp);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值