1. JDBC基本操作
1.1. JDBC概述
- JDBC(Java Data Base Connectivity)Java连接数据库
- 是一种用于执行SQL语句的Java API,为多种关系数据库提供统一访问
- 它由一组用Java语言编写的类和接口组成
- 有了JDBC,程序员只需用JDBC API写一个程序,就可以访问所有数据库
- SUN公司时规范制定者,制定了JDBC规范
- DriverManager类:管理不同的JDBC驱动
- Connection接口
- Statement接口和PreparedStatement接口
- ResultSet接口
- 数据库厂商,如微软、甲骨文等提供JDBC接口的驱动jar包
- 程序员学习JDBC规范应用jar包里的类
- JDBC访问数据库的步骤
- 加载一个Driver驱动
- 创建数据库连接Connection
- 创建SQL命令发送器Statement
- 通过Statement发送SQL命令并得到结果
- 处理结果(SELECT语句)
- 关闭数据库连接:ResultSet、Statement、Connection
1.2. 使用JDBC完成添加操作
public class TestInsert {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 0. 将相应数据库的jar包放入项目
// 1. 加载驱动
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
String user = "root";
String password = "root";
Class.forName(driver);
// 2. 建立(和数据库连接)
Connection conn = DriverManager.getConnection(url, user, password);
// 3. 创建一个SQL命令发送器
Statement stmt = conn.createStatement();
// 4. 使用SQL命令发送器(手枪)发送SQL命令(子弹)并得到结果
String sql = "insert into dept values(101, '教学部', '北京')";
// insert update delete返回值表明添加了几条数据
int n = stmt.executeUpdate(sql);
// 5. 处理结果
if(n > 0) {
System.out.println("添加部门成功");
}else {
System.out.println("添加部门失败");
}
// 6. 关闭各种数据库资源
stmt.close();
conn.close();
}
}
总结:
- 错误: Exception in thread “main” java.lang.ClassNotFoundException: com.mysql.jdbc2.Driver,原因:没有添加jar或com.mysql.jdbc2.Driver路径错误
- 理解:Class.forName(“com.mysql.jdbc.Driver”);的作用:第一次使用com.mysql.jdbc.Driver,就会执行静态代码块,注册驱动,注册之后 jar–META-INF–services–java.sql.Driver–com.mysql.jdbc.Driver,会自动找到该内容并加载
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
}catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
2. JDBC基本操作
2.1. 使用JDBC完成更新删除操作
使用JDBC完成更新、删除操作的操作步骤和完成添加操作的步骤完全相同,唯一不同的就是SQL语句不通
String sql = "update dept set dname='咨询部' where deptno = 90";
String sql = "delete from dept where deptno > 70";
2.2. 使用JDBC完成查询操作
public class TestSelect {
public static void main(String[] args) {
// 将相应数据库的jar包放入项目
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
int n = 0;
try {
// 1. 加载驱动
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
String user = "root";
String password = "root";
Class.forName(driver);
// 2. 建立和数据库连接
conn = DriverManager.getConnection(url, user, password);
// 3. 创建一个SQL命令发送器
stmt = conn.createStatement();
// 4. 使用SQL命令发送器发送SQL命令(子弹)并得到结果
String sql = "select * from dept where deptno > 20";
rs = stmt.executeQuery(sql);
// 5. 处理结果
System.out.println("编号\t名称\t地址");
while (rs.next()) {
// 获取各列的数据
int deptno = rs.getInt(1);
String dname = rs.getString(2);
String location = rs.getString(3);
System.out.println(deptno + "\t" + dname + "\t" + location);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 6. 关闭各种数据库资源
try {
if(stmt != null) {
stmt.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
- ResultSet里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行。想要取得某一条记录,就要使用ResultSet的next()方法,如果想要得到ResultSet中的所有记录,就要使用while循环
- ResultSet对象自动维护指向当前数据行的游标。每调用一次next()方法,游标向下移动一行
- 初始状态下记录指针指向第一条数据的前面,通过next()方法指向第一条记录。循环完毕后指向最后一条记录的后面
- 作为一种好的编程风格,应在不需要Statement对象和Connection对象时显式地关闭他们。
- 用户不必关闭ResultSet。当他的Statement关闭、重新执行或用于从多结果序列中获取下一个结果时,该ResultSet将被自动关闭
// 封装后台查询数据并在前台显示
public class TestSelect2 {
// 前台页面
public static void main(String[] args) {
// 点击查询按钮
List<Dept> deptList = selectAll();
// 显示查询结果
System.out.println("编号\t名称\t地址");
for (Dept dept : deptList) {
System.out.println(dept.getDeptno() + "\t" + dept.getDname() + "\t" + dept.getLocation());
}
}
// 后台服务器
public static List<Dept> selectAll() {
// 将相应数据库的jar包放入项目
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<Dept> deptList = new ArrayList<Dept>();
try {
// 1. 加载驱动
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
String user = "root";
String password = "root";
Class.forName(driver);
// 2. 建立数据库连接
conn = DriverManager.getConnection(url, user, password);
// 3. 创建一个SQL命令发送器
stmt = conn.createStatement();
// 4. 使用SQL命令发送器来发送SQL命令(子弹)并得到结果
String sql = "select * from dept";
rs = stmt.executeQuery(sql);
// 5. 处理结果
while (rs.next()) {
// 获取当前行各个列的数据
int deptno = rs.getInt("deptno");
String dname = rs.getString("dname");
// 可以使用列的编号,更建议使用列的名称,不区分大小写
String loc = rs.getString("loc");
// 将当前行各个列的数据封装到一个Dept对象中
Dept dept = new Dept(deptno, dname, loc);
// 将Dept对象加入到集合中
deptList.add(dept);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
}
return deptList;
}
}
3. JDBC高级操作
3.1. PreparedStatement
模拟淘宝登陆功能。用户在前台输入用户名和密码,后台判断信息是否正确,并给出前台反馈信息,前台输出反馈信息。
- 创建数据库表t_user
CREATE TABLE t_user(
userid VARCHAR(10) PRIMARY KEY, -- 字符串做主键无法实现自增
realname VARCHAR(3) NOT NULL,
password VARCHAR(6) NOT NULL,
money DOUBLE(10, 2)
);
SELECT * FROM t_user;
INSERT INTO t_user VALUES('wyb', '王一博', 'wyb', 100000), ('xz', '肖战', 'xz', 200000);
- 创建实体类User
- 开发前后台代码
- 进行测试
public class TestLogin {
// 前台
public static void main(String[] args) {
// 1. 从键盘输入用户名和密码
Scanner input = new Scanner(System.in);
System.out.println("请输入用户名");
String userId = input.next();
System.out.println("请输入密码");
String password = input.next();
// 2. 调用后台完成登陆
User user = login(userId, password);
// 3. 输出结果
if(user != null) {
System.out.println("登陆成功,当前用户名:" + user.getRealname());
}else {
System.out.println("登陆失败,请重新登陆");
}
}
public static User login(String userId, String pwd) {
// 将相应数据库的jar包放入项目
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
User user = null; // 默认登陆失败
try {
// 1. 加载驱动
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "root";
Class.forName(driver);
// 2. 建立和数据库连接
conn = DriverManager.getConnection(url, username, password);
// 3. 创建一个SQL命令发送器
stmt = conn.createStatement();
// 4. 使用SQL命令发送器来发送SQL命令并得到结果
String sql = "select * from t_user where userid = '" + userId + "' and password = '" + pwd + "'";
System.out.println(sql);
rs = stmt.executeQuery(sql);
// 5. 处理结果
if(rs.next()) { // 登陆成功,查询到了数据
// 获取当前行各个列的数据
String realName = rs.getString("realname");
double money = rs.getDouble("money");
// 将当前行各个列的数据封装到一个User对象中
user = new User(userId, realName, null, money);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
}
return user;
}
}
问题:有SQL注入风险,原因就是SQL语句是字符串拼接的。使用PreparedStatement来解决
- PreparedStatement和Statement的关系和区别
- 关系:public interface PreparedStatement extends Statement
- 区别
- PreparedStatement安全性高,可以避免SQL注入
- PreparedStatement简单不繁琐,不用进行字符串拼接
- 用在执行多个相同数据库DML操作时,PreparedStatement性能高
public static User login(String userId, String pwd) {
// 将相应数据库的jar包放入项目
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
User user = null; // 默认登陆失败
try {
// 1. 加载驱动
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "root";
Class.forName(driver);
// 2. 建立和数据库连接
conn = DriverManager.getConnection(url, username, password);
// 3. 使用SQL命令发送器来发送SQL命令并得到结果
String sql = "select * from t_user where userid = ? and password = ?"; // ?:占位符
// 4. 创建一个SQL命令发送器
pstmt = conn.prepareStatement(sql);
// 5. 使用SQL命令发送器来发送SQL命令并得到结果
pstmt.setString(1, userId);
pstmt.setString(2, pwd);
rs = pstmt.executeQuery();
// 6. 处理结果
if(rs.next()) {
String realName = rs.getString("realname");
double money = rs.getDouble("money");
user = new User(userId, realName, null, money);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}finally {
}
return user;
}
3.2. JDBC中使用事务
- 在JDBC中,事务操作缺省是自动提交的
- 一条对数据库的DML(insert、update、delete)代表一项事务操作
- 操作成功后,系统将自动调用commit()提交,否则自动调用rollback()回滚
- 在JDBC中,事务操作方法都位于接口java.sql.Connection中
- 可以通过调用setAutoCommit(false)来禁止自动提交
- 之后就可以把多个数据库操作的表达式作为一个事务,在操作完成之后调用commit()来进行整体提交
- 倘若其中一个表达式操作失败,都不会执行到commit(),并且将产生响应的异常;此时就可以在异常捕获时调用rollback()进行回滚,恢复至初始数据状态
- 事务开始的边界是组成当前事务的所有statement中的第一个被执行的时候
- 事务结束的边界是commit或rollback方法被调用
public class TestTransaction {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1. 加载驱动
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
String user = "root";
String password = "root";
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
// 事务不再自动结束,需要手动的提交或回滚
conn.setAutoCommit(false);
stmt.executeUpdate("update t_user set money = money - 2000 where userid = 'wyb'");
stmt.executeUpdate("update t_user set money = money1 + 2000 where userid = 'xz'");
// 手动提交事务,能执行该语句,表明前面多个DML操作都可以成功,只是数据只写入缓存,还没有真正写入数据库
conn.commit();
} catch (SQLException e) {
// 手动的回滚事务,回到所有DML操作执行之前的状态
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
}
}
}
3.3. JDBC API总结
- Connection接口:代表数据库连接
- DriverManager类:管理一组JDBC驱动程序的基本服务,应用程序不再需要使用Class.forName()显式的加载JDBC驱动程序,在调用getConnection方法时,DriverManager会试着从初始化时加载的那些驱动程序以及使用与当前applet或应用程序相同的类加载器显示加载的那些驱动程序中查找合适的驱动程序
- Statement接口:用于将SQL语句发送到数据库中,或理解为执行sql语句
- Statement:用于执行不带参数的简单SQL语句
- PreparedStatement(从Statement继承):用于执行带或不带参数的预编译SQL语句
- CallableStatement(从Statement继承):用于执行数据库存储过程的调用
- ResultSet接口:初始状态下记录指针指向第一条记录的前面,通过next()方法指向第一条记录。循环完毕后指向最后一条记录的后面。
4. 案例:员工管理系统
- 项目功能:查询所有、按照编号查询、添加员工、更新员工、删除员工
- 项目技能:
- 使用JDBC访问数据库
- 分层开发:前台:调用后台并输出结果,后台:使用JDBC访问数据库并返回结果
- 提供工具类DBUtil,复用代码
- 使用Properties类读取属性文件
- 使用log4j记录日志
- 扩展:提取查询方法(使用反射)
4.1. 搭建项目框架
- 创建项目empmgr
- 添加jar包
- 创建包
- com.wyb.dao:后台:存放访问数据库的接口,EmployeeDao
- com.wyb.dao.imp:后台:存放访问数据库的实现类,EmployeeDaoImpl
- com.wyb.entity:后台:存放实体类Employee
- com.wyb.test:前台
- com.wyb.util:工具类,提取数据库常用操作便于重复调用,避免代码重复
- 创建实体类Employee
- 创建后台的接口EmployeeDao和实现类EmployeeDaoImpl
public Lisk<Employee> findAll();
public Employee findById(int empno);
public int save(Employee emp);
public int update(Employee emp);
public void delete(int empno);
4.2. Employee类和EmployeeDao接口
package com.wyb.dao;
import com.wyb.entity.Employee;
import java.util.List;
public interface EmployeeDao {
/*
* 查询所有员工
* */
public List<Employee> findAll();
/*
* 查询指定编号的员工
* */
public Employee findById(int empno);
/*
* 添加员工
* */
public int save(Employee emp);
/*
* 修改员工信息
* */
public int update(Employee emp);
/*
* 删除指定编号的员工
* */
public int delete(int empno);
}
package com.wyb.entity;
import java.util.Date;
public class Employee {
private int empno;
private String ename;
private String job;
private int mgr;
private Date hireDate;
private double sal;
private double comm;
private int deptno;
public Employee(int empno, String ename, String job, int mgr, Date hireDate, double sal, double comm, int 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 Employee(String ename, String job, int mgr, Date hireDate, double sal, double comm, int deptno) {
this.ename = ename;
this.job = job;
this.mgr = mgr;
this.hireDate = hireDate;
this.sal = sal;
this.comm = comm;
this.deptno = deptno;
}
public Employee(int empno, String job, double sal, int deptno) {
this.empno = empno;
this.job = job;
this.sal = sal;
this.deptno = deptno;
}
public int getEmpno() {
return empno;
}
public void setEmpno(int 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 int getMgr() {
return mgr;
}
public void setMgr(int 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 int getDeptno() {
return deptno;
}
public void setDeptno(int deptno) {
this.deptno = deptno;
}
@Override
public String toString() {
return "Employee{" +
"empno=" + empno +
", ename='" + ename + '\'' +
", job='" + job + '\'' +
", mgr=" + mgr +
", hireDate=" + hireDate +
", sal=" + sal +
", comm=" + comm +
", deptno=" + deptno +
'}';
}
}
4.3. 查询所有员工
后台代码
public class EmployeeDaoImpl implements EmployeeDao {
@Override
public List<Employee> findAll() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<Employee> list = new ArrayList<Employee>();
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table";
String user = "root";
String password = "root";
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
String sql = "select * from emp";
rs = stmt.executeQuery(sql);
while (rs.next()) {
int empno = rs.getInt(1);
String ename = rs.getString(2);
String job = rs.getString(3);
int mgr = rs.getInt(4);
Date hiredate = rs.getDate("hiredate");
double sal = rs.getDouble("sal");
double comm = rs.getDouble("comm");
int deptno = rs.getInt("deptno");
Employee emp = new Employee(empno, ename, job,mgr, hiredate, sal, comm, deptno);
list.add(emp);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try {
if(rs != null) {
rs.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(stmt != null) {
stmt.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return list;
}
}
前台代码
package com.wyb.test;
import com.wyb.dao.EmployeeDao;
import com.wyb.dao.impl.EmployeeDaoImpl;
import com.wyb.entity.Employee;
import java.util.List;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
findAll();
}
private static void findAll() {
// 1. 调用后台获取所有员工信息
EmployeeDao employeeDao = new EmployeeDaoImpl();
List<Employee> list = employeeDao.findAll();
System.out.println("编号\t\t姓名\t\t岗位\t\t\t上级\t\t入职时间\t\t薪水\t\t补助\t\t部门编号");
for (Employee emp : list) {
System.out.println(emp.getEmpno() + "\t" + emp.getEname() + "\t" + emp.getJob() + "\t" + emp.getMgr() + "\t" + emp.getHireDate() + "\t" + emp.getSal() + "\t" + emp.getComm() + "\t" + emp.getDeptno());
}
}
}
4.4. 查询指定编号的员工
后台代码
@Override
public Employee findById(int empno) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
Employee emp = null;
List<Employee> list = new ArrayList<Employee>();
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table";
String user = "root";
String password = "root";
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
String sql = "select * from emp where empno = " + empno;
rs = stmt.executeQuery(sql);
while (rs.next()) {
String ename = rs.getString(2);
String job = rs.getString(3);
int mgr = rs.getInt(4);
Date hiredate = rs.getDate("hiredate");
double sal = rs.getDouble("sal");
double comm = rs.getDouble("comm");
int deptno = rs.getInt("deptno");
emp = new Employee(ename, job,mgr, hiredate, sal, comm, deptno);
list.add(emp);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try {
if(rs != null) {
rs.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(stmt != null) {
stmt.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return emp;
}
前台代码
private static void findById() {
Scanner input = new Scanner(System.in);
System.out.println("请输入员工编号:");
int empno = input.nextInt();
// 调用后台获取指定编号的员工信息
EmployeeDaoImpl employeeDao = new EmployeeDaoImpl();
Employee emp = employeeDao.findById(empno);
if(emp == null) {
System.out.println("员工不存在");
}else {
System.out.println(emp);
}
}
4.5. 提取DBUtil工具类
在查询所有员工信息、查询指定编号员工信息的DAO层代码中,建立数据库连接、关闭数据库资源的代码重复了,可以提前到一个工具类的方法中供调用,避免代码重复,也利用修改维护。
package com.wyb.util;
import java.sql.*;
public class DBUtil {
/*
* 创建并返回数据库连接
* */
public static Connection getConnection() {
Connection conn = null;
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/table";
String user = "root";
String password = "root";
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return conn;
}
/*
* 关闭资源
* */
public static void closeAll(ResultSet rs, Statement stmt, Connection conn) {
try {
if(rs != null) {
rs.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(stmt != null) {
stmt.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
4.6. 添加员工
@Override
public int save(Employee emp) {
Connection conn = null;
PreparedStatement pstmt = null;
int n = 0;
try {
conn = DBUtil.getConnection();
String sql = "insert into emp values(null, ?, ?, ?, ?, ?, ?, ?)";
pstmt = conn.prepareStatement(sql);
// 使用Statement发送SQL命令并得到结果
pstmt.setString(1, emp.getEname());
pstmt.setString(2, emp.getJob());
pstmt.setInt(3, emp.getMgr());
pstmt.setDate(4, new java.sql.Date(emp.getHireDate().getTime()));
pstmt.setDouble(5, emp.getSal());
pstmt.setDouble(6, emp.getComm());
pstmt.setInt(7, emp.getDeptno());
n = pstmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 6. 关闭资源
DBUtil.closeAll(null, pstmt, conn);
}
// 返回数据
return n;
}
private static void save() {
// 从键盘输入要添加的员工信息
Scanner input = new Scanner(System.in);
System.out.println("请输入员工姓名");
String ename = input.next();
System.out.println("请输入岗位");
String job = input.next();
System.out.println("请输入员工上级编号");
int mgr = input.nextInt();
System.out.println("请输入员工入职时间(yyyy-MM-dd)");
String sdate = input.next();
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date hireDate = null;
try {
hireDate = sdf.parse(sdate);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("请输入员工薪水");
double sal = input.nextDouble();
System.out.println("请输入员工津贴");
double comm = input.nextDouble();
System.out.println("请输入员工部门编号");
int deptno = input.nextInt();
// 调用后台完成添加操作并返回结果
Employee emp = new Employee(ename, job, mgr, hireDate, sal, comm, deptno);
EmployeeDaoImpl employeeDao = new EmployeeDaoImpl();
int n = employeeDao.save(emp);
if(n > 0) {
System.out.println("添加成功");
}else {
System.out.println("添加失败");
}
}
问题:java.util.Date和java.sql.Date关系
联系:public class Date extend java.util.Date
区别
- java.util.Date yyyy-MM-dd hh:mm:ss java.sql.Date yyyy-MM-dd
- java.util.Date有无参数构造方法,new Date()获取当前的时间; java.sql.Date没有无参数构造方法,但有Date.valueOf(“1997-08-05”);
- java.util.Date str --> Date DateFormat; java.sql.Date str --> Date Date.valueOf(“1997-08-05”)
问题: util.Date转换为 sql.Date:new java.sql.Date(emp.getHireDate().getTime())
问题:JDBC缺点
需要手动进行关系和对象的转换
添加操作是需要将对象数据转换为关系表的数据;查询操作是需要将关系数据转换为对象的数据
转换必须要做,JDBC是程序员做的,增加了时间;可以交给框架来做,比如MyBats、Hibernate
4.7. 完善DBUtil类
修改员工、删除员工功能的代码和添加员工非常相似,只是SQL语句和传递的具体参数的不同。
可以为insert、update、delete这三个DML操作提取一个方法呢
/*
* 针对DML操作:insert、update、delete
* */
public static int executeUpdate(String sql, Object[] params) {
Connection conn = null;
PreparedStatement pstmt = null;
int n = 0;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
n = pstmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
closeAll(null, pstmt, conn);
}
return n;
}
EmployeeDaoImpl的三个DML方法调用DButil的executeUpdate()方法后,代码大大的简化了
@Override
public int save(Employee emp) {
String sql = "insert into emp values(null, ?, ?, ?, ?, ?, ?, ?)";
Object[] params = {emp.getEname(), emp.getJob(), emp.getMgr(), emp.getHireDate(), emp.getSal(), emp.getComm(), emp.getDeptno()}
return DBUtil.executeUpdate(sql, params);
}
@Override
public int update(Employee emp) {
String sql = "update emp set job = ?, sal = ? where empno = ?";
Object[] params = {emp.getJob(), emp.getSal(), emp.getEmpno()};
return DBUtil.executeUpdate(sql, params);
}
@Override
public int delete(int empno) {
return DBUtil.executeUpdate("delete from emp where empno = ?", new Object[]{empno});
}
4.8. 添加主菜单
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
public class Test2 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
do {
System.out.println("");
System.out.println("欢迎使用员工管理系统");
System.out.println("\t1.查询所有员工");
System.out.println("\t2.查询指定编号员工");
System.out.println("\t3.添加员工信息");
System.out.println("\t4.修改员工信息");
System.out.println("\t5.删除员工信息");
System.out.println("\t6.退出");
System.out.println("请选择菜单");
int choice = input.nextInt();
switch (choice) {
case 1: findAll();break;
case 2: findById();break;
case 3: save();break;
case 4: update();break;
case 5: delete();break;
case 6:
System.out.println("谢谢使用");
return;
default:
System.out.println("输入错误");
}
System.out.println("按任意键继续");
input.nextLine();
input.nextLine();
}while (true);
}
public static void delete() {
Scanner input = new Scanner(System.in);
System.out.println("请输入员工编号");
int empno = input.nextInt();
// 调用后台完成删除操作并返回结果
EmployeeDao employeeDao = new EmployeeDaoImpl();
int n = employeeDao.delete(empno);
if(n > 0) {
System.out.println("删除成功");
}else {
System.out.println("删除失败");
}
}
public static void update() {
Scanner input = new Scanner(System.in);
System.out.println("请输入员工岗位");
String job = input.next();
System.out.println("请输入员工薪水");
double sal = input.nextDouble();
System.out.println("请输入员工部门编号");
int deptno = input.nextInt();
System.out.println("请输入员工编号");
int empno = input.nextInt();
Employee emp = new Employee(empno, job, sal, deptno);
EmployeeDaoImpl employeeDao = new EmployeeDaoImpl();
int n = employeeDao.update(emp);
if(n > 0) {
System.out.println("修改成功");
}else {
System.out.println("修改失败");
}
}
public static void save(){
//从键盘输入要添加的员工信息
Scanner input = new Scanner(System.in);
System.out.println("请输入员工姓名");
String ename = input.next();
System.out.println("请输入员工岗位");
String job = input.next();
System.out.println("请输入员工上级编号");
int mgr = input.nextInt();
System.out.println("请输入员工入职时间(yyyy-MM-dd)");
String sdate = input.next(); //"1999-12-23"
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date hireDate = null;
try {
hireDate = sdf.parse(sdate);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("请输入员工薪水");
double sal = input.nextDouble();
System.out.println("请输入员工津贴");
double comm = input.nextDouble();
System.out.println("请输入员工部门编号");
int deptno = input.nextInt();
//调用后台完成添加操作并返回结果
Employee emp = new Employee(ename, job, mgr, hireDate,
sal, comm, deptno);
EmployeeDao employeeDao = new EmployeeDaoImpl();
int n = employeeDao.save(emp);
//输出结果
if(n>0){
System.out.println("添加成功");
}else{
System.out.println("添加失败");
}
}
public static void findById(){
//从键盘输入员工编号
Scanner input = new Scanner(System.in);
System.out.println("请输入员工编号");
int empno =input.nextInt();
//调用后台获取指定编号员工
EmployeeDao employeeDao = new EmployeeDaoImpl();
Employee emp = employeeDao.findById(empno);
//在前台输出员工列表
if(emp == null){
System.out.println("查无此人");
}else{
System.out.println("编号\t姓名\t岗位\t上级编号\t入职时间\t薪水\t补助\t所属部门编号");
System.out.println(emp.getEmpno()+"\t"+emp.getEname()+"\t"+emp.getJob()+
"\t"+emp.getMgr()+"\t"+emp.getHireDate()+
"\t"+emp.getSal()+"\t"+emp.getComm()+"\t"+emp.getDeptno());
}
}
/**
* 查询所有员工的前台
*/
public static void findAll(){
//调用后台获取员工列表
EmployeeDao employeeDao = new EmployeeDaoImpl();
List<Employee> empList = employeeDao.findAll();
//在前台输出员工列表
System.out.println("编号\t姓名\t岗位\t上级编号\t入职时间\t薪水\t补助\t所属部门编号");
for(Employee emp:empList){
System.out.println(emp.getEmpno()+"\t"+emp.getEname()+"\t"+emp.getJob()+
"\t"+emp.getMgr()+"\t"+emp.getHireDate()+
"\t"+emp.getSal()+"\t"+emp.getComm()+"\t"+emp.getDeptno());
}
}
}
4.9. 使用属性文件存储数据库连接参数
属性文件是一个文本文件,可以直接打开修改,不涉及编译问题
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/table
user=root
pwd=root
读取属性文件:用类Properties来完成。并且不仅可以读属性文件,也可以写属性文件
认识Properties类
- Properties也是一个集合类,并且使用Map类型的集合类,存储key-value
public class Properties extend Hashtable<Object, Object>
- 特点:key、value都是String类型
- 作用:读+写属性文件,更多的是读
- 存储键值对:
prop.setProperty("cn", "china");
- 根据key找到对应的value:
String pwd = prop.getPropperty("pwd")
- 读属性文件
InputStream is = Test.class.getResourceAsStream("/jdbc.properties");
prop.load(is);
- 写属性文件:
prop.store(out, comments);
/*
* 读取属性文件
* */
private DBUtil() {
}
private static String driver;
private static String url;
private static String user;
private static String password;
static {
// 读取属性文件,根据key获取四个连接参数value
// 1. 创建Properties对象
Properties prop = new Properties();
// 2. 使用Properties对象读取属性文件并存储键值对
InputStream is = DBUtil.class.getResourceAsStream("/jdbc.propertoes");
try {
prop.load(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 3. 根据key找到value
driver = prop.getProperty("driver");
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("pwd");
}
4.10. 使用log4j记录日志
- log4j日志的级别
- FATAL:出现非常严重的错误事件,可能导致应用程序异常终止
- ERROR:虽有错误,但仍允许应用程序继续进行
- WARN:运行环境潜藏着危害
- INFO:报告信息,这些信息在粗粒度级别上突出显示应用程序的进程
- DEBUG:细粒度信息事件,用于应用程序调试
- 使用log4j记录日志
- 加入jar包:log4j-1.2.8.jar
- 加入属性文件:src下log4j.properties
log4j.rootLogger=error,logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=/Users/mingqi/Desktop/audio/java/practive/jdbc_empmgr/log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n
- Appender 日志目的地 : ConsoleAppender FileAppender
- Layout 日志格式化器 :SimpleLayout PatternLayout
- 代码中记录日志
//创建一个日志记录器
private static final Logger logger = Logger.getLogger(DBUtil.class.getName());
//在合适的地方添加日志
logger.info("正确的读取了属性文件:"+prop);
logger.debug("正确的关闭了结果集");
logger.error("DML操作错误:"+e);
5. 连接池
5.1. 连接池原理
建立数据库连接的方式有两种
- 传统连接方式:
- 首先调用Class.forName()方法加载数据库驱动
- 然后调用DriverManager.getConnection()方法建立连接
- 连接池技术:
- 连接池是应用程序启动时就预先建立多个数据库连接对象,然后将连接对象保存到连接池中
- 当客户请求时,从池中取出一个连接对象为客户服务
- 当完成请求时,客户程序调用close()方法,将连接对象放回池中
- 多于连接池中连接数的请求,排队等待
- 应用程序还可根据连接池中连接的使用率,动态增加或减少池中的连接数
- 传统数据库连接方式的缺点
- 一个连接对象对应一个物理连接
- 每次操作都打开一个物理连接
- 使用完都关闭连接,造成系统性能低下
- 连接池技术的优点
- 客户程序得到的连接对象是连接池中物理连接的一个句柄
- 调用连接对象的close()方法,物理连接并没有关闭,数据源的实现知识删除了应用程序中的连接对象和池中的连接对象之间的联系
- 数据库连接的建立及关闭是耗费系统资源的操作,为了能重复利用数据库连接对象,缩短请求的响应时间、提高服务器的性能,应采用连接池技术
5.2. 连接池的实现
package src.com.db1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class ConnectionPool {
private static LinkedList<Connection> list = new LinkedList<Connection>();
/*
* 只执行一次,第一次加载类时执行,默认10个连接
* */
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < 10; i++) {
Connection conn = newConnection();
list.addLast(conn);
}
}
public static Connection newConnection() {
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql//localhost:3306/table", "root", "root");
} catch (SQLException e) {
throw new RuntimeException(e);
}
return conn;
}
public static Connection getConnection() {
// 如果连接池中有连接,就返回
if(list.size() > 0) {
return list.removeFirst();
}else {
// 如果没有连接,就创建一个新的物理连接
return newConnection();
}
}
public static void returnConnection(Connection conn) {
// 数量小于10,不关闭连接,而是放入连接池
if(list.size() < 10) {
list.addLast(conn);
}else {
// 关闭物理连接
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
// 获取连接池中连接
ConnectionPool.getConnection();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
可以使用List来存储多个连接,因为连接池的主要操作是连接的获取和归还,就是添加和删除两个操作,使用LinkList的效率更高。