文章目录
问题描述
我们以访问oracl数据库的emp表为例,来对java对数据库的访问相关问题进行一下详细讨论。
我们需要完成的功能是可以通过命令控制台以及键盘输入,来实现对emp表的增,删,查,改等功能。
这里不讨论通过任何框架来实现数据库的访问,比如mybatis等。
但会考虑三层架构等设计问题。
类的设计
三层架构就是为了符合“高内聚,低耦合”思想,把各个功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构,各层之间采用接口相互访问,并通过对象模型的实体类(Model)作为数据传递的载体。
从上图我们可以分析出,所需要的类有:
实体层(entity)
Emp类
Emp类,属业务实体类,因考虑到方便,实体类的名称与表的名称相一致。其属性与表中的字段一一对应,并有各属性的setter和getter方法。
所有实体类放在com.javaoldman.entity包中。
- emp表结构
名称 是否为空? 类型
------- -------- ----------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)
- Emp类的定义
package com.javaoldman.entity;
public class Emp {
private int empno;
private String ename;
private String job;
private int mgr;
private double sal;
private double comm;
private int deptno;
public int getEmpno() {
return empno;
}
public String getEname() {
return ename;
}
public String getJob() {
return job;
}
public int getMgr() {
return mgr;
}
public double getSal() {
return sal;
}
public double getComm() {
return comm;
}
public int getDeptno() {
return deptno;
}
public void setEmpno(int empno) {
this.empno = empno;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setJob(String job) {
this.job = job;
}
public void setMgr(int mgr) {
this.mgr = mgr;
}
public void setSal(double sal) {
this.sal = sal;
}
public void setComm(double comm) {
this.comm = comm;
}
public void setDeptno(int deptno) {
this.deptno = deptno;
}
@Override
public String toString() {
return "Emp{" +
"empno=" + empno +
", ename='" + ename + '\'' +
", job='" + job + '\'' +
", mgr=" + mgr +
", sal=" + sal +
", comm=" + comm +
", deptno=" + deptno +
'}';
}
}
数据访问层(DAO)
数据访问层实现对于数据库的具体操作,包括增删改查等。
但考虑到可能会连接不同的数据库类型,所以, 为了解除业务逻辑层与DAO层的耦合,将DAO层进行符合依赖倒置原则的设计。即分成DAO接口和DAO具体实现类两层。
同理,为了解除表示层对业务逻辑层的依赖,也将业务逻辑层分成接口和实现类两层。
EmpDao 接口
EmpDao接口,定义数据访问的方法。主要方法有:
- 查询所有emp记录
- 按empno查询emp记录
- 按empno删除emp记录
- 添加新的emp记录
- 按empno来更新emp记录
所有dao接口放在com.javaoldman.dao包内。
EmpDao代码:
package com.javaoldman.dao;
import com.javaoldman.entity.Emp;
import java.util.List;
public interface EmpDao {
List<Emp> selectAllEmp();
Emp selectEmpByEmpno(int empno);
int deleteEmpByEmpno(int empno);
int insertEmp(Emp emp);
int updateEmp(Emp emp);
}
从接口方法的申明中可以看出,方法的入口参数以及返回值多是为Emp对象或集合,这也是将关系数据库中的表记录与实体对象进行映射的关键。这可以让业务逻辑层只面向对象,不需要面向数据库的细节。
JDBCUtil工具类
因为访问数据库,有许多的代码是重复的,包括数据库的驱动注册,连接对象等,还有数据库访问结束后的资源释放等。所以,可能定义一个工具类,来实现这些重复性的工作。比如直接获得一个连接对象,或直接释放数据库资源等。
这个类可以放在com.javaoldman.util包中
代码如下:
package com.javaoldman.util;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* JDBC工具类
*/
public class JDBCUtil {
private static String driver;
private static String url;
private static String username;
private static String password;
//静态代码块,在程序编译的时候执行
static {
try {
//创建Properties对象
Properties p = new Properties();
//获取文件输入流
InputStream in = JDBCUtil.class.getResourceAsStream("/db.properties");
//加载输入流
p.load(in);
//获取数据库连接驱动名字
driver = p.getProperty("driver",null);
//获取数据库连接地址
url = p.getProperty("url",null);
//获取数据库连接用户名
username = p.getProperty("username",null);
//获取数据库连接密码
password = p.getProperty("password",null);
if(driver != null && url != null
&& username != null && password != null){
//加载驱动
Class.forName(driver);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接对象
* @return Connection连接对象
*/
public static Connection getConn(){
Connection conn = null;
try {
conn = DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭连接(Connection连接对象必须在最后关闭)
* @param conn Connection连接对象
* @param st 编译执行对象
* @param rs 结果集
*/
public static void close(Connection conn, Statement st, ResultSet rs){
try {
if(rs != null){
rs.close();
}
if(st != null){
st.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
属性文件
数据库连接需要一些属性数据,比如URL,用户名,密码,还有驱动的类名等,为了让修改,可以将这些信息保存在一个属性文件中,方便修改。在工具类中,读取这个属性文件来设置相关的值。
在项目目录中创建一个properties的属性文件db.properties,里面定义了四行数据,分别对应着JDBC连接所需要的几个参数(注:Properties底层为一个Hashtable,配置文件中“=”之前的代表Map中的键,之后的代表相应键所对应的值)
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
username=scott
password=tiger
EmpDaoImpl实现类
EmpDaoImpl是对EmpDao接口的具体实现,分别实现接口中的所有方法。在该类中,实现具体的通过JDBC访问ORACLE数据库的相关操作。
该类放在com.javaoldman.dao.impl包中。
代码:
package com.javaoldman.dao.impl;
import com.javaoldman.dao.EmpDao;
import com.javaoldman.entity.Emp;
import com.javaoldman.util.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class EmpDaoImpl implements EmpDao {
@Override
public List<Emp> selectAllEmp() {
Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
String sql = "select * from emp";
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Emp> empList = new ArrayList<>();
try {
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
Emp emp = new Emp();
emp.setEmpno(resultSet.getInt("empno"));
emp.setEname(resultSet.getString("ename"));
emp.setJob(resultSet.getString("job"));
emp.setMgr(resultSet.getInt("mgr"));
emp.setSal(resultSet.getDouble("sal"));
emp.setComm(resultSet.getDouble("comm"));
emp.setDeptno(resultSet.getInt("deptno")); //组装emp对象
empList.add(emp); //组装emp集合
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.close(connection,preparedStatement,resultSet); //释放数据库资源
}
return empList; //返回emp集合
}
@Override
public Emp selectEmpByEmpno(int empno) {
Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
String sql = "select * from emp where empno = ?";
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Emp> empList = new ArrayList<>();
Emp emp = null;
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,empno);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()){
emp = new Emp();
emp.setEmpno(resultSet.getInt("empno"));
emp.setEname(resultSet.getString("ename"));
emp.setJob(resultSet.getString("job"));
emp.setMgr(resultSet.getInt("mgr"));
emp.setSal(resultSet.getDouble("sal"));
emp.setComm(resultSet.getDouble("comm"));
emp.setDeptno(resultSet.getInt("deptno")); //组装emp对象
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.close(connection,preparedStatement,resultSet); //释放数据库资源
}
return emp; //如果有记录,就返回emp,否则返回null
}
@Override
public int deleteEmpByEmpno(int empno) {
Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
String sql = "delete from emp where empno = ?";
PreparedStatement preparedStatement = null;
int num = 0;
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,empno);
num = preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.close(connection,preparedStatement,null); //释放数据库资源,因为没有记录集对象,所以,用null
}
return num; //返回受影响的行数
}
@Override
public int insertEmp(Emp emp) {
Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
int empno;
String ename;
String job;
int mgr;
double sal;
double comm;
int deptno;
empno = emp.getEmpno();
ename = emp.getEname();
job = emp.getJob();
mgr = emp.getMgr();
sal = emp.getSal();
comm = emp.getComm();
deptno = emp.getDeptno();
String sql = "insert into emp(empno,ename,job,mgr,sal,comm,deptno) values(?,?,?,?,?,?,?)";
PreparedStatement preparedStatement = null;
int num = 0;
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,empno);
preparedStatement.setString(2,ename);
preparedStatement.setString(3,job);
preparedStatement.setInt(4,mgr);
preparedStatement.setDouble(5,sal);
preparedStatement.setDouble(6,comm);
preparedStatement.setInt(7,deptno);
num = preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.close(connection,preparedStatement,null); //释放数据库资源,因为没有记录集对象,所以,用null
}
return num; //返回受影响的行数
}
@Override
public int updateEmp(Emp emp) {
Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
int empno;
String ename;
String job;
int mgr;
double sal;
double comm;
int deptno;
empno = emp.getEmpno();
ename = emp.getEname();
job = emp.getJob();
mgr = emp.getMgr();
sal = emp.getSal();
comm = emp.getComm();
deptno = emp.getDeptno();
String sql = "update emp set ename=?,job=?,mgr=?,sal=?,comm=?,deptno=? where empno=?";
PreparedStatement preparedStatement = null;
int num = 0;
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,ename);
preparedStatement.setString(2,job);
preparedStatement.setInt(3,mgr);
preparedStatement.setDouble(4,sal);
preparedStatement.setDouble(5,comm);
preparedStatement.setInt(6,deptno);
preparedStatement.setInt(7,empno);
num = preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.close(connection,preparedStatement,null); //释放数据库资源,因为没有记录集对象,所以,用null
}
return num; //返回受影响的行数
}
}
业务逻辑层(service)
业务逻辑层实现具体的业务功能,同样,为了解除表示层与业务逻辑层之间的耦合,实现依赖倒置原则,将业务逻辑层又分层为接口和具体的实现类。
EmpService接口
EmpService接口规范对Emp类对象的一些业务功能。
该接口定义在com.javaoldman.service包下。
代码如下:
package com.javaoldman.service;
import com.javaoldman.entity.Emp;
import java.util.List;
public interface EmpService {
List<Emp> getAllEmps();
Emp getEmpByEmpno(int empno);
int deleteEmpByEmpno(int empno);
int addEmp(Emp emp); //如果empno的记录已存在,返回-1,其他,返回添加成功的数量
int updateEmp(Emp emp); //如果empno的记录不存在,返回-1,其他,返回更新成功的数量
}
因为我们暂时需要的业务功能也只是简单的增删查改的业务功能,所以,看上去,EmpService接口的定义与EmpDao接口的定义有些类似。但是有区别。
- EmpDao是只负责对数据库的访问方法,
- EmpService是业务功能。这些业务功能需要使用EmpDao中的方法以及其他的一些业务逻辑代码来实现。甚至有许多情况下,还可能需要其他实体的Dao接口的实现类来合作完成。
例如:在网上购物的需求中,买东西的业务类,可能就需要一个buy方法,而该方法中可能需要使用到订单Dao和库存Dao。因为buy的业务的实现,需要增加订单,同时减少库存。
所以,从这个例子就可以看出,业务逻辑和DAO的所负责的责任是有较大的区别的。
在本例中,包括addEmp方法以及updateEmp方法,由于Emp表涉及到部分编号deptno。所以,其实,这里的业务逻辑类的实现类,在实现addEmp和updateEmp方法时,其实是需要EmpDao以及DeptDao两个DAO类的。因为在添加和更新时,要查询在Dept表中,是否存在deptno所对应的条记录。如果不存在,是不能添加或更新的。因为会违反表的参考约束。但在本例中,暂不考虑以上问题。
但为了对service和dao方法的实现有所区分,特意在addEmp和updateEmp中,都在业务逻辑上增加了先判断empno值对应的记录是否存在,然后再进行增加或更新。如此操作,仅为演示区分的效果。
EmpServiceImpl实现类
该实现类具体实现EmpService接口。
该类实义在com.javaoldman.service.impl包下。
代码如下:
package com.javaoldman.service.impl;
import com.javaoldman.dao.EmpDao;
import com.javaoldman.dao.impl.EmpDaoImpl;
import com.javaoldman.entity.Emp;
import com.javaoldman.service.EmpService;
import java.util.List;
public class EmpServiceImpl implements EmpService {
EmpDao dao = new EmpDaoImpl(); //实例化EmpDao的实现类对象
//下面,利用dao的方法来实现service的业务逻辑
@Override
public List<Emp> getAllEmps() {
return dao.selectAllEmp();
}
@Override
public Emp getEmpByEmpno(int empno) {
return dao.selectEmpByEmpno(empno);
}
@Override
public int deleteEmpByEmpno(int empno) {
return dao.deleteEmpByEmpno(empno);
}
@Override
public int addEmp(Emp emp) {
//为了演示service与Dao之间的区别。这里假设,在添加业务中,需要先判断这个empno的记录是否存在,如果存在就不添加,返回-1。
int empno;
empno = emp.getEmpno();
Emp emp2 = dao.selectEmpByEmpno(empno);
if(emp2!=null){ //如果已存在empno值的记录,就返回-1
return -1;
}
return dao.insertEmp(emp);
}
@Override
public int updateEmp(Emp emp) {
//为了演示service与Dao之间的区别。这里假设,在更新业务中,需要先判断这个empno的记录是否存在,如果不存在就不更新,返回-1。
int empno;
empno = emp.getEmpno();
Emp emp2 = dao.selectEmpByEmpno(empno);
if(emp2==null){ //如果不存在empno值的记录,就返回-1
return -1;
}
return dao.updateEmp(emp);
}
}
从以上代码可以看出,业务逻辑层只负责业务功能,而不会负责任何数据的展示,即不会进行打印,输出一类的操作。打印输出的操作会通过业务层返回的数据,交给表示层来完成。这也是单一职责原则的一种体现。
表示层(UI)
实现与用户之间的交互,并将数据转交给相应的业务逻辑层。
从上图可以看出,三层架构中的表示层,其他可以基本上包括MVC模型中的V(View)视图以及C(Controller),而MVC模型中的M(model),往往基本上包括了三层架构中的业务逻辑层,数据访问层以及实体类。
所以,前面,我们已实现了三层架构中的业务逻辑层和数据访问层,这也是MVC模型中的M模型。
现在还需要实现表示层。表示层在MVC模型中可以分成View视图层以及Controller控制层。
EmpController控制类
该类实现根据用户的选择,调用业务逻辑层的相关方法来实现用户的需求。
在本例中,用户会根据界面的提示,输入数字来选择相应的功能,所以,在本控制类中,主要是实现根据数字来调用业务功能。并将业务处理的结果返回给View进行显示。
该类定义在com.javaoldman.controller包下
代码如下:
package com.javaoldman.controller;
import com.javaoldman.entity.Emp;
import com.javaoldman.service.EmpService;
import com.javaoldman.service.impl.EmpServiceImpl;
import java.util.ArrayList;
import java.util.List;
public class EmpController {
EmpService service = new EmpServiceImpl();
Emp emp = null;
public EmpController(){
}
public void setEmp(Emp emp) {
this.emp = emp;
}
public List<Emp> doByOption(int option){
switch (option){
case 1:
return service.getAllEmps();
case 2:
Emp emp2 = service.getEmpByEmpno(emp.getEmpno());
List<Emp> empList = new ArrayList<>();
empList.add(emp2);
return empList;
case 3:
service.deleteEmpByEmpno(emp.getEmpno());
return service.getAllEmps();
case 4:
service.addEmp(emp);
return service.getAllEmps();
case 5:
service.updateEmp(emp);
return service.getAllEmps();
default:
return null;
}
}
}
View层将用户的输入包装成一个emp对象,然后将这个对象以及用户的选择号提交给controller对象,由Controller对象根据选择调用相应的业务逻辑类的方法。
EmpView类
该类实现与用户的交互,包括:
- 输出功能:将菜单显示给用户
- 输入功能:获取用户的输入
- 提交给控制层功能:并最后将用户的输入包装成为一个emp对象,和用户选择一起,提交给Controller层
- 获得控制层的返回数据功能:获得控制层的返回数据
- 输出功能:并从Controller获得处理结果,并加以显示
该类定义在com.javaoldman.view包下
代码如下:
package com.javaoldman.view;
import com.javaoldman.controller.EmpController;
import com.javaoldman.entity.Emp;
import java.util.List;
import java.util.Scanner;
public class EmpView {
public void displayMenu(){
Scanner scanner = new Scanner(System.in);
EmpController controller = new EmpController();
Emp emp = new Emp();
int option = 0;
int empno;
String ename;
String job;
int mgr;
double sal;
double comm;
int deptno;
//输出功能:显示菜单
System.out.println("功能菜单");
System.out.println("1 列出EMP表的所有记录");
System.out.println("2 删除EMP表中指定EMPNO的记录,并列出删除后的所有记录");
System.out.println("3 查询指定EMPNO的记录");
System.out.println("4 添加新的记录,并列出添加后的所有记录");
System.out.println("5 修改指定EMPNO的记录的字段信息,显示修改后的该记录");
System.out.println("6 退出");
//输入功能:获得用户的选择和输入
while(true){
System.out.println("请输入菜单前面的数字,将执行相应的功能:");
try {
option = scanner.nextInt();
}catch (Exception e){ //如果输入的不是数字,提示重新输入
System.out.println("只能输入数字,请重新输入!");
continue;
}
switch (option){
case 1:
break;
case 2:
System.out.println("请输入要查询的雇员编号,要求是整数");
empno = scanner.nextInt(); //获得雇员编号
emp.setEmpno(empno); //将empno值包装到emp对象中
break;
case 3:
System.out.println("请输入要删除的雇员编号,要求是整数");
empno = scanner.nextInt(); //获得雇员编号
emp.setEmpno(empno); //将empno值包装到emp对象中
break;
case 4:
System.out.println("请输入要添加的雇员编号,要求是整数");
empno = scanner.nextInt(); //获得雇员编号
System.out.println("请输入要添加的雇员名称");
ename = scanner.next();
System.out.println("请输入要添加的雇员工作");
job = scanner.next();
System.out.println("请输入要添加的雇员的领导的编号,要求是整数");
mgr = scanner.nextInt();
System.out.println("请输入要添加的雇员工资,要求是数字");
sal = scanner.nextDouble();
System.out.println("请输入要添加的雇员资金,要求是数字");
comm = scanner.nextDouble();
System.out.println("请输入要添加的雇员部门编号,要求只能是10,20,30,40中的一个");
deptno = scanner.nextInt();
emp.setEmpno(empno); //将empno值包装到emp对象中
emp.setEname(ename);
emp.setJob(job);
emp.setMgr(mgr);
emp.setSal(sal);
emp.setComm(comm);
emp.setDeptno(deptno);
break;
case 5:
System.out.println("请输入要更新的雇员编号,要求是整数");
empno = scanner.nextInt(); //获得雇员编号
System.out.println("请输入要更新的雇员名称");
ename = scanner.next();
System.out.println("请输入要更新的雇员工作");
job = scanner.next();
System.out.println("请输入要更新的雇员的领导的编号,要求是整数");
mgr = scanner.nextInt();
System.out.println("请输入要更新的雇员工资,要求是数字");
sal = scanner.nextDouble();
System.out.println("请输入要更新的雇员资金,要求是数字");
comm = scanner.nextDouble();
System.out.println("请输入要更新的雇员部门编号,要求只能是10,20,30,40中的一个");
deptno = scanner.nextInt();
emp.setEmpno(empno); //将empno值包装到emp对象中
emp.setEname(ename);
emp.setJob(job);
emp.setMgr(mgr);
emp.setSal(sal);
emp.setComm(comm);
emp.setDeptno(deptno);
break;
case 6:
return; //如果是选择6,则退出程序
}
//将用户输入提交给控制层,并得到控制层的返回数据
controller.setEmp(emp); //值emp对象给Controller类
List<Emp> empList = controller.doByOption(option);
//输出功能:对从控制层返回的数据进行显示
for(Emp emp1:empList){
System.out.println(emp1);
}
}
}
}
Client客户类
客户类作为整个功能的测试类,代码如下:
package com.javaoldman;
import com.javaoldman.view.EmpView;
public class Client
{
public static void main( String[] args )
{
EmpView view = new EmpView();
view.displayMenu();
}
}
结束语
本文以oracle数据库样本库中的emp表为例,按三层架构和MVC模式,对类进行了设计,并满足开闭原则和依赖倒置原则。
下图说明了这些类之间的依赖和实现关系
下图为该例中所有类文件的包结构图
此文仅供同学们参考,希望能帮助同学们理解java利用JDBC访问数据库的主要结构和方法。特别是帮助理解三层架构与MVC之间的关系和各类各自的职责。