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&useTimezone=true&useSSL=false&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;}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(empno, ename, job, mgr, hiredate, sal, comm, deptno);}@Overridepublic 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 {@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 {@Testpublic 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);}@Testpublic void testDeleteEmployee(){EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();dao.deleteEmployee(10000);}/*** 测试修改员工*/@Testpublic 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);}@Testpublic void testFindEmployeeById(){EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();Employee e1 = dao.findEmployeeById(7934);System.out.println(e1);}@Testpublic void testFindAll(){EmployeeDao dao = DaoFactory.getEmployeeDaoInstance();List<Employee> fall = dao.findAll();for (Employee e:fall){System.out.println(e);}}@Testpublic 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注解的方法执行完之后执行*/@Testpublic void test1(){System.out.println(1+2);}@Testpublic 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 {@Testpublic 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);}@Testpublic 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);}@Testpublic 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);}
}