四 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;
}
@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);
}
}